fix(audit): require integer gateway window padding

Enforce non-negative integer padding_ms/--window-padding-ms across gateway window helper and live baseline capture CLI, with regression coverage for invalid values. Architecture/protocol diagrams reviewed; no updates were needed for this validation-only change.
This commit is contained in:
William Valentin
2026-02-27 13:18:37 -08:00
parent bf79f734f1
commit 0decf34760
4 changed files with 42 additions and 9 deletions
+13
View File
@@ -520,6 +520,19 @@
], ],
"test_status": "pnpm test:run src/audit/phase0BaselineDrift.test.ts + pnpm typecheck passing" "test_status": "pnpm test:run src/audit/phase0BaselineDrift.test.ts + pnpm typecheck passing"
}, },
"phase0-live-baseline-gateway-window-padding-integer-validation": {
"status": "completed",
"date": "2026-02-27",
"updated": "2026-02-27",
"summary": "Hardened gateway cancel-window auto-detection padding by requiring non-negative integer `padding_ms`/`--window-padding-ms` values in both helper and capture CLI, preventing silent flooring of fractional millisecond values.",
"files_modified": [
"src/audit/phase0GatewayWindow.ts",
"src/audit/phase0GatewayWindow.test.ts",
"scripts/capture-phase0-live-baseline.ts",
"docs/plans/state.json"
],
"test_status": "pnpm test:run src/audit/phase0GatewayWindow.test.ts + pnpm typecheck passing"
},
"phase0-instrumentation-ticket-checklist": { "phase0-instrumentation-ticket-checklist": {
"status": "completed", "status": "completed",
"date": "2026-02-25", "date": "2026-02-25",
+2 -5
View File
@@ -39,7 +39,7 @@ function usage(): string {
' --backend <native|pi_embedded|...[,..]> Restrict sample to selected backends (via backend.route timeline)', ' --backend <native|pi_embedded|...[,..]> Restrict sample to selected backends (via backend.route timeline)',
' --exclude-session-substring <text[,..]> Exclude sessions containing any substring (default: probe)', ' --exclude-session-substring <text[,..]> Exclude sessions containing any substring (default: probe)',
' --auto-gateway-cancel-window Auto-select latest gateway cancel/cancelled session window', ' --auto-gateway-cancel-window Auto-select latest gateway cancel/cancelled session window',
' --window-padding-ms <number> Milliseconds added before/after auto-selected window (default: 250)', ' --window-padding-ms <integer> Milliseconds added before/after auto-selected window (default: 250)',
' --raw-identifiers Keep raw session/sender/request IDs (default: anonymized)', ' --raw-identifiers Keep raw session/sender/request IDs (default: anonymized)',
' --tag <YYYY-MM-DD> Output file tag (default: current date UTC)', ' --tag <YYYY-MM-DD> Output file tag (default: current date UTC)',
' --sample-out <path> Output JSONL sample path override', ' --sample-out <path> Output JSONL sample path override',
@@ -202,10 +202,7 @@ async function main(): Promise<void> {
const backendTargets = parseBackendTargets(values.backend); const backendTargets = parseBackendTargets(values.backend);
const excludeSessionSubstrings = parseCsv(values['exclude-session-substring']) ?? ['probe']; const excludeSessionSubstrings = parseCsv(values['exclude-session-substring']) ?? ['probe'];
const autoGatewayCancelWindow = Boolean(values['auto-gateway-cancel-window']); const autoGatewayCancelWindow = Boolean(values['auto-gateway-cancel-window']);
const windowPaddingMs = parseOptionalNumber(values['window-padding-ms'], '--window-padding-ms'); const windowPaddingMs = parseOptionalInteger(values['window-padding-ms'], '--window-padding-ms');
if (windowPaddingMs !== undefined && windowPaddingMs < 0) {
throw new Error('--window-padding-ms must be greater than or equal to 0.');
}
if (autoGatewayCancelWindow && (values.since || values.until)) { if (autoGatewayCancelWindow && (values.since || values.until)) {
throw new Error('--auto-gateway-cancel-window cannot be combined with --since/--until.'); throw new Error('--auto-gateway-cancel-window cannot be combined with --since/--until.');
+10
View File
@@ -71,4 +71,14 @@ describe('findLatestGatewayCancelWindow', () => {
expect(findLatestGatewayCancelWindow(events)).toBeNull(); expect(findLatestGatewayCancelWindow(events)).toBeNull();
}); });
it('rejects invalid padding values', () => {
const events: AuditEvent[] = [
event(1000, 'run.cancel', { session_id: 's1', source: 'gateway' }),
event(1010, 'run.state', { session_id: 's1', source: 'gateway', state: 'cancelled' }),
];
expect(() => findLatestGatewayCancelWindow(events, { padding_ms: -1 })).toThrow('padding_ms');
expect(() => findLatestGatewayCancelWindow(events, { padding_ms: 1.5 })).toThrow('padding_ms');
});
}); });
+17 -4
View File
@@ -36,6 +36,22 @@ function isGatewayEvent(payload: Record<string, unknown>): boolean {
return readString(payload.source) === 'gateway'; return readString(payload.source) === 'gateway';
} }
function normalizePaddingMs(value: number | undefined): number {
if (value === undefined) {
return 0;
}
if (!Number.isFinite(value)) {
throw new Error('padding_ms must be a finite integer greater than or equal to 0.');
}
if (!Number.isInteger(value)) {
throw new Error('padding_ms must be an integer greater than or equal to 0.');
}
if (value < 0) {
throw new Error('padding_ms must be greater than or equal to 0.');
}
return value;
}
export function findLatestGatewayCancelWindow( export function findLatestGatewayCancelWindow(
events: AuditEvent[], events: AuditEvent[],
options: FindGatewayCancelWindowOptions = {}, options: FindGatewayCancelWindowOptions = {},
@@ -104,10 +120,7 @@ export function findLatestGatewayCancelWindow(
return null; return null;
} }
const padRaw = options.padding_ms ?? 0; const paddingMs = normalizePaddingMs(options.padding_ms);
const paddingMs = Number.isFinite(padRaw) && padRaw > 0
? Math.floor(padRaw)
: 0;
return { return {
session_id: latest.session_id, session_id: latest.session_id,