← back to Secrets Manager
Sanitize verifier report fields
2363f17529aa26f370ff4f85f97b7e7dad1ce245 · 2026-08-28 22:24:37 -0700 · Steve Abrams
Files touched
M cli.jsM test/verify-all.test.jsM verification/e2e-proof.json
Diff
commit 2363f17529aa26f370ff4f85f97b7e7dad1ce245
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Aug 28 22:24:37 2026 -0700
Sanitize verifier report fields
---
cli.js | 17 ++++++++++++-----
test/verify-all.test.js | 22 +++++++++++++++++++++-
verification/e2e-proof.json | 4 ++--
3 files changed, 35 insertions(+), 8 deletions(-)
diff --git a/cli.js b/cli.js
index 6445cde..4e1bfd2 100755
--- a/cli.js
+++ b/cli.js
@@ -305,9 +305,12 @@ async function buildVerifyAllReport({ master, routes = ROUTES, verifier = verify
}
async function boundedVerify(verifier, key, value, timeoutMs = 10000) {
+ const safeTimeoutMs = Number.isSafeInteger(timeoutMs) && timeoutMs >= 1 && timeoutMs <= 60000
+ ? timeoutMs
+ : 10000;
let timer;
const timeout = new Promise((resolve) => {
- timer = setTimeout(() => resolve({ ok: false, reason: 'timeout' }), timeoutMs);
+ timer = setTimeout(() => resolve({ ok: false, reason: 'timeout' }), safeTimeoutMs);
});
try {
const result = await Promise.race([
@@ -317,10 +320,14 @@ async function boundedVerify(verifier, key, value, timeoutMs = 10000) {
if (!result || typeof result !== 'object' || typeof result.ok !== 'boolean') {
return { ok: false, reason: 'malformed-result' };
}
- if (!result.ok && !result.reason) {
- return { ok: false, status: result.status, reason: result.error ? 'network-error' : 'provider-rejected' };
- }
- return result;
+ const status = Number.isInteger(result.status) && result.status >= 100 && result.status <= 599
+ ? result.status
+ : null;
+ if (result.ok) return { ok: true, status };
+ const reason = result.reason === 'timeout' || result.reason === 'verifier-error'
+ ? result.reason
+ : result.error ? 'network-error' : 'provider-rejected';
+ return { ok: false, status, reason };
} finally {
clearTimeout(timer);
}
diff --git a/test/verify-all.test.js b/test/verify-all.test.js
index 3c121ed..1ff8970 100644
--- a/test/verify-all.test.js
+++ b/test/verify-all.test.js
@@ -5,7 +5,7 @@ const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
-const { buildVerifyAllReport, cmdVerifyAll, main } = require('../cli.js');
+const { boundedVerify, buildVerifyAllReport, cmdVerifyAll, main } = require('../cli.js');
const routes = {
_comment: { text: 'metadata is not a secret' },
@@ -97,3 +97,23 @@ test('blank secrets count as missing and CLI exit code fails only aggregate FAIL
assert.equal(await run('warn', 'value', {}, async () => ({ ok: true })), 0);
assert.equal(await run('pass', 'value', { verify: {} }, async () => ({ ok: true, status: 200 })), 0);
});
+
+test('allowlists verifier output fields and rejects hostile timeout configuration', async () => {
+ const marker = 'token-do-not-leak';
+ for (const result of [
+ { ok: false, reason: marker },
+ { ok: false, status: { credential: marker } },
+ { ok: true, status: { body: marker } },
+ { ok: false, reason: { body: marker } },
+ ]) {
+ const normalized = await boundedVerify(async () => result, 'TEST_KEY', 'secret', 5);
+ assert.doesNotMatch(JSON.stringify(normalized), new RegExp(marker));
+ assert.ok(normalized.status === null || normalized.status === undefined);
+ if (!normalized.ok) assert.match(normalized.reason, /^(provider-rejected|network-error)$/);
+ }
+
+ for (const timeoutMs of [0, -1, NaN, Infinity, 60001, 1.5]) {
+ const normalized = await boundedVerify(async () => ({ ok: true, status: 204 }), 'TEST_KEY', 'secret', timeoutMs);
+ assert.deepEqual(normalized, { ok: true, status: 204 });
+ }
+});
diff --git a/verification/e2e-proof.json b/verification/e2e-proof.json
index a357586..e8394fb 100644
--- a/verification/e2e-proof.json
+++ b/verification/e2e-proof.json
@@ -9,11 +9,11 @@
{ "verdict": "PASS", "boundary": "classification", "command": "node --test test/verify-all.test.js", "assertions": "PASS/WARN/FAIL aggregation covers verified, rejected, missing, and no-endpoint keys" },
{ "verdict": "PASS", "boundary": "secret redaction", "command": "node --test test/verify-all.test.js", "assertions": "report serialization contains no input secret values or provider bodies" },
{ "verdict": "PASS", "boundary": "report artifact", "command": "node --test test/verify-all.test.js", "assertions": "temporary data/latest.json round-trips to the returned schema" },
- { "verdict": "PASS", "boundary": "failure isolation", "command": "node --test test/verify-all.test.js", "assertions": "timeout, throw, and malformed verifier results become sanitized FAIL rows and later checks continue" },
+ { "verdict": "PASS", "boundary": "failure isolation", "command": "node --test test/verify-all.test.js", "assertions": "timeout, throw, malformed, and hostile verifier results become allowlisted rows and later checks continue" },
{ "verdict": "PASS", "boundary": "permissions and exit status", "command": "node --test test/verify-all.test.js", "assertions": "report replacement remains 0600; FAIL exits 1 while PASS/WARN exit 0" },
{ "verdict": "PASS", "boundary": "syntax/diff", "command": "node --check cli.js && git diff --check", "assertions": "CLI parses and diff is clean" }
],
- "negative_checks": ["provider rejection", "configured key missing or blank in master", "key lacking a verify endpoint", "provider body containing the secret", "never-settling verifier", "thrown verifier", "undefined verifier result"],
+ "negative_checks": ["provider rejection", "configured key missing or blank in master", "key lacking a verify endpoint", "provider body containing the secret", "never-settling verifier", "thrown verifier", "undefined verifier result", "verifier-controlled reason/status objects", "invalid timeout configuration"],
"side_effects": "temporary test report only; no provider request, secret write, route/registry change, launchd install, restart, deploy, or send",
"cleanup": "temporary directory removed by test",
"verdict": "PASS for the local report pipeline"
← f585023 Harden secret freshness verification failures
·
back to Secrets Manager
·
auto-data-snapshot: 2026-08-29T12:47:37 (2 data files) — reg 87ca939 →