docs: add SECURITY.md and GPG release signing for OpenSSF passing badge

Satisfies vulnerability_report_process/know_secure_design/know_common_errors via SECURITY.md; delivery_unsigned via GPG-signed releases (public key in docs/signing-key.asc); N/A justifications for the 4 N/A-able MUST criteria (crypto_password_storage, vulnerability_report_private, vulnerability_report_response, release_notes_vulns). Backs the OpenSSF Best Practices passing badge and closes the CIIBestPracticesID alert.
This commit is contained in:
Devid HW
2026-08-26 03:17:38 +07:00
parent b843146554
commit c65c4b5d1c
5 changed files with 287 additions and 36 deletions
+33 -11
View File
@@ -2,12 +2,15 @@
"""List incomplete OpenSSF Best Practices criteria and generate a pre-filled edit URL.
Usage:
cii_criteria.py <project_id> [section] [--exclude n1,n2,n3]
cii_criteria.py <project_id> [section] [--exclude n1,n2,n3] [--na n1,n2,n3]
section: passing | silver | gold | baseline-1 | baseline-2 | baseline-3
(default: passing)
--exclude: comma-separated criterion names to SKIP from the pre-filled URL
(they stay at their current Unmet/?/N/A in the form).
--na: comma-separated criterion names to mark as N/A in the pre-filled URL
(emits <name>_status=N/A with the justification from the JSON).
section: passing | silver | gold | baseline-1 | baseline-2 | baseline-3
(default: passing)
--exclude: comma-separated criterion names to SKIP from the pre-filled URL
(they stay at their current Unmet/?/N/A in the form).
The script fetches the upstream criteria.yml to learn which criteria belong to
which tier/section, then restricts BOTH the printed incomplete list AND the
@@ -121,11 +124,15 @@ def main():
choices=list(SECTION_TO_TIER.keys()))
parser.add_argument("--exclude", default="",
help="Comma-separated criterion names to skip in the URL.")
parser.add_argument("--na", default="",
help="Comma-separated criterion names to mark as N/A "
"with the justification from cii_justifications.json.")
args = parser.parse_args()
project_id = args.project_id
section = args.section
exclude = {x.strip() for x in args.exclude.split(",") if x.strip()}
na_set = {x.strip() for x in args.na.split(",") if x.strip()}
justifications = load_justifications()
@@ -174,23 +181,38 @@ def main():
for n, c in sorted(na.items()):
print(f" - {n}: N/A | note: {c['justification'][:70]}")
# Build URL params, restricted to the section AND honoring --exclude.
# N/A criteria are never pre-filled as Met (they are excluded from `doable`).
# Build URL params, restricted to the section AND honoring --exclude /
# --na. N/A criteria (already excluded from `doable`) are never pre-filled
# as Met; --na criteria that ARE in `doable` are emitted as N/A instead.
params = []
for n in sorted(doable):
if exclude and n in exclude:
if n in exclude:
continue # leave at current Unmet/?/N/A in the form
if n in na_set:
continue # emitted as N/A below instead of Met
params.append((f"{n}_status", "Met"))
params.append((f"{n}_justification",
justifications.get(n, "TODO: justify")))
# N/A criteria: set status=N/A with a justification from the JSON, but only
# when they belong to the targeted section (so the params stay in scope).
for n in sorted(na_set):
if n in exclude:
continue
if n not in scoped:
continue
params.append((f"{n}_status", "N/A"))
params.append((f"{n}_justification",
justifications.get(n, "TODO: justify N/A")))
params.append(("overrides", "*,osps_*"))
edit_url = f"{BASE}/projects/{project_id}/{section}/edit?{urllib.parse.urlencode(params)}"
print(f"\n=== Edit URL ({section}"
+ (f", excluding {sorted(exclude)}" if exclude else "")
+ f") ===\n{edit_url}")
+ (f", excluding {sorted(exclude)}" if exclude else "")
+ (f", N/A {sorted(na_set)}" if na_set else "")
+ f") ===\n{edit_url}")
print(f"\nURL param count for _status/_justification pairs: "
f"{sum(1 for k,_ in params if k.endswith('_status'))}")
print("WARNING: pre-fills Unmet/? criteria as 'Met' with a placeholder justification.")
print("WARNING: pre-fills Unmet/? criteria as 'Met' (and --na as 'N/A') using "
"justifications from cii_justifications.json.")
print("Review EVERY field before Save; only mark Met what you genuinely satisfy.")
+11 -1
View File
@@ -32,5 +32,15 @@
"warnings_fixed": "Because Clippy uses `-D warnings`, any warning fails CI and must be fixed before a change can merge.",
"dynamic_analysis": "Dynamic analysis is performed via cargo-fuzz (libFuzzer) fuzz targets in fuzz/ that exercise the report/metrics parsers with arbitrary input.",
"dynamic_analysis_enable_assertions": "The cargo-fuzz build enables debug/overflow assertions: fuzz/Cargo.toml sets `[profile.release] debug-assertions = true` (and `[profile.dev] debug-assertions = true`), so debug_assert! checks and integer-overflow traps remain active while libFuzzer feeds arbitrary input; crashes that violate internal invariants are caught as bugs.",
"dynamic_analysis_fixed": "Fuzzing findings are triaged and fixed; crashes found by the harness are treated as bugs."
"dynamic_analysis_fixed": "Fuzzing findings are triaged and fixed; crashes found by the harness are treated as bugs.",
"delivery_unsigned": "Release artifacts are GPG-signed in CI (.github/workflows/release.yml) with a dedicated RSA-4096 project key; each GitHub Release attaches the macOS and Linux binaries, their detached ASCII-armored signatures (*.tar.gz.asc), and the public key at docs/signing-key.asc. Verification steps are documented in SECURITY.md.",
"vulnerability_report_process": "SECURITY.md defines the vulnerability reporting process: reports accepted via GitHub Security Advisories (preferred) or public GitHub Issues, what to include, expected acknowledgement/triage timelines, and a coordinated-disclosure policy.",
"know_secure_design": "SECURITY.md documents secure-development practices: input validation at all 89 tool boundaries, no hardcoded secrets, safe bounded parsing of untrusted MT5 HTML/XML reports, single-MT5-instance least-privilege, cargo-deny/cargo-audit dependency auditing in CI, parser fuzzing, and Wine/MT5 isolation.",
"know_common_errors": "SECURITY.md lists common implementation errors avoided: integer-overflow (checked arithmetic + fuzz overflow traps), path traversal in .set file handling, UTF-16LE encoding pitfalls, avoiding unsafe, and proper error propagation via Result/?.",
"crypto_password_storage": "No passwords are stored by the application; the only secrets are a user-provided MT5/Wine path config (gitignored) and an optional release PAT — neither is a password persisted by the project.",
"vulnerability_report_private": "No private vulnerability reporting channel is offered; reports are accepted via public GitHub Security Advisories/issues.",
"vulnerability_report_response": "No formal fixed-response SLA; handled case-by-case via the public reporting channel.",
"release_notes_vulns": "Release notes do not currently track security vulnerabilities separately."
}