Skip to content

CSAF/VEX 5.9.2 catalogue and CI validation - #28

Merged
MarkAtwood merged 3 commits into
masterfrom
feat/advisory-csaf-5.9.2
Sep 18, 2026
Merged

MarkAtwood merged 3 commits into
masterfrom
feat/advisory-csaf-5.9.2

Conversation

@sameehj

@sameehj sameehj commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Commit the 32 wolfSSL 5.9.2 ChangeLog bullet CVEs (CNA records + VEX overlay) and the unsigned publish tooling (csaf-publish / csaf-verify).
  • Pin membership in advisories/releases/5.9.2.cves. Completeness counts only * [High] CVE-… bullets, so predecessor ids named in a paragraph are not members.
  • CI regenerates CSAF 2.0 + CycloneDX 1.6 VEX and runs Secvisogram mandatory tests, CycloneDX 1.6 strict schema, overlay JSON Schema, hash/self URL publish checks. Generated trees are gitignored.

Test plan

  • python3 -m unittest central/test_advisory_completeness.py central/test_csaf_publish.py central/test_gen_advisory.py
  • python3 central/advisory-completeness --cve-list advisories/releases/5.9.2.cves
  • GitHub Advisory catalogue workflow is green (generate 5.9.2, Secvisogram, CycloneDX, unsigned publish)
  • Confirm advisories/out/ and advisories/publish/ are not in the PR

@sameehj
sameehj requested a review from MarkAtwood September 16, 2026 14:55
Commit the CNA records and VEX overlay for the 5.9.2 bullet CVEs, plus
publish tooling. CI regenerates CSAF/VEX and runs Secvisogram plus
CycloneDX strict checks so an incomplete or non-conformant catalogue
cannot merge.
@sameehj
sameehj force-pushed the feat/advisory-csaf-5.9.2 branch from cbe75d8 to 3649396 Compare September 16, 2026 15:06

@MarkAtwood MarkAtwood left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving to merge as the foundation — the architecture is right and the interfaces are stable. The overlay schema is unchanged, and the pin-list format, record layout, and .well-known/csaf tree shape are unaffected by every follow-up below. Generated trees are gitignored, so regenerating after any fix is free. Nothing here publishes, so no bad data escapes by merging.

Two things to settle before this catalogue gets treated as source of truth:

1. CVE-2026-6679 and CVE-2026-6681 look like 5.9.1 fixes, not 5.9.2. Both are pinned in advisories/releases/5.9.2.cves, but every field says 5.9.1: record lessThanOrEqual: 5.9.0 (the other 30 say 5.9.1), overlay fixed_versions: ["5.9.1"] (other 30 say ["5.9.2"]), remediation "Update to wolfSSL 5.9.1 or later", and the detail prose says "fixed in the 5.9.1 release". Both cite PR 10116. They match the signature of the two pre-existing 5.9.1 records (CVE-2026-5501, CVE-2026-5778), which are correctly absent from the pin list. CVE-2026-6679 is High (8.8). Either drop them from the pin list or correct their version metadata — as-is the 5.9.2 advisory claims credit for fixes 5.9.1 shipped.

2. Worth confirming the pin list is complete. _BULLET_CVE_RE (central/advisory-completeness:83-86) captures one id per bullet, so * [High] CVE-A and CVE-B and * [Medium] CVE-A, CVE-B both silently lose the second id — and cves_fixed_in_release only ever adds to found, with the only guard firing when every bullet misses. A partial miss is indistinguishable from a complete release and exits 0. Since 5.9.2.cves was transcribed with this same rule, a loose CVE-[0-9]{4}-[0-9]{4,} count over the real ChangeLog's Vulnerabilities section, diffed against the 32 pinned ids, would settle it. Suggested fix for the tool: compute both the strict and loose sets and fail loudly naming any id the strict rule dropped.

Notable follow-ups, none blocking:

  • No CVSS reaches the output. All 34 records are v4.0 only; gen-advisory:608-610 emits scores[] only for v2/v3 because the CSAF 2.0 schema predates v4, so _best_cvss_csaf20 returns None every time. Consumers get advisories with no severity data, and mandatory tests 6.1.8-6.1.11 are vacuous. The CVSS data itself is the cleanest part of this PR — I recomputed all 34 vectors independently and every baseScore/baseSeverity matches — so it's a shame none of it ships.
  • csaf-verify's signature check is circular. It loads the trust anchor from the tree under test (:39) with no fingerprint pin and never reads provider-metadata.json. Someone with write access to the web root can swap the key, re-sign, regenerate sidecars, and get OK on everything. It also globs only what's on disk and ignores index.txt, so deleting a published advisory is invisible — I got 1/1 documents verified, exit 0, with index.txt still listing two paths.
  • Neither csaf-verify nor csaf-keygen is ever executed — both workflows only py_compile them, which doesn't resolve imports, so the top-level import pgpy (undeclared in any manifest) is invisible to CI. Pointed at the default unsigned publish tree, csaf-verify dies with an uncaught FileNotFoundError. Given that, consider dropping both from this tree until key custody is settled (scoping doc W2) — a verifier that exists, is never run, and doesn't actually verify is worse than none, because the next person will assume it works. Costs nothing to reintroduce.
  • csaf-keygen:49 writes the secret key world-readable (0664 under umask 0002) and unencrypted — no chmod, no opener=, so the permissive bits exist before the key bytes land. Fine for a PoC key, must be fixed before real key material.
  • Validator deps aren't pinned. .gitignore:25 ignores package-lock.json and advisory.yml:65 uses npm install --no-save, so 19 packages resolve fresh per run with lifecycle scripts enabled. And cyclonedx-bom==7.* pins a CLI that's never invoked — the schema comes from cyclonedx-python-lib, floating >=8.0,<12. Both contradict csaf_validate.mjs:10.
  • The validator self-test can't detect losing the mandatory tests. Its fixture already fails strict schema, so the assertion passes either way — stubbing gateTests down to schema-only leaves all four assertions printing ok. A schema-valid but mandatory-test-violating fixture would fix it.
  • csaf-publish ignores SOURCE_DATE_EPOCH (:142) though gen-advisory:188-194 honors it and docs/PLAN.md names it the reproducibility mechanism; provider-metadata.json is also never hashed or signed.
  • Smaller: --release is silently ignored when --cve-list is given (--release 9.9.9 against the 5.9.2 list prints wolfSSL 9.9.9, 32/32, exit 0, and that label feeds the --json consumer); CVE-2026-6681's CWE-120 name is missing ('Classic Buffer Overflow'); tracking ids differing only in case collide after canonicalization and one document is silently dropped; csaf-publish:18 says signing is delegated to gpg but _sign() uses pgpy; no permissions: block on either workflow.

