diff --git a/src/core/validate-graph.ts b/src/core/validate-graph.ts index 959d1a0..cedaf1c 100644 --- a/src/core/validate-graph.ts +++ b/src/core/validate-graph.ts @@ -60,6 +60,27 @@ export function validatePackageURL(pkg: types.PkgInfo): void { ); break; + // The PURL spec for Linux distros does not include the source in the name. + // This is why we relax the assertion here and match only on the package name: + // / - we omit the source name + // For now, make this exception only for deb to cover a support case. + case 'deb': { + const pkgName = pkg.name.split('/').pop(); + assert( + pkgName === purlPkg.name, + 'name and packageURL name do not match', + ); + if (purlPkg.qualifiers?.['upstream'] && pkg.name.includes('/')) { + const pkgSrc = pkg.name.split('/')[0]; + const pkgUpstream = purlPkg.qualifiers['upstream'].split('@')[0]; + assert( + pkgSrc === pkgUpstream, + 'source and packageURL source do not match', + ); + } + break; + } + default: assert( pkg.name === purlPkg.name, diff --git a/test/core/validate-graph.test.ts b/test/core/validate-graph.test.ts new file mode 100644 index 0000000..d7b5c1c --- /dev/null +++ b/test/core/validate-graph.test.ts @@ -0,0 +1,90 @@ +import { validatePackageURL } from '../../src/core/validate-graph'; + +describe('validatePackageURL', () => { + describe('deb package type tests', () => { + it.each([ + [ + 'package name includes source', + { + name: 'foo/bar', + version: '1.2.3', + purl: 'pkg:deb/bar@1.2.3', + }, + ], + [ + 'purl is namespaced (includes a vendor)', + { + name: 'foo/bar', + version: '1.2.3', + purl: 'pkg:deb/debian/bar@1.2.3', + }, + ], + [ + 'package name does not include source', + { + name: 'bar', + version: '1.2.3', + purl: 'pkg:deb/bar@1.2.3', + }, + ], + [ + 'matches on upstream where only the source name is provided', + { + name: 'foo/bar', + version: '1.2.3', + purl: 'pkg:deb/bar@1.2.3?upstream=foo', + }, + ], + [ + 'matches on upstream where full upstream is provided', + { + name: 'foo/bar', + version: '1.2.3', + purl: 'pkg:deb/bar@1.2.3?upstream=foo%401.2.3', + }, + ], + [ + 'matches on package name where source is unavailable', + { + name: 'bar', + version: '1.2.3', + purl: 'pkg:deb/bar@1.2.3?upstream=foo%401.2.3', + }, + ], + ])( + 'matches only on package name for debian purls: %s', + (_testCaseName, pkg) => { + expect(() => validatePackageURL(pkg)).not.toThrow(); + }, + ); + + it.each([ + [ + 'package name does not match purl name', + { + name: 'foo/bar', + version: '1.2.3', + purl: 'pkg:deb/baz@1.2.3', + }, + ], + [ + 'package source does not match purl source', + { + name: 'foo/bar', + version: '1.2.3', + purl: 'pkg:deb/bar@1.2.3?upstream=baz', + }, + ], + [ + 'purl includes source name', + { + name: 'foo/bar', + version: '1.2.3', + purl: 'pkg:deb/debian/foo%2Fbar@1.2.3', + }, + ], + ])('should throw on invalid purl: %s', (_testCaseName, pkg) => { + expect(() => validatePackageURL(pkg)).toThrow(); + }); + }); +});