feat(release): CycloneDX SBOM from lockfiles (#953 increment 2) - #1063
feat(release): CycloneDX SBOM from lockfiles (#953 increment 2)#1063seonghobae wants to merge 1 commit into
Conversation
app.release.sbom — pure text/JSON parsing of the lockfiles the repo already commits into a CycloneDX 1.6 bom. No pip/npm run, no dependency resolution, no network. - parse_pip_lock(text): name==version requirements + trailing --hash=sha256 values; skips comment / blank / option lines. - parse_npm_lock(obj): walks package-lock.json v2/v3 "packages"; skips the "" root and version-less workspace links; keeps @scope/; maps integrity (sha512-<b64>) to a hash entry. Non-dict -> ValueError. - build_sbom(...): merges both, dedupes by purl, sorts by (type, name, version), wraps in the CycloneDX envelope with an application metadata component. Blank metadata -> ValueError naming the field. 16 tests over small literal fixtures (never the real lockfiles). mypy clean, interrogate 100%. Cites OWASP CycloneDX 1.6 and NTIA (2021) SBOM minimum elements; doctoring note updated. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013SeQS8tSee5QVeyGpJ9SaY
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Team Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| algo, _, content = integrity.partition("-") | ||
| alg_map = {"sha512": "SHA-512", "sha384": "SHA-384", "sha256": "SHA-256"} | ||
| if algo not in alg_map or not content: | ||
| return [] | ||
| return [{"alg": alg_map[algo], "content": content}] |
There was a problem hiding this comment.
🔴 npm hashes use the wrong encoding
For every standard npm integrity value, _npm_hashes copies Base64 into a CycloneDX field that requires hexadecimal. Schema validation rejects the generated SBOM.
Prompt for agents
Update backend/app/release/sbom.py so _npm_hashes parses npm Subresource Integrity digests and emits the decoded digest as hexadecimal, as required by CycloneDX. Support the declared SHA-256, SHA-384, and SHA-512 algorithms, reject malformed Base64 safely, and add tests using real integrity values that assert the expected hex output and schema-compatible lengths.
Was this helpful? React with 👍 or 👎 to provide feedback.
| "type": "library", | ||
| "name": name, | ||
| "version": version, | ||
| "purl": f"pkg:npm/{name}@{version}", |
There was a problem hiding this comment.
🟡 Scoped npm packages get invalid identifiers
For scoped dependencies, parse_npm_lock leaves the leading @ unescaped instead of %40. Package URL consumers can reject or misidentify those components.
Prompt for agents
Generate npm purls in backend/app/release/sbom.py according to the Package URL npm rules rather than interpolating package names directly. At minimum, percent-encode the leading @ in scoped package namespaces as %40 while preserving the scope/name separator. Prefer a standards-aware purl builder or equivalent encoding that also handles reserved characters in names and versions. Add tests for scoped package purls.
Was this helpful? React with 👍 or 👎 to provide feedback.
| def test_build_sbom_round_trips_through_json() -> None: | ||
| """The whole document is JSON-serializable.""" | ||
|
|
||
| bom = build_sbom( | ||
| pip_lock=_PIP_LOCK, | ||
| npm_lock=_NPM_LOCK, | ||
| component_name="app", | ||
| component_version="1.0.0", | ||
| generated_at="t", | ||
| ) | ||
| assert json.loads(json.dumps(bom)) == bom |
There was a problem hiding this comment.
| References (APA 7th): | ||
|
|
||
| * OWASP Foundation. (2024). *CycloneDX specification 1.6*. | ||
| https://cyclonedx.org/docs/1.6/ | ||
| * National Telecommunications and Information Administration. (2021). *The | ||
| minimum elements for a software bill of materials (SBOM)*. U.S. Department | ||
| of Commerce. | ||
| https://www.ntia.gov/report/2021/minimum-elements-software-bill-materials-sbom |
There was a problem hiding this comment.
Gate still frozen (main@8dc74692; .github#1531 queue ~2100 and climbing every tick). iter40 gap-baseline consolidation: record PR #1063 (pure lockfile -> CycloneDX 1.6 parser) in the #953 "This loop's increment PRs" list. Stacked-PR count 18 -> 19; #953 merge-wave chain extended to #1057 -> #1063; #953 "Remaining increments" reworded (SBOM generator landed, SBOM signing + manifest linkage + SLSA provenance remain). CHANGELOG [Docs] bullet count 18 -> 19. MD018-clean. Docs-only. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013SeQS8tSee5QVeyGpJ9SaY
What
Second #953 increment, stacked on the manifest assembler (PR #1057). Adds
app.release.sbom— pure text/JSON parsing of the lockfiles the repoalready commits into a CycloneDX 1.6
bom. Nopip/npmrun, nodependency resolution, no network: whatever a lockfile pins is exactly what
the SBOM reports.
parse_pip_lock(text)—name==versionrequirements + trailing--hash=sha256:<hex>values (same line or\-continued); skips blank /#/ option lines.parse_npm_lock(obj)— walks a parsedpackage-lock.jsonv2/v3packagesmap; skips the""root and version-less workspace links;derives the name from the last
node_modules/segment (@scope/kept);maps
integrity(sha512-<b64>) to a hash entry. Non-dict →ValueError.build_sbom(*, pip_lock, npm_lock, component_name, component_version, generated_at)— merges both, de-dupes bypurl, sorts by(type, name, version), wraps in the CycloneDX envelope with anapplicationmetadata component. Blank envelope metadata →ValueErrornaming the first bad field.
Why
An SBOM is a routine enterprise-procurement requirement. Generating it from
the committed lockfiles keeps it exact and reproducible. Deferred: signing
the SBOM, referencing it from the release manifest by digest, SPDX
rendering, VEX.
Tests / checks
pip happy path + hash collection + skip rules; npm happy path + root skip
build_sbomshape + spec version + sort + dedupe + JSON round-trip + blank-metadata
rejection.
PYTHONPATH=. mypy appclean (71 files);interrogate100%;tests/test_docstrings.pygreen.Standards (APA 7th)
Stack
Branched off
feat/release-manifest-assembler-20260902(PR #1057). Blockedfrom merge by the org CI incident
ContextualWisdomLab/.github#1531like therest of the loop's stack.
🤖 Generated with Claude Code