test: preserve Windows GateGuard corpus denial policy

This commit is contained in:
haelyra committed 2026-10-11 06:15:49 -04:00
1 parent cc12310b69
commit a0b286011a
3 files changed
+49 -2

No files matched your search

+10 -2
View File
@@ -62,6 +62,9 @@ function validateStep(scenario, step, index) {
if (!step.payload || typeof step.payload !== 'object') fail(`${where}: missing payload`);
if (typeof step.payload.tool_name !== 'string') fail(`${where}: payload.tool_name must be a string`);
if (!EXPECTATIONS.has(step.expect)) fail(`${where}: expect must be deny or allow`);
if (step.expectOnWindows !== undefined && step.expectOnWindows !== 'deny') {
fail(`${where}: expectOnWindows may only strengthen the expectation to deny`);
}
if (typeof step.mustDeny !== 'boolean') fail(`${where}: mustDeny must be a boolean`);
if (typeof step.redundant !== 'boolean') fail(`${where}: redundant must be a boolean`);
if (step.mustDeny && step.expect !== 'deny') fail(`${where}: mustDeny requires expect deny`);
@@ -86,7 +89,7 @@ function validateScenario(scenario, file) {
}
/** Load and validate every scenario file in a corpus directory, sorted by file name. */
function loadCorpus(dir = DEFAULT_CORPUS_DIR) {
function loadCorpus(dir = DEFAULT_CORPUS_DIR, platform = process.platform) {
return fs
.readdirSync(dir)
.filter(name => name.endsWith('.json'))
@@ -94,7 +97,12 @@ function loadCorpus(dir = DEFAULT_CORPUS_DIR) {
.map(name => {
const scenario = JSON.parse(fs.readFileSync(path.join(dir, name), 'utf8'));
validateScenario(scenario, name);
return { ...scenario, file: name };
// Folded Windows filenames cannot prove case-sensitive type coverage.
// Normalize the shared corpus so assertions, summaries and SARIF agree.
const steps = scenario.steps.map(step => platform === 'win32' && step.expectOnWindows === 'deny'
? { ...step, expect: 'deny', mustDeny: true, redundant: false }
: step);
return { ...scenario, steps, file: name };
});
}
@@ -1022,6 +1022,7 @@
},
{
"id": "rg-type-includes-target",
"expectOnWindows": "deny",
"transcript": [
{
"type": "user",
+38
View File
@@ -67,6 +67,44 @@ function results(log) {
console.log('\n=== Testing gateguard-eval ===\n');
test('the corpus preserves POSIX type credit and requires denial for folded Windows names', () => {
const posix = loadCorpus(undefined, 'linux');
const windows = loadCorpus(undefined, 'win32');
const typeStep = corpus => corpus.find(scenario => scenario.name === 'bypass-filter-forms')
.steps.find(candidate => candidate.id === 'rg-type-includes-target');
assert.strictEqual(typeStep(posix).expect, 'allow');
assert.strictEqual(typeStep(posix).redundant, true);
assert.strictEqual(typeStep(windows).expect, 'deny');
assert.strictEqual(typeStep(windows).mustDeny, true);
assert.strictEqual(typeStep(windows).redundant, false);
posix.forEach((scenario, index) => scenario.steps.forEach((candidate, at) => {
if (candidate.mustDeny) assert.deepStrictEqual(windows[index].steps[at], candidate);
}));
const scenario = windows.find(candidate => candidate.name === 'bypass-filter-forms');
const expected = typeStep(windows);
const run = decision => [{ scenario: { ...scenario, steps: [expected] }, results: [{
decision, kind: decision === 'deny' ? 'deny' : 'credit', latencyMs: 0, reason: ''
}] }];
assert.strictEqual(summarize(run('deny')).totals.mismatches, 0);
assert.strictEqual(summarize(run('deny')).totals.redundantDenials, 0);
assert.strictEqual(summarize(run('allow')).totals.mustDenyBypasses, 1);
});
test('Windows corpus overrides only strengthen denial and reject invalid values on every host', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gateguard-platform-corpus-'));
try {
const scenario = loadCorpus()[0];
for (const expectOnWindows of ['allow', 'invalid', false]) {
fs.writeFileSync(path.join(dir, 'fixture.json'), JSON.stringify({
...scenario, steps: [{ ...scenario.steps[0], expectOnWindows }]
}));
for (const platform of ['linux', 'win32']) assert.throws(() => loadCorpus(dir, platform), /expectOnWindows/);
}
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('--sarif takes a file path and keeps the other options', () => {
const options = parseArgs(['--json', '--sarif', 'out/gate.sarif', '--baseline', 'main']);
assert.strictEqual(options.format, 'json');