fix(gateguard): link SARIF findings to the evaluated corpus

This commit is contained in:
Jeffrey Montoya
2026-09-30 00:04:11 -04:00
parent 1221a8727f
commit 3055eb3746
2 changed files with 28 additions and 3 deletions
+12 -3
View File
@@ -6,6 +6,7 @@ const fs = require('fs');
const os = require('os');
const path = require('path');
const { performance } = require('perf_hooks');
const { pathToFileURL } = require('url');
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');
const REPO_ROOT = path.join(__dirname, '..', '..');
@@ -512,6 +513,15 @@ function sarifFindings(step) {
return findings;
}
function sarifArtifactLocation(corpusDir, file) {
const absolute = path.resolve(corpusDir || DEFAULT_CORPUS_DIR, file || '');
const relative = path.relative(REPO_ROOT, absolute);
if (relative && !relative.startsWith('..') && !path.isAbsolute(relative)) {
return { uri: relative.split(path.sep).join('/'), uriBaseId: '%SRCROOT%' };
}
return { uri: pathToFileURL(absolute).href };
}
function sarifTotals(summary) {
const { steps, denials, mustDenyBypasses, mismatches, explicitAllows, errors, skippedScenarios } = summary.totals;
return { steps, denials, mustDenyBypasses, mismatches, explicitAllows, errors, skippedScenarios };
@@ -530,9 +540,7 @@ function renderSarif(report) {
message: { text: `${step.scenario} / ${step.step}: expected ${step.expect}, got ${step.decision} (${step.kind})` },
locations: [
{
physicalLocation: {
artifactLocation: { uri: `tests/fixtures/gateguard-scenarios/${step.file}`, uriBaseId: '%SRCROOT%' }
}
physicalLocation: { artifactLocation: sarifArtifactLocation(report.corpusDir, step.file) }
}
]
});
@@ -614,6 +622,7 @@ async function evaluate({ baselines = [DEFAULT_BASELINE], corpus = DEFAULT_CORPU
}
return {
corpus: { scenarios: scenarios.length, steps: scenarios.reduce((sum, scenario) => sum + scenario.steps.length, 0) },
corpusDir: corpus,
hooks: evaluated
};
} finally {
+16
View File
@@ -5,7 +5,9 @@
'use strict';
const assert = require('assert');
const os = require('os');
const path = require('path');
const { pathToFileURL } = require('url');
const { parseArgs, renderSarif } = require('../../scripts/dev/gateguard-eval');
let passed = 0;
@@ -105,6 +107,20 @@ test('a must-deny step that was allowed is an error on its scenario file', () =>
assert.match(result.message.text, /sensitive-targets \/ edit-env: expected deny, got allow \(credit\)/);
});
test('a finding from another corpus inside the repository links to that corpus', () => {
const failing = report([step({ decision: 'allow', kind: 'credit', file: 'custom.json' })]);
failing.corpusDir = path.join(__dirname, '..', 'fixtures', 'other-scenarios');
const location = results(JSON.parse(renderSarif(failing)))[0].locations[0].physicalLocation.artifactLocation;
assert.deepStrictEqual(location, { uri: 'tests/fixtures/other-scenarios/custom.json', uriBaseId: '%SRCROOT%' });
});
test('a finding from a corpus outside the repository links to its absolute file', () => {
const failing = report([step({ decision: 'allow', kind: 'credit', file: 'custom.json' })]);
failing.corpusDir = path.join(os.tmpdir(), 'gateguard-corpus');
const location = results(JSON.parse(renderSarif(failing)))[0].locations[0].physicalLocation.artifactLocation;
assert.deepStrictEqual(location, { uri: pathToFileURL(path.join(os.tmpdir(), 'gateguard-corpus', 'custom.json')).href });
});
test('an explicit allow and a hook error are reported as errors', () => {
const log = JSON.parse(
renderSarif(