← back to Secrets Manager
Add secret freshness report command
76a6bf346810a0ec379f15e9c165d9633a2244f1 · 2026-08-28 22:18:37 -0700 · Steve Abrams
Files touched
M cli.jsA test/verify-all.test.jsA verification/e2e-proof.json
Diff
commit 76a6bf346810a0ec379f15e9c165d9633a2244f1
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Aug 28 22:18:37 2026 -0700
Add secret freshness report command
---
cli.js | 69 ++++++++++++++++++++++++++++++++++++++++++---
test/verify-all.test.js | 61 +++++++++++++++++++++++++++++++++++++++
verification/e2e-proof.json | 18 ++++++++++++
3 files changed, 144 insertions(+), 4 deletions(-)
diff --git a/cli.js b/cli.js
index 6d41835..59e5a24 100755
--- a/cli.js
+++ b/cli.js
@@ -6,6 +6,7 @@
// node cli.js add <KEY> <VALUE>
// node cli.js import-paste # reads KEY=VALUE block from stdin
// node cli.js check <KEY>
+// node cli.js verify-all # verify configured keys; write data/latest.json
// node cli.js sync # re-fan master → all destinations
// node cli.js audit # scan home for leaked secrets
//
@@ -265,6 +266,61 @@ async function verifyToken(key, value) {
}
}
+function configuredSecretKeys(routes = ROUTES) {
+ const isSecretKey = (key) => /^[A-Z][A-Z0-9_]*$/.test(key);
+ const keys = new Set(Object.keys(routes.services || {}).filter(isSecretKey));
+ for (const [key, value] of Object.entries(routes)) {
+ if (isSecretKey(key) && value && typeof value === 'object') keys.add(key);
+ }
+ return [...keys].sort();
+}
+
+async function buildVerifyAllReport({ master, routes = ROUTES, verifier = verifyToken, now = new Date() }) {
+ const checks = [];
+ const lookup = (key) => (routes.services && routes.services[key]) || routes[key] || null;
+ for (const key of configuredSecretKeys(routes)) {
+ const cfg = lookup(key);
+ const value = master[key];
+ if (!value) {
+ checks.push({ key, outcome: cfg?.verify ? 'FAIL' : 'WARN', reason: 'missing-from-master' });
+ continue;
+ }
+ if (!cfg?.verify) {
+ checks.push({ key, outcome: 'WARN', reason: 'no-verify-endpoint' });
+ continue;
+ }
+ const result = await verifier(key, value);
+ checks.push(result.ok
+ ? { key, outcome: 'PASS', http_status: result.status || null }
+ : { key, outcome: 'FAIL', http_status: result.status || null, reason: result.error ? 'network-error' : 'provider-rejected' });
+ }
+ const counts = checks.reduce((acc, check) => { acc[check.outcome]++; return acc; }, { PASS: 0, WARN: 0, FAIL: 0 });
+ return {
+ schema_version: 1,
+ generated_at: now.toISOString(),
+ status: counts.FAIL ? 'FAIL' : counts.WARN ? 'WARN' : 'PASS',
+ summary: { total: checks.length, ...counts },
+ checks,
+ };
+}
+
+async function cmdVerifyAll(options = {}) {
+ const report = await buildVerifyAllReport({
+ master: options.master || loadEnvFile(MASTER_ENV),
+ routes: options.routes || ROUTES,
+ verifier: options.verifier || verifyToken,
+ now: options.now || new Date(),
+ });
+ const outputPath = options.outputPath || path.join(ROOT, 'data', 'latest.json');
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
+ const tempPath = `${outputPath}.tmp-${process.pid}`;
+ fs.writeFileSync(tempPath, JSON.stringify(report, null, 2) + '\n');
+ fs.renameSync(tempPath, outputPath);
+ console.log(`verify-all ${report.status} — PASS=${report.summary.PASS} WARN=${report.summary.WARN} FAIL=${report.summary.FAIL}`);
+ console.log(`report: ${outputPath}`);
+ return report;
+}
+
// ─── fan-out ────────────────────────────────────────────────────────────
function fanOut(key, value) {
const destinations = routeFor(key)?.destinations || [];
@@ -560,18 +616,23 @@ function cmdRegen(args) {
}
// ─── dispatch ───────────────────────────────────────────────────────────
-const [, , cmd, ...rest] = process.argv;
-(async () => {
+async function main(argv = process.argv.slice(2)) {
+ const [cmd, ...rest] = argv;
switch (cmd) {
case 'add': await cmdAdd(rest[0], rest[1]); break;
case 'import-paste': await cmdImportPaste(); break;
case 'list': cmdList(); break;
case 'check': await cmdCheck(rest[0]); break;
+ case 'verify-all': await cmdVerifyAll(); break;
case 'sync': cmdSync(); break;
case 'audit': cmdAudit(); break;
case 'regen': cmdRegen(rest); break;
default:
- console.error('usage: cli.js <add|import-paste|list|check|sync|audit|regen> [args]');
+ console.error('usage: cli.js <add|import-paste|list|check|verify-all|sync|audit|regen> [args]');
process.exit(2);
}
-})().catch(e => { console.error('ERR:', e.message); process.exit(1); });
+}
+
+if (require.main === module) main().catch(e => { console.error('ERR:', e.message); process.exit(1); });
+
+module.exports = { buildVerifyAllReport, cmdVerifyAll, configuredSecretKeys };
diff --git a/test/verify-all.test.js b/test/verify-all.test.js
new file mode 100644
index 0000000..41ba9f1
--- /dev/null
+++ b/test/verify-all.test.js
@@ -0,0 +1,61 @@
+'use strict';
+
+const test = require('node:test');
+const assert = require('node:assert/strict');
+const fs = require('node:fs');
+const os = require('node:os');
+const path = require('node:path');
+const { buildVerifyAllReport, cmdVerifyAll } = require('../cli.js');
+
+const routes = {
+ _comment: { text: 'metadata is not a secret' },
+ leak_patterns: [{ regex: 'unused' }],
+ services: {
+ GOOD_KEY: { verify: { url: 'https://unused.test' } },
+ BAD_KEY: { verify: { url: 'https://unused.test' } },
+ MANUAL_KEY: { label: 'manual only' },
+ MISSING_KEY: { verify: { url: 'https://unused.test' } },
+ },
+};
+const master = { GOOD_KEY: 'secret-good', BAD_KEY: 'secret-bad', MANUAL_KEY: 'secret-manual' };
+
+test('builds a deterministic FAIL report without retaining secret values or response bodies', async () => {
+ const seen = [];
+ const report = await buildVerifyAllReport({
+ master, routes, now: new Date('2026-08-29T05:20:00Z'),
+ verifier: async (key, value) => {
+ seen.push([key, value]);
+ return key === 'GOOD_KEY' ? { ok: true, status: 200, body: value } : { ok: false, status: 401, body: value };
+ },
+ });
+ assert.equal(report.status, 'FAIL');
+ assert.deepEqual(report.summary, { total: 4, PASS: 1, WARN: 1, FAIL: 2 });
+ assert.deepEqual(seen.map(([key]) => key), ['BAD_KEY', 'GOOD_KEY']);
+ const serialized = JSON.stringify(report);
+ assert.doesNotMatch(serialized, /secret-good|secret-bad|secret-manual/);
+ assert.doesNotMatch(serialized, /body/);
+ assert.equal(report.generated_at, '2026-08-29T05:20:00.000Z');
+});
+
+test('status is WARN for unverified keys and PASS when every configured key verifies', async () => {
+ const warn = await buildVerifyAllReport({ master: { MANUAL_KEY: 'x' }, routes: { services: { MANUAL_KEY: {} } } });
+ assert.equal(warn.status, 'WARN');
+ const pass = await buildVerifyAllReport({
+ master: { GOOD_KEY: 'x' }, routes: { services: { GOOD_KEY: { verify: {} } } },
+ verifier: async () => ({ ok: true, status: 204 }),
+ });
+ assert.equal(pass.status, 'PASS');
+});
+
+test('writes the latest report atomically for local consumers without network calls', async (t) => {
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'secret-freshness-'));
+ t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
+ const outputPath = path.join(dir, 'data', 'latest.json');
+ const report = await cmdVerifyAll({
+ master: { GOOD_KEY: 'x' }, routes: { services: { GOOD_KEY: { verify: {} } } },
+ verifier: async () => ({ ok: true, status: 200 }), outputPath,
+ now: new Date('2026-08-29T05:21:00Z'),
+ });
+ assert.equal(report.status, 'PASS');
+ assert.deepEqual(JSON.parse(fs.readFileSync(outputPath, 'utf8')), report);
+});
diff --git a/verification/e2e-proof.json b/verification/e2e-proof.json
new file mode 100644
index 0000000..726c562
--- /dev/null
+++ b/verification/e2e-proof.json
@@ -0,0 +1,18 @@
+{
+ "intent": "Produce a machine-readable PASS/WARN/FAIL freshness report for configured secrets without exposing secret values.",
+ "risk_tier": "R1 isolated CLI/report code; live provider verification and scheduling were not invoked",
+ "environment": "local Node tests with injected zero-network verifier and temporary output path",
+ "timestamp": "2026-08-29T05:21:00Z",
+ "ticket": "TK-10950-add-read-only-secret-freshness-report",
+ "build_identity": "git parent b712be4 plus owned Cycle 13 diff",
+ "checks": [
+ { "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": "syntax/diff", "command": "node --check cli.js && git diff --check", "assertions": "CLI parses and diff is clean" }
+ ],
+ "negative_checks": ["provider rejection", "configured key missing from master", "key lacking a verify endpoint", "provider body containing the secret"],
+ "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"
+}
← b712be4 register Shopify full-access token routes
·
back to Secrets Manager
·
Harden secret freshness verification failures f585023 →