From e86e03a5f1c6281e2faa6dca8056b7ba9b2324b8 Mon Sep 17 00:00:00 2001 From: s-b-e-n-s-o-n <80784472+s-b-e-n-s-o-n@users.noreply.github.com> Date: Thu, 28 May 2026 23:19:46 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(ci):=20preserve=20leading=20?= =?UTF-8?q?slash=20in=20ZAP=20SARIF=20artifact=20URIs=20so=20root=20scans?= =?UTF-8?q?=20validate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GHAS Code Scanning rejects SARIF results with an empty artifactLocation.uri (error: locationFromSarifResult: expected artifact location). The root URL http://localhost:3333/ produced pathname '/' which, after stripping the leading slash, became ''. Keep the leading slash so root scans emit uri='/' and all other paths emit '/foo' — both are valid absolute-path-references per RFC 3986 §4.2 and resolve correctly against originalUriBaseIds.TARGET. --- scripts/zap-json-to-sarif.mjs | 8 +++++--- scripts/zap-json-to-sarif.test.mjs | 12 +++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/scripts/zap-json-to-sarif.mjs b/scripts/zap-json-to-sarif.mjs index 580834da..c205add8 100644 --- a/scripts/zap-json-to-sarif.mjs +++ b/scripts/zap-json-to-sarif.mjs @@ -154,9 +154,11 @@ function resolveArtifactLocation(rawUri) { try { const parsed = new URL(rawUri); if (parsed.protocol === 'http:' || parsed.protocol === 'https:') { - // Relative path (without leading slash) + uriBaseId referencing the origin. - const relative = (parsed.pathname + parsed.search + parsed.hash).replace(/^\//, ''); - return { uri: relative || '', uriBaseId: 'TARGET', origin: parsed.origin + '/' }; + // Keep the leading slash so the root path becomes '/' instead of ''. + // GHAS Code Scanning rejects empty artifactLocation.uri values + // (locationFromSarifResult: expected artifact location). + const path = parsed.pathname + parsed.search + parsed.hash; + return { uri: path, uriBaseId: 'TARGET', origin: parsed.origin + '/' }; } } catch { // Not a URL — fall through and return as-is. diff --git a/scripts/zap-json-to-sarif.test.mjs b/scripts/zap-json-to-sarif.test.mjs index 3a18f7ec..ff07a3e5 100644 --- a/scripts/zap-json-to-sarif.test.mjs +++ b/scripts/zap-json-to-sarif.test.mjs @@ -76,7 +76,8 @@ describe('zap-json-to-sarif', () => { assert.equal(sarif.runs[0].results[0].ruleId, '10055-6'); assert.equal(sarif.runs[0].results[0].level, 'warning'); // http URIs must be relativised: origin goes into originalUriBaseIds, path into uri. - assert.equal(sarif.runs[0].results[0].locations[0].physicalLocation.artifactLocation.uri, ''); + // Root path must be '/' not '' — GHAS rejects empty artifactLocation.uri. + assert.equal(sarif.runs[0].results[0].locations[0].physicalLocation.artifactLocation.uri, '/'); assert.equal( sarif.runs[0].results[0].locations[0].physicalLocation.artifactLocation.uriBaseId, 'TARGET', @@ -135,10 +136,10 @@ describe('zap-json-to-sarif', () => { assert.equal(sarif.runs[0].tool.driver.rules.length, 1); assert.equal(sarif.runs[0].results.length, 1); assert.equal(sarif.runs[0].results[0].ruleId, 'singleton-alert'); - // http URI → relative path + uriBaseId; origin hoisted to originalUriBaseIds. + // http URI → absolute-path-reference + uriBaseId; origin hoisted to originalUriBaseIds. assert.equal( sarif.runs[0].results[0].locations[0].physicalLocation.artifactLocation.uri, - 'singleton', + '/singleton', ); assert.equal( sarif.runs[0].results[0].locations[0].physicalLocation.artifactLocation.uriBaseId, @@ -191,12 +192,13 @@ describe('zap-json-to-sarif', () => { ], }); - // http URIs are relativised; non-http fallbacks ('zap-target') pass through unchanged. + // http URIs become absolute-path-references (leading slash preserved); + // non-http fallbacks ('zap-target') pass through unchanged. assert.deepEqual( sarif.runs[0].results.map( (result) => result.locations[0].physicalLocation.artifactLocation.uri, ), - ['node', '', 'zap-target'], + ['/node', '/', 'zap-target'], ); assert.deepEqual( sarif.runs[0].results.map(