feat(cve-json): emit affected[].packageURL for generated CVE records - #1197
Merged
Conversation
…ured The CVE Record Format defines an optional packageURL on each affected product entry — a Package URL identifying the package on its host. The generator carried collectionURL and packageName but never emitted one. Adds an optional product.purl_type config key. When set, every affected[] entry gains pkg:<type>/<packageName>; when unset the field is omitted, so existing adopters' records are unchanged. The purl never carries a version, per the schema — the entry's versions[] holds the range. A name needing namespace semantics (an npm scope, a Maven group ID) emits no purl rather than a guessed one. The new compute_purl() sits beside the pre-existing compute_package_url(), which despite its name returns a human-readable project-page URL for the attachment table, not a purl; both docstrings now say so. Generated-by: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01M7zttXzAM1wbTdBAjJCECv
Checked pypi and npm against the package-url spec's type definitions. pypi was right. npm was wrong twice: scoped names returned no purl at all, and names were lowercased. A scope is a namespace, not a guess — @angular/animation is pkg:npm/%40angular/animation. And npm names are case-sensitive: only new packages must be lowercase, pre-existing mixed-case ones were grandfathered in, so folding case points at a different package. Also drops golang, composer, hex, github and bitbucket from the lowercase set. Those were added from memory without reading their rules; a guessed normalisation corrupts an identifier silently, so unverified types now pass their name through untouched. Generated-by: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01M7zttXzAM1wbTdBAjJCECv
Maven, Go and Composer purls need a namespace the package name does not carry: a groupId is separate coordinate metadata, and Go's module/package split cannot be derived without consulting the module proxy. An optional product.purl_namespace supplies it, and without it those types still emit nothing rather than a guess pointing at another organisation's artifact. Types the spec gives no namespace (pypi, cargo, gem, nuget) ignore the key, and an npm scope in the package name wins over it. A namespace may itself be a path — Go's github.com/gorilla — so its slashes are kept as segment separators rather than percent-encoded. Also pins cargo, gem and nuget with tests. They already worked through the pass-through path; nuget in particular must keep its case, which the first cut of this feature would have folded. Generated-by: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01M7zttXzAM1wbTdBAjJCECv
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The CVE Record Format defines an optional
packageURLon each affected product entry — a Package URL identifying the package on its host.generate-cve-jsonalready emittedcollectionURLandpackageNamebut never a purl, so consumers had no machine-matchable package identifier.Two new optional config keys under
[product], both off by default — an adopter who sets neither gets byte-identical records to today:purl_type— the Package URL type (pypi,npm,maven, …). When set, everyaffected[]entry gainspkg:<type>/<name>.purl_namespace— the namespace for types that require one: a MavengroupId, a Composer vendor, a Go module prefix.Per the schema the purl never carries a version — the entry's
versions[]holds the range.Per-type rules come from the spec's own type definitions, not from memory. Reading them caught three things this PR would otherwise have got wrong:
github.com/gorillais two segments, not one with an encoded/.Supported today:
pypi(lowercase,_→-),npm(scope becomes the namespace,@percent-encoded, case preserved),maven,golang,composer(namespace-required), andcargo/gem/nuget(plain pass-through). Any other type passes its name through unchanged rather than applying a guessed normalisation.Where it emits nothing: a type that requires a namespace and has none, and a name needing namespace semantics the type's rule doesn't model. A guessed Maven groupId is a valid-looking identifier pointing at another organisation's artifact — worse than an absent optional field.
One naming hazard worth flagging for review: the pre-existing
compute_package_url()is not a purl — it returns a human-readable project-page URL for the attachment table. The new function iscompute_purl(), and both docstrings now say so. Renaming the old one may be worth a follow-up.24 new tests; 306 pass; ruff/mypy clean.
🤖 Generated with Claude Code
https://claude.ai/code/session_01M7zttXzAM1wbTdBAjJCECv