import assert from 'node:assert/strict'; import test from 'node:test'; import { buildConsolidatedAdvisoryFeed, buildGhsaWithoutCveFeed, fetchGitHubSecurityAdvisories, inferPlatforms, normalizeGhsaAdvisory, } from './ghsa-without-cve-feed.mjs'; const fixedNow = '2026-05-24T00:00:00Z'; function advisory(overrides = {}) { return { ghsa_id: 'GHSA-test-1111-2222', cve_id: null, html_url: 'https://github.com/openclaw/openclaw/security/advisories/GHSA-test-1111-2222', summary: 'Workspace bridge allows sandbox escape', description: 'OpenClaw before 2026.4.25 allowed a sandbox escape.', severity: 'high', published_at: '2026-04-24T00:00:00Z', updated_at: '2026-04-25T00:00:00Z', vulnerabilities: [ { package: { ecosystem: 'npm', name: 'openclaw' }, vulnerable_version_range: '<2026.4.25', patched_versions: '2026.4.25', }, ], cvss: { vector_string: 'CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H', score: 7.8, }, cwe_ids: ['CWE-94'], credits: [{ login: 'researcher', type: 'reporter' }], ...overrides, }; } test('inferPlatforms maps known repositories to feed platforms', () => { assert.deepEqual(inferPlatforms('openclaw/openclaw'), ['openclaw']); assert.deepEqual(inferPlatforms('qwibitai/nanoclaw'), ['nanoclaw']); assert.deepEqual(inferPlatforms('softwarepub/hermes'), ['hermes']); assert.deepEqual(inferPlatforms('sipeed/picoclaw'), ['picoclaw']); }); test('fetchGitHubSecurityAdvisories follows cursor pagination links', async (t) => { const originalFetch = globalThis.fetch; const nextUrl = 'https://api.github.com/repositories/1103012935/security-advisories?per_page=100&after=cursor'; const calls = []; globalThis.fetch = async (url) => { calls.push(String(url)); if (calls.length === 1) { return new globalThis.Response( JSON.stringify( Array.from({ length: 100 }, (_, index) => advisory({ ghsa_id: `GHSA-page-1111-${String(index).padStart(4, '0')}` }), ), ), { status: 200, headers: { Link: `<${nextUrl}>; rel="next"`, }, }, ); } if (String(url) !== nextUrl) { throw new Error(`unexpected pagination URL: ${url}`); } return new globalThis.Response(JSON.stringify([advisory({ ghsa_id: 'GHSA-next-1111-2222' })]), { status: 200, }); }; t.after(() => { globalThis.fetch = originalFetch; }); const advisories = await fetchGitHubSecurityAdvisories('openclaw/openclaw', { token: 'test-token', }); assert.equal(calls.length, 2); assert.equal(calls[1], nextUrl); assert.equal(advisories.length, 101); assert.equal(advisories.at(-1).ghsa_id, 'GHSA-next-1111-2222'); }); test('normalizeGhsaAdvisory marks fresh GHSA-only advisories active', () => { const normalized = normalizeGhsaAdvisory(advisory(), { now: fixedNow, repository: 'openclaw/openclaw', staleAfterDays: 60, }); assert.equal(normalized.id, 'GHSA-test-1111-2222'); assert.equal(normalized.status, 'active'); assert.equal(normalized.cve_id, null); assert.equal(normalized.stale, false); assert.deepEqual(normalized.platforms, ['openclaw']); assert.deepEqual(normalized.affected, ['openclaw@<2026.4.25']); }); test('normalizeGhsaAdvisory marks old GHSA-only advisories stale after threshold', () => { const normalized = normalizeGhsaAdvisory( advisory({ published_at: '2026-03-01T00:00:00Z' }), { now: fixedNow, repository: 'openclaw/openclaw', staleAfterDays: 60, }, ); assert.equal(normalized.status, 'stale'); assert.equal(normalized.stale, true); assert.equal(normalized.cve_id, null); }); test('normalizeGhsaAdvisory marks existing GHSA entries matured when a CVE appears', () => { const normalized = normalizeGhsaAdvisory( advisory({ cve_id: 'CVE-2026-9999' }), { now: fixedNow, repository: 'openclaw/openclaw', staleAfterDays: 60, }, ); assert.equal(normalized.status, 'matured'); assert.equal(normalized.stale, false); assert.equal(normalized.cve_id, 'CVE-2026-9999'); assert.equal(normalized.nvd_url, 'https://nvd.nist.gov/vuln/detail/CVE-2026-9999'); }); test('buildGhsaWithoutCveFeed only imports CVE-backed advisories that were already tracked', () => { const existing = { version: '0.1.0', advisories: [ normalizeGhsaAdvisory(advisory({ ghsa_id: 'GHSA-old-1111-2222' }), { now: '2026-04-25T00:00:00Z', repository: 'openclaw/openclaw', staleAfterDays: 60, }), ], }; const fetched = [ { repository: 'openclaw/openclaw', advisories: [ advisory({ ghsa_id: 'GHSA-new-1111-2222', cve_id: null }), advisory({ ghsa_id: 'GHSA-old-1111-2222', cve_id: 'CVE-2026-1111' }), advisory({ ghsa_id: 'GHSA-cve-only-1111-2222', cve_id: 'CVE-2026-2222' }), ], }, ]; const feed = buildGhsaWithoutCveFeed({ fetched, existingFeed: existing, nvdFeed: { advisories: [] }, now: fixedNow, staleAfterDays: 60, }); assert.deepEqual( feed.advisories.map((entry) => [entry.id, entry.status, entry.cve_id]), [ ['GHSA-new-1111-2222', 'active', null], ['GHSA-old-1111-2222', 'matured', 'CVE-2026-1111'], ], ); }); test('buildGhsaWithoutCveFeed matures tracked GHSAs when the CVE feed references them', () => { const existing = { version: '0.1.0', advisories: [ normalizeGhsaAdvisory(advisory({ ghsa_id: 'GHSA-oooo-3333-4444' }), { now: '2026-04-25T00:00:00Z', repository: 'openclaw/openclaw', staleAfterDays: 60, }), ], }; const feed = buildGhsaWithoutCveFeed({ fetched: [ { repository: 'openclaw/openclaw', advisories: [advisory({ ghsa_id: 'GHSA-oooo-3333-4444', cve_id: null })], }, ], existingFeed: existing, nvdFeed: { advisories: [ { id: 'CVE-2026-3333', references: [ 'https://github.com/openclaw/openclaw/security/advisories/GHSA-oooo-3333-4444', ], }, ], }, now: fixedNow, staleAfterDays: 60, }); assert.equal(feed.advisories[0].status, 'matured'); assert.equal(feed.advisories[0].cve_id, 'CVE-2026-3333'); }); test('buildConsolidatedAdvisoryFeed appends active GHSA advisories without moving the NVD poll cursor', () => { const canonicalFeed = { version: '1.0.0', updated: '2026-05-23T00:00:00Z', description: 'Community-driven security advisory feed for ClawSec', advisories: [ { id: 'CVE-2026-1111', severity: 'high', type: 'os_command_injection', title: 'Existing CVE', description: 'Existing CVE advisory', affected: ['openclaw@*'], platforms: ['openclaw'], action: 'Review NVD.', published: '2026-05-01T00:00:00Z', }, ], }; const ghsaFeed = { advisories: [ normalizeGhsaAdvisory(advisory({ ghsa_id: 'GHSA-active-1111-2222', cve_id: null }), { now: fixedNow, repository: 'openclaw/openclaw', staleAfterDays: 60, }), ], }; const consolidated = buildConsolidatedAdvisoryFeed({ canonicalFeed, ghsaFeed, now: fixedNow, }); assert.deepEqual( consolidated.advisories.map((entry) => entry.id), ['CVE-2026-1111', 'GHSA-active-1111-2222'], ); assert.equal(consolidated.updated, canonicalFeed.updated); assert.equal(consolidated.advisories[1].source_feed, 'ghsa-without-cve'); }); test('buildConsolidatedAdvisoryFeed keeps existing GHSA advisories when replacement feed is empty', () => { const canonicalFeed = { version: '1.0.0', updated: '2026-05-23T00:00:00Z', advisories: [ { id: 'CVE-2026-1111', published: '2026-05-01T00:00:00Z', }, { id: 'GHSA-keep-1111-2222', ghsa_id: 'GHSA-keep-1111-2222', status: 'active', published: '2026-05-02T00:00:00Z', source_feed: 'ghsa-without-cve', }, ], }; const consolidated = buildConsolidatedAdvisoryFeed({ canonicalFeed, ghsaFeed: { advisories: [] }, now: fixedNow, }); assert.deepEqual( consolidated.advisories.map((entry) => entry.id), ['GHSA-keep-1111-2222', 'CVE-2026-1111'], ); }); test('buildConsolidatedAdvisoryFeed replaces only matching GHSA canonical entries', () => { const canonicalFeed = { version: '1.0.0', updated: '2026-05-23T00:00:00Z', advisories: [ { id: 'GHSA-repl-1111-2222', ghsa_id: 'GHSA-repl-1111-2222', status: 'active', title: 'Old GHSA payload', published: '2026-05-01T00:00:00Z', source_feed: 'ghsa-without-cve', }, { id: 'GHSA-keep-3333-4444', ghsa_id: 'GHSA-keep-3333-4444', status: 'active', title: 'Retained GHSA payload', published: '2026-05-02T00:00:00Z', source_feed: 'ghsa-without-cve', }, ], }; const ghsaFeed = { advisories: [ { id: 'GHSA-repl-1111-2222', ghsa_id: 'GHSA-repl-1111-2222', status: 'stale', title: 'Replacement GHSA payload', published: '2026-05-03T00:00:00Z', }, ], }; const consolidated = buildConsolidatedAdvisoryFeed({ canonicalFeed, ghsaFeed, now: fixedNow, }); assert.deepEqual( consolidated.advisories.map((entry) => [entry.id, entry.title, entry.status]), [ ['GHSA-repl-1111-2222', 'Replacement GHSA payload', 'stale'], ['GHSA-keep-3333-4444', 'Retained GHSA payload', 'active'], ], ); }); test('buildConsolidatedAdvisoryFeed drops GHSA duplicate when matching CVE is present', () => { const canonicalFeed = { version: '1.0.0', updated: '2026-05-23T00:00:00Z', advisories: [ { id: 'CVE-2026-2222', severity: 'high', type: 'code_injection', title: 'Canonical CVE', description: 'Canonical CVE advisory', affected: ['openclaw@*'], platforms: ['openclaw'], action: 'Review NVD.', published: '2026-05-02T00:00:00Z', }, { id: 'GHSA-old-duplicate', ghsa_id: 'GHSA-old-duplicate', cve_id: 'CVE-2026-2222', status: 'matured', source_feed: 'ghsa-without-cve', severity: 'high', type: 'github_security_advisory', title: 'Old duplicate', description: 'Old provisional duplicate', affected: ['openclaw@*'], platforms: ['openclaw'], action: 'Track CVE.', published: '2026-05-01T00:00:00Z', }, ], }; const ghsaFeed = { advisories: [ normalizeGhsaAdvisory( advisory({ ghsa_id: 'GHSA-new-duplicate', cve_id: 'CVE-2026-2222' }), { now: fixedNow, repository: 'openclaw/openclaw', staleAfterDays: 60, }, ), ], }; const consolidated = buildConsolidatedAdvisoryFeed({ canonicalFeed, ghsaFeed, now: fixedNow, }); assert.deepEqual( consolidated.advisories.map((entry) => entry.id), ['CVE-2026-2222'], ); }); test('buildConsolidatedAdvisoryFeed keeps matured GHSA until CVE lands in canonical feed', () => { const canonicalFeed = { version: '1.0.0', updated: '2026-05-23T00:00:00Z', advisories: [], }; const ghsaFeed = { advisories: [ normalizeGhsaAdvisory( advisory({ ghsa_id: 'GHSA-matured-1111-2222', cve_id: 'CVE-2026-4444' }), { now: fixedNow, repository: 'openclaw/openclaw', staleAfterDays: 60, }, ), ], }; const consolidated = buildConsolidatedAdvisoryFeed({ canonicalFeed, ghsaFeed, now: fixedNow, }); assert.deepEqual( consolidated.advisories.map((entry) => [entry.id, entry.status, entry.cve_id]), [['GHSA-matured-1111-2222', 'matured', 'CVE-2026-4444']], ); });