← back to Secrets Manager

test/verify-all.test.js

120 lines

'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 { boundedVerify, buildVerifyAllReport, cmdVerifyAll, main } = 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');
  fs.mkdirSync(path.dirname(outputPath), { recursive: true });
  fs.writeFileSync(outputPath, '{}\n', { mode: 0o644 });
  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);
  assert.equal(fs.statSync(outputPath).mode & 0o777, 0o600);
});

test('timeouts, throws, and malformed results fail safely while later checks continue', async () => {
  const calls = [];
  const allRoutes = { services: Object.fromEntries(['A_TIMEOUT', 'B_THROW', 'C_UNDEFINED', 'D_MALFORMED', 'E_PASS']
    .map((key) => [key, { verify: {} }])) };
  const allMaster = Object.fromEntries(Object.keys(allRoutes.services).map((key) => [key, `value-${key}`]));
  const report = await buildVerifyAllReport({
    master: allMaster, routes: allRoutes, timeoutMs: 5,
    verifier: async (key) => {
      calls.push(key);
      if (key === 'A_TIMEOUT') return new Promise(() => {});
      if (key === 'B_THROW') throw new Error('secret value-B_THROW');
      if (key === 'C_UNDEFINED') return undefined;
      if (key === 'D_MALFORMED') return { ok: 'yes', body: 'secret malformed body' };
      return { ok: true, status: 200, body: 'secret body' };
    },
  });
  assert.deepEqual(calls, ['A_TIMEOUT', 'B_THROW', 'C_UNDEFINED', 'D_MALFORMED', 'E_PASS']);
  assert.deepEqual(report.checks.map((check) => check.reason || 'pass'),
    ['timeout', 'verifier-error', 'malformed-result', 'malformed-result', 'pass']);
  assert.equal(report.status, 'FAIL');
  assert.doesNotMatch(JSON.stringify(report), /value-|secret body|B_THROW.*secret/);
});

test('blank secrets count as missing and CLI exit code fails only aggregate FAIL', async (t) => {
  const base = fs.mkdtempSync(path.join(os.tmpdir(), 'secret-exit-'));
  t.after(() => fs.rmSync(base, { recursive: true, force: true }));
  const run = (name, masterValue, route, verifier) => main(['verify-all'], { verifyAllOptions: {
    master: { TEST_KEY: masterValue }, routes: { services: { TEST_KEY: route } }, verifier,
    outputPath: path.join(base, `${name}.json`), timeoutMs: 5,
  } });
  assert.equal(await run('fail', '   ', { verify: {} }, async () => ({ ok: true })), 1);
  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 });
  }
});