Skip to content

fix(parser): recover licenses from CycloneDX expressions and SPDX declared field#32

Open
dmchaledev wants to merge 1 commit into
mainfrom
claude/magical-ptolemy-xhswx7
Open

fix(parser): recover licenses from CycloneDX expressions and SPDX declared field#32
dmchaledev wants to merge 1 commit into
mainfrom
claude/magical-ptolemy-xhswx7

Conversation

@dmchaledev

Copy link
Copy Markdown
Contributor

Summary

Two license data-loss bugs in the public parse() API. Both silently drop a component's license, returning undefined (or a useless sentinel) even though the source SBOM carries the real value. This is squarely in the supply-chain-security remit and directly feeds the license-compliance use case the README advertises (and the proposed license-diff work in #9).

Both fixes are confined to src/parser.ts (license extraction only) — untouched by any open PR, so no conflict with the in-flight diff.ts / reporter.ts / cli.ts work.

Bug 1 — CycloneDX license expression is dropped

CycloneDX allows a licenses entry to be an SPDX license expression instead of a license object, e.g.:

{ "name": "dual", "version": "1.0.0", "licenses": [{ "expression": "MIT OR Apache-2.0" }] }

extractCycloneDXLicense() only read license.id / license.name, so any dual- or expression-licensed component yielded license: undefined. Expression licensing is common, so this is real signal loss.

Bug 2 — SPDX NOASSERTION stored verbatim, licenseDeclared ignored

Many generators leave licenseConcluded as the SPDX sentinel "NOASSERTION" while the real license sits in licenseDeclared:

{ "name": "foo", "versionInfo": "1.0.0", "licenseConcluded": "NOASSERTION", "licenseDeclared": "BSD-3-Clause" }

The old code stored the string "NOASSERTION" verbatim and never looked at licenseDeclared, so the known BSD-3-Clause license was lost. The fix prefers a concrete concluded license, falls back to the declared license, and drops the NOASSERTION sentinel (leaving license undefined only when neither field is meaningful).

Before / after

                         before                       after
CycloneDX expression     undefined                    "MIT OR Apache-2.0"
SPDX NOASSERTION+declared "NOASSERTION"                "BSD-3-Clause"

Changes

  • src/parser.ts: extractCycloneDXLicense() now falls back to licenses[].expression; new extractSPDXLicense() helper handles the licenseConcludedlicenseDeclared fallback and the NOASSERTION sentinel.
  • src/__tests__/parser.test.ts: adds coverage for the CycloneDX expression path and the SPDX declared-license fallback (incl. the both-NOASSERTION → undefined case).

Testing

  • npx tsc --noEmit — clean
  • npm run lint — clean
  • npm test — 33 passed (4 new)

Backward compatible: only previously-undefined/sentinel license values change; components that already resolved a license are unaffected.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Qqw3TvZsp8exiuHLLLkxTA


Generated by Claude Code

…lared field

Two license data-loss bugs in the public parse() API:

- CycloneDX components licensed via an SPDX license *expression*
  (e.g. `{ "expression": "MIT OR Apache-2.0" }`) — a spec-valid and common
  shape for dual/expression-licensed packages — yielded `license: undefined`
  because only `license.id` / `license.name` were read.

- SPDX packages whose `licenseConcluded` is the sentinel "NOASSERTION" stored
  that meaningless string verbatim and ignored `licenseDeclared`, discarding
  the real license. Now prefer a concrete concluded license, fall back to the
  declared license, and drop the NOASSERTION sentinel.

Both feed the license/compliance use case the README advertises. Adds tests
for the expression path and the SPDX declared-license fallback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qqw3TvZsp8exiuHLLLkxTA
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