Skip to content

feat(release): CycloneDX SBOM from lockfiles (#953 increment 2) - #1063

Open
seonghobae wants to merge 1 commit into
feat/release-manifest-assembler-20260902from
feat/sbom-from-lockfiles-20260902
Open

feat(release): CycloneDX SBOM from lockfiles (#953 increment 2)#1063
seonghobae wants to merge 1 commit into
feat/release-manifest-assembler-20260902from
feat/sbom-from-lockfiles-20260902

Conversation

@seonghobae

@seonghobae seonghobae commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

What

Second #953 increment, stacked on the manifest assembler (PR #1057). Adds
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: whatever a lockfile pins is exactly what
the SBOM reports.

  • parse_pip_lock(text)name==version requirements + trailing
    --hash=sha256:<hex> values (same line or \-continued); skips blank /
    # / option lines.
  • parse_npm_lock(obj) — walks a parsed package-lock.json v2/v3
    packages map; 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 by purl, sorts by
    (type, name, version), wraps in the CycloneDX envelope with an
    application metadata component. Blank envelope metadata → ValueError
    naming 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

  • 16 tests over small literal lockfile fixtures (never the real repo files):
    pip happy path + hash collection + skip rules; npm happy path + root skip
    • version-less skip + scoped/nested names + non-dict rejection; build_sbom
      shape + spec version + sort + dedupe + JSON round-trip + blank-metadata
      rejection.
  • PYTHONPATH=. mypy app clean (71 files); interrogate 100%;
    tests/test_docstrings.py green.

Standards (APA 7th)

Stack

Branched off feat/release-manifest-assembler-20260902 (PR #1057). Blocked
from merge by the org CI incident ContextualWisdomLab/.github#1531 like the
rest of the loop's stack.

🤖 Generated with Claude Code


Devin Review

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
@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 1c8d5991-f8b7-4bf8-8f6e-22cf5f1bc4e9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@devin-ai-integration devin-ai-integration Bot 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.

Devin Review found 4 potential issues.

Devin Review

Comment on lines +125 to +129
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}]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 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.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

"type": "library",
"name": name,
"version": version,
"purl": f"pkg:npm/{name}@{version}",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 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.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +158 to +168
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Schema compatibility remains untested

Synthetic fixtures and JSON round-tripping cannot detect invalid CycloneDX values. Add real-lockfile coverage and CycloneDX 1.6 schema validation to the test suite.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +25 to +32
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Research artifact requirement is unmet

The repository requires substantive feature PRs to attach redistributable papers or explain citation-only treatment. This change provides links without either follow-up.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

seonghobae added a commit that referenced this pull request Sep 2, 2026
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
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.

1 participant