One structural note: the bundle-membership check at advisory.yml:99-119 is near-tautological — pin and the copied records both derive from 5.9.2.cves, so it catches gen-advisory dropping a CVE but never a wrong pin. That's why item 1 above passed green CI.

What holds up well: CI genuinely fails closed (mutating a doc to violate 6.1.1 and 6.1.14/15 each exits 1), no continue-on-error or || true, pull_request rather than pull_request_target. The overlay validates clean against the schema, coverage matches in both directions, canonical_filename isn't path-traversable, and the record data is otherwise solid — 34 distinct titles and descriptions, no placeholders, dates consistent throughout. test_csaf_publish.py is a proper test: it recomputes digests independently rather than round-tripping through the tool.

sameehj added a commit that referenced this pull request Sep 17, 2026
CVE-2026-6679 and CVE-2026-6681 shipped in 5.9.1. Completeness now
fails on ids the strict bullet rule drops, and publish/verify/CI
close the rest of the review on #28.

Co-authored-by: Cursor <cursoragent@cursor.com>
CVE-2026-6679 and CVE-2026-6681 shipped in 5.9.1. Completeness now
fails on ids the strict bullet rule drops, and publish/verify/CI
close the rest of the review on #28.
CI loops advisories/releases/<version>/ instead of naming 5.9.2.
5.9.1 is a full pin (ChangeLog bullets plus late disclosures 6679/6681)
so a second release proves the same path works for any version.
@sameehj
sameehj force-pushed the feat/advisory-csaf-5.9.2 branch from bbd5fb7 to 09c8c67 Compare September 17, 2026 16:31
@sameehj

sameehj commented Sep 17, 2026

Copy link
Copy Markdown
Contributor Author

@MarkAtwood

Thanks for the review. Both source-of-truth items and the follow-ups are in this branch. Generated trees stay gitignored; still nothing publishes.

CVE-2026-6679 and CVE-2026-6681 are out of the 5.9.2 pin and out of the wolfssl-5.9.2 bundle. Records and overlay stay as 5.9.1 (fixed_versions: ["5.9.1"], PR 10116). They live in advisories/releases/5.9.1/ (supplemental — disclosed on the 5.9.2 ChangeLog, shipped in 5.9.1) and in advisories/releases/5.9.2/prior-release.cves. Completeness now requires overlay fixed_versions to contain the release, so this class of pin error fails CI.

Completeness computes strict (bullet heads) and loose (every CVE in the Vulnerabilities section). A drop is a hard fail unless it is listed in mentions.cves. 5.9.2 mentions is CVE-2026-5460 (body of 7531). CI reconciles each pin against a frozen ChangeLog in that release directory, not against a copy of the pin.

Follow-ups:

CSAF 2.0 still cannot put v4 in scores[]. CycloneDX 1.6 carries the v4 rating; CI fails if any pinned vuln lacks one. CSAF keeps the CVSS v4 note.
csaf-verify walks index.txt (missing indexed file or extra on-disk doc fails). Unsigned default is hashes, including provider-metadata.json. Signatures require --fingerprint matching metadata; the key is loaded from that entry, not a loose file in the tree. CI runs verify on the unsigned tree.
pgpy is lazy. Keygen stays PoC-only (W2). Secret file is created mode 0600 before bytes land.
Committed package-lock.json, npm ci --ignore-scripts, pinned cyclonedx-python-lib (no unused cyclonedx-bom CLI).
Validator self-test uses a schema-valid, 6.1.6-failing fixture.
csaf-publish honors SOURCE_DATE_EPOCH, hashes provider-metadata, fails on canonical-name collisions. Docstring says pgpy. --release must match the pin path. CWE-120 name on 6681 includes ('Classic Buffer Overflow'). Both workflows have permissions: contents: read.
Also: CI no longer names 5.9.2. It loops advisories/releases//. 5.9.1 is a second full catalogue on that path (24 members including the two late disclosures). Next release is a new directory, not a workflow edit.

@MarkAtwood
MarkAtwood merged commit 9223bb0 into master Sep 18, 2026
3 checks passed
MarkAtwood pushed a commit that referenced this pull request Sep 18, 2026
CVE-2026-6679 and CVE-2026-6681 shipped in 5.9.1. Completeness now
fails on ids the strict bullet rule drops, and publish/verify/CI
close the rest of the review on #28.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants