mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 05:29:44 +02:00
ci: Drop exports-subpath stubs from image SBOMs (no-changelog) (#37488)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f86e11f1dc
commit
f7b507ca65
9
.github/workflows/docker-build-push.yml
vendored
9
.github/workflows/docker-build-push.yml
vendored
|
|
@ -706,8 +706,11 @@ jobs:
|
|||
notify-on-failure:
|
||||
name: Notify Cats on nightly build failure
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-and-push-docker]
|
||||
if: always() && needs.build-and-push-docker.result == 'failure' && github.event_name == 'schedule'
|
||||
needs: [build-and-push-docker, sbom-attestation]
|
||||
if: |
|
||||
always() && github.event_name == 'schedule' &&
|
||||
(needs.build-and-push-docker.result == 'failure' ||
|
||||
needs.sbom-attestation.result == 'failure')
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
|
|
@ -719,4 +722,4 @@ jobs:
|
|||
run: |
|
||||
node .github/scripts/slack/notify.mjs \
|
||||
--channel '#team-catalysts' \
|
||||
--text 'Nightly Docker build failed - ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'
|
||||
--text 'Nightly Docker build failed (build=${{ needs.build-and-push-docker.result }}, sbom=${{ needs.sbom-attestation.result }}) - ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'
|
||||
|
|
|
|||
|
|
@ -75,6 +75,16 @@ function srcFileOf(component) {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* An npm name is `name` or `@scope/name` — neither part may contain a slash. A
|
||||
* further slash means the scanner named a directory inside a package, i.e. an
|
||||
* `exports` subpath (@google/genai/node, @linear/sdk/webhooks).
|
||||
*/
|
||||
function isExportsSubpath(component) {
|
||||
const qualified = qualifiedName(component) ?? '';
|
||||
return qualified.replace(/^@[^/]+\//, '').includes('/');
|
||||
}
|
||||
|
||||
/**
|
||||
* A scan of a container image filesystem (unlike the pnpm-lockfile scan of the
|
||||
* release closure) deep-walks node_modules and mis-catalogs nested package.json
|
||||
|
|
@ -91,6 +101,11 @@ export function isPhantomNpm(component) {
|
|||
const purl = component.purl ?? '';
|
||||
if (!purl.startsWith('pkg:npm/')) return false;
|
||||
|
||||
// Checked before the path rules: a subpath stub lives at
|
||||
// node_modules/@google/genai/node/package.json, which *does* end with its own
|
||||
// qualified name, so the canonical-path check below would call it real.
|
||||
if (isExportsSubpath(component)) return true;
|
||||
|
||||
const src = srcFileOf(component);
|
||||
// syft writes "UNKNOWN" where cdxgen omits the field. Neither is a real
|
||||
// version. cdxgen emits `exports`-subpath phantoms with no path either, so
|
||||
|
|
|
|||
|
|
@ -204,6 +204,42 @@ describe('isPhantomNpm (cdxgen image-scan noise)', () => {
|
|||
|
||||
const syftSrc = (p) => ({ properties: [{ name: 'syft:location:0:path', value: p }] });
|
||||
|
||||
it('flags an exports subpath whose syft path matches its own qualified name', () => {
|
||||
assert.equal(
|
||||
isPhantomNpm({
|
||||
name: 'genai/node',
|
||||
group: '@google',
|
||||
version: 'UNKNOWN',
|
||||
purl: 'pkg:npm/%40google/genai%2Fnode',
|
||||
...syftSrc('/usr/local/lib/node_modules/n8n/node_modules/@google/genai/node/package.json'),
|
||||
}),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
isPhantomNpm({
|
||||
name: 'sdk/webhooks',
|
||||
group: '@linear',
|
||||
version: 'UNKNOWN',
|
||||
purl: 'pkg:npm/%40linear/sdk%2Fwebhooks',
|
||||
...syftSrc('/usr/local/lib/node_modules/n8n/node_modules/@linear/sdk/webhooks/package.json'),
|
||||
}),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps an unscoped package whose name is a prefix of a subpath', () => {
|
||||
assert.equal(
|
||||
isPhantomNpm({
|
||||
name: 'genai',
|
||||
group: '@google',
|
||||
version: '1.19.0',
|
||||
purl: 'pkg:npm/%40google/genai@1.19.0',
|
||||
...syftSrc('/usr/local/lib/node_modules/n8n/node_modules/@google/genai/package.json'),
|
||||
}),
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps an application root outside node_modules (runners ship the task runner there)', () => {
|
||||
assert.equal(
|
||||
isPhantomNpm({
|
||||
|
|
|
|||
|
|
@ -238,7 +238,7 @@ describe('isPhantomNpm — properties', () => {
|
|||
}
|
||||
});
|
||||
|
||||
it('CANONICAL-PATH-WITH-VERSION-NEVER-PHANTOM', () => {
|
||||
it('CANONICAL-PATH-WITH-VERSION-NEVER-PHANTOM (for a well-formed npm name)', () => {
|
||||
const node = (group, name) => ({
|
||||
group,
|
||||
name,
|
||||
|
|
@ -255,6 +255,27 @@ describe('isPhantomNpm — properties', () => {
|
|||
assert.equal(isPhantomNpm(node('@n8n', 'db')), false);
|
||||
});
|
||||
|
||||
it('SUBPATH-NAME-ALWAYS-PHANTOM: a slash past the scope is never a real npm name', () => {
|
||||
const atCanonicalPath = (group, name) => ({
|
||||
group,
|
||||
name,
|
||||
version: '1.0.0',
|
||||
purl: `pkg:npm/${group ? group + '/' : ''}${name}@1.0.0`,
|
||||
properties: [
|
||||
{
|
||||
name: 'syft:location:0:path',
|
||||
value: `/x/node_modules/${group ? group + '/' : ''}${name}/package.json`,
|
||||
},
|
||||
],
|
||||
});
|
||||
// Canonical path AND a resolved version — every other signal says "real".
|
||||
assert.equal(isPhantomNpm(atCanonicalPath('@google', 'genai/node')), true);
|
||||
assert.equal(isPhantomNpm(atCanonicalPath('@linear', 'sdk/webhooks')), true);
|
||||
assert.equal(isPhantomNpm(atCanonicalPath('', 'foo/bar')), true);
|
||||
// The scope slash alone must not trip it.
|
||||
assert.equal(isPhantomNpm(atCanonicalPath('@google', 'genai')), false);
|
||||
});
|
||||
|
||||
it('NON-NPM-NEVER-PHANTOM: OS and other ecosystems are out of scope', () => {
|
||||
for (const purl of ['pkg:apk/alpine/busybox@1', 'pkg:deb/debian/libc@2', 'pkg:cargo/x@1']) {
|
||||
assert.equal(isPhantomNpm({ purl, name: 'x' }), false, purl);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user