Skip to content

fix: review findings — 3 Highs + advisory test coverage + CDX VEX per-product component - #4

Closed
MarkAtwood wants to merge 7 commits into
masterfrom
fix/review-highs
Closed

fix: review findings — 3 Highs + advisory test coverage + CDX VEX per-product component#4
MarkAtwood wants to merge 7 commits into
masterfrom
fix/review-highs

Conversation

@MarkAtwood

Copy link
Copy Markdown

Fixes the three High-severity findings from the 2026-07-23 internal review of wolfGlass. One commit per issue.

Changes

Verification

  • Path traversal: cveId "../ESCAPED" now exits 1 and writes nothing outside --out-dir; a real CVE-2026-12345 still emits both docs.
  • Workflow: valid YAML.
  • sbom.am: SBOM_GEN resolves to the vendored copy, falls back to WOLFSSL_DIR/scripts/gen-sbom, and is empty (→ clear recipe error) when neither exists.
  • Existing suite: 162 tests pass (3 skipped), test_sbom.py green.

Note: the fix(advisory) change has no in-repo regression test because test_gen_advisory.py was dropped during consolidation (tracked internally as SBOM-gpex.14); verified manually here.

cveId comes verbatim from remotely-fetched CVE records and was
interpolated into output filenames (<id>.csaf.json / <id>.cdx.json)
and the CSAF self URL without validation -- an untrusted-input ->
arbitrary-file-write vector (e.g. cveId "../ESCAPED" escaped --out-dir).

Constrain cveId to ^CVE-[0-9]{4}-[0-9]{4,}$ and --advisory-id to a
path-safe grammar before either is used to build a path.

Fixes #1
Caller-controlled ${{ inputs.* }} values were interpolated directly
into run: blocks; the runner substitutes them before the shell parses
the script, so shell metacharacters execute as code. As a reusable
workflow shipped to every product repo this distributes the footgun.

Bind each input to env: and reference "$VAR" in the script (env values
are not re-parsed by ${{ }}), add permissions: contents: read, and make
the host-path-leak grep distinguish exit 2 (no-file glob) from 1 (clean).

Fixes #2
$(dir $(lastword $(MAKEFILE_LIST))) cannot locate this fragment:
Automake's include is textual, so at make time MAKEFILE_LIST is the top
Makefile and SBOM_GEN resolved to <builddir>/gen-sbom, breaking
`make sbom` unless every product overrode SBOM_GEN.

Name the vendored directory explicitly (SBOM_VENDOR_DIR, default
$(srcdir)/tools/sbom) and restore the WOLFSSL_DIR fallback for the
wolfSSH-style route. Empty when neither exists so the recipe's
`test -f "$(SBOM_GEN)"` fails with a clear error.

Fixes #3
Copilot AI review requested due to automatic review settings July 23, 2026 18:27

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses three high-severity internal review findings across the advisory generator, the reusable SBOM GitHub Actions workflow, and the autotools SBOM fragment—tightening input/path safety, hardening workflow execution, and fixing gen-sbom discovery under Automake.

Changes:

  • Constrain CVE/advisory identifiers to safe grammars before using them in output filenames/URLs in central/gen-advisory.
  • Harden .github/workflows/sbom-reusable.yml by binding workflow_call inputs through env: and improving the host-path-leak scan behavior.
  • Fix autotools gen-sbom discovery in share/sbom.am by switching from a MAKEFILE_LIST heuristic to an explicit vendored directory + WOLFSSL_DIR fallback.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
share/sbom.am Reworks autotools gen-sbom discovery to be reliable under Automake include, with WOLFSSL_DIR fallback.
central/gen-advisory Validates untrusted CVE/advisory identifiers before interpolating into output paths/URLs.
.github/workflows/sbom-reusable.yml Reduces script-injection risk by moving inputs into env: and hardens the host-path leak scan logic.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread central/gen-advisory
Comment on lines +57 to +62
def _validate_path_id(value, kind, pattern):
"""Reject an id that is unsafe to interpolate into an output path."""
if not pattern.match(value):
sys.exit(f"ERROR: refusing unsafe {kind} {value!r}: must match "
f"{pattern.pattern} (no path separators)")
return value
Comment on lines +48 to +49
permissions:
contents: read
Comment on lines +100 to +102
set +e
out="$(grep -REn '"/(home|Users|root)/' $OUTPUTS)"
rc=$?
The consolidation dropped the live advisory data that gen-advisory reads
by default (advisories/records/*.json + advisories/vex-overlay.json), so
a bare `gen-advisory` / `make advisory` had no input. Vendor it from the
wolfSSL source of truth. Also the fixture the ported test's canonical-tree
case needs.
Port the 720-line stdlib gen-advisory gate (dropped in consolidation)
into central/ beside gen-advisory, with its frozen testdata/ fixtures,
and wire it into selftest.yml. Adds a TestPathIdValidation class that
locks in the cveId/--advisory-id path-traversal fix: a record with
cveId "../ESCAPED" or "/etc/pwned", or --advisory-id "../evil", is
rejected and writes nothing outside --out-dir; a well-formed CVE id
still generates.

66 tests (62 ported + 4 regression) pass.
@MarkAtwood

Copy link
Copy Markdown
Author

Added test coverage + the advisory data the test needs (2 more commits):

  • feat(advisory): vendored the canonical advisories/ tree (records + vex-overlay.json) that consolidation had dropped, so gen-advisory/make advisory has default input again.
  • test(advisory): ported the 720-line stdlib test_gen_advisory.py gate into central/ with its testdata/ fixtures, wired into selftest.yml, and added a TestPathIdValidation class that locks in the Path traversal via unvalidated cveId in gen-advisory (arbitrary file write) #1 path-traversal fix (../ESCAPED, /etc/pwned, --advisory-id ../evil all rejected; well-formed CVE still generates).

66 tests pass (62 ported + 4 new regression). This also closes the two consolidation gaps tracked internally as SBOM-gpex.14 (dropped test) and SBOM-gpex.15 (dropped overlay data).

The CycloneDX VEX emitter collapsed every non-FIPS product onto the
hardcoded wolfssl metadata.component, so a wolfSSH (or wolfMQTT, wolfTPM,
...) CVE emitted a VEX whose affects[] pointed at a component named
"wolfssl" -- disagreeing with the CSAF output for the same record and
mis-attributing the vulnerability.

Mint a per-product component keyed on cdx_key (as the FIPS path already
does) with the product's own name/cpe/purl, and reference it in affects[];
wolfssl products still use the umbrella metadata.component. Adds a
regression test class covering a wolfSSH record.
@MarkAtwood MarkAtwood changed the title fix: three High-severity review findings (path traversal, CI injection, autotools discovery) fix: review findings — 3 Highs + advisory test coverage + CDX VEX per-product component Jul 23, 2026
@MarkAtwood

Copy link
Copy Markdown
Author

Added a Medium fix in the same advisory subsystem (its regression test depends on the test_gen_advisory.py ported here):

  • fix(advisory): per-product component in CDX VEX (SBOM-gpex.4). The CycloneDX VEX collapsed every non-FIPS product onto the hardcoded wolfssl metadata.component, so a wolfSSH/wolfMQTT/wolfTPM CVE emitted a VEX attributing the bug to wolfssl (disagreeing with the CSAF output). Now mints a per-product component keyed on cdx_key with the product's own name/cpe/purl; wolfssl products keep the umbrella component. Regression test TestCdxVexNonWolfsslProduct added.

69 advisory tests pass (66 + 3 new).

load_overlay only json.load'd the overlay, so a hand-edited entry the
schema forbids passed straight through -- above all a not_affected
determination with no justification, which then degraded to a default
CSAF flag and an omitted CycloneDX analysis.justification (the two VEX
outputs silently disagreeing).

Add a stdlib validator enforcing the overlay schema's structural
invariants at load (required state, not_affected => justification for
both the entry and its fips sub-object, enum membership, CVE-id keys,
no unknown keys). The authoritative jsonschema pass still runs in CI;
a drift test ties the validator's vocabulary to the schema file.

77 advisory tests pass (69 + 8 new).
@MarkAtwood

Copy link
Copy Markdown
Author

Another Medium in the advisory subsystem:

  • fix(advisory): validate the VEX overlay at load time (SBOM-gpex.5). load_overlay previously only json.load'd the file, so a hand-edited entry the schema forbids passed through — most dangerously a not_affected determination with no justification, which silently degraded to a default CSAF flag + omitted CycloneDX analysis.justification. Added a stdlib validator enforcing the schema's structural invariants (required state; not_affectedjustification for the entry and its fips sub-object; enum membership; CVE-id keys; no unknown keys). A drift test ties the validator's vocabulary to the schema file so the two can't diverge.

77 advisory tests pass (69 + 8 new).

@MarkAtwood

Copy link
Copy Markdown
Author

@MarkAtwood MarkAtwood closed this Jul 23, 2026
@MarkAtwood
MarkAtwood deleted the fix/review-highs branch July 23, 2026 22:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants