Vealth · Security program · Guard negative-test checklist

Your guard fires on the bad case. Does it know the good one?

A regex, a linter rule, a schema check, any automated guard that exists because something bad happened once: almost every one of them also carries a documented exception, a case it is supposed to let through. Most test suites prove the guard catches the bad case. Far fewer prove it correctly spares the good one, and a guard nobody proved spares its own exception is a guard nobody has actually tested. This page names the four-step discipline we now hold every guard in our own security program to, with the real, dated examples where it caught something. It is written so another AI-built codebase can run the same four steps against its own guards, not just read about ours.

The checklist

Run this against any automated check in your own repository that exists to catch a documented edge case or exception. It applies to a secret scanner, a style linter, a schema validator, a deploy-scope guard, anything with an "except when" written into a comment.

  1. 1. Find the exception, then find or write the test for it. For every guard that exists to catch something, list every documented "this should NOT trigger" case in its comments or its spec. For each one, confirm a test asserts the guard stays quiet on that exact case, not only that it fires on the violation. A guard with only positive tests, or no tests at all, has an unproven exception, which in practice means an unproven guard: it is exactly as likely to be silently wrong about the case that matters as the case that does not.
  2. 2. Prove the new test is not vacuous. A negative test that passes for the wrong reason is worse than no test, because it reads as coverage. Temporarily break the exact line of logic the test claims to cover (invert a condition, force a boolean, delete the exemption), rerun the suite, and confirm that specific test and only that one goes red. Restore the logic and confirm the suite is green again. If nothing goes red, or a different test goes red instead, the new test was not exercising the exception at all.
  3. 3. When a guard's scope widens, re-derive every exemption's scope explicitly. A fix that makes a guard catch more (a wider name pattern, a broader file glob, a longer scan window) can silently change what an existing exemption now covers, because the exemption was scoped against the old, narrower match. Do not assume the exemption still composes safely. Write out, in the open, exactly what the widened exemption now spares, and add a test proving it still refuses the case it was never meant to spare.
  4. 4. Get a second, independent pass, especially on anything that just widened. A careful first draft that says "clean" is a claim, not a fact. The examples below are all real, dated cases where a second look, done directly (rerunning the exact fail-then-restore proof, not re-reading the prose) found a real problem in work that had already been written up as finished.

Worked examples from this repo

All four steps, run today, against the guards behind our own security program (scripts/check-security-posture.ts, scripts/check-site-copy-em-dash.ts, scripts/check-nginx-static-prefix-collisions.ts, scripts/safety-card/check-safety-card.ts). Every claim below cites the file and test, or the receipt that recorded the fail-then-restore run.

Step 1: which guards had the exception proven, which did not

GuardBefore this passAfter
check-nginx-static-prefix-collisions.tsAlready had 3 negative testsNo change needed, already properly covered
check-safety-card.ts (no-verdict rule)Already had negative tests, added after an earlier real bug (see Step 4)No change needed, already properly covered
check-security-posture.ts (15 checks)Zero tests of any kind, not just missing the negative case10 pure functions exported, 56 tests added, 10 more after a widen, 2 more after a regression the widen introduced (68 total)
check-site-copy-em-dash.tsZero tests of any kind4 functions exported, 19 tests added

Source: docs/backlog/generated/cybersecurity-guard-negative-test-audit-receipt-2026-08-11.md. Check for yourself: npx vitest run tests/check-security-posture.test.ts tests/check-site-copy-em-dash.test.ts tests/security-program.test.ts tests/safety-card-spec.test.ts, 87 tests passing across the two newly-covered guards alone.

Step 2: two tests that passed for the wrong reason, caught by breaking the logic

The fail-then-restore discipline (break the exemption, confirm exactly the matching test goes red, restore, confirm green again) found two of its own fixtures were vacuous before this work was recorded as done:

Source: docs/backlog/generated/cybersecurity-guard-negative-test-audit-receipt-2026-08-11.md, "Fail → pass proof" section, rounds 3 and B.

Step 3: widening a guard, then re-deriving the exemption it broke

A secret scanner's name-matching regex missed several real secret-shaped env var names (GITHUB_TOKEN, JWT_SECRET, WEBHOOK_SECRET, SLACK_TOKEN, SESSION_SECRET) because its word-boundary check never fired inside an underscore-joined name. Widening the name match to catch these also had to add a new value-side exemption, because the wider match now also caught real, non-secret env vars that hold public blockchain addresses under names like NET_TOKEN_ADDRESS. The first version of that exemption was scoped too broadly: it was applied after a single combined name check, so it silently spared an address-shaped value under an EXPLICIT sensitive name too (STRIPE_API_KEY=0x<40hex>, DB_PASSWORD=0x<40hex> would have gone from flagged to spared), a real weakening versus the guard's own prior behavior. Caught during independent review, fixed by splitting the name match into two regexes (an explicit-suffix branch and a bare-fallback branch) and only allowing the address exemption on the fallback branch, with two new regression tests proving the fix by the same fail-then-restore discipline.

Source: docs/backlog/generated/secret-scanner-widening-receipt-2026-08-11.md, "Correction, added during independent review before this landed," item 1.

Step 4: three times a second, independent look found something real, in work already written up as clean

  1. The safety card's own no-verdict rule, two attempts. The first fix dropped a regex flag to require a real capital letter, with a code comment claiming this stopped a false positive on a sentence-initial lowercase word. The comment was wrong: English capitalizes the first word of every sentence regardless of content, so the documented exception ("Reading is safe and free.") still matched, and so did an unrelated third party ("The Safe Multisig is audited by a third party."). The real fix does not guess a brand name from capitalization at all; it cross-references the matched text against the card's own declared operator.name and domain. Source: docs/backlog/generated/cybersecurity-program-codex-handoff-2026-08-11.md, "The regex lesson."
  2. A test fixture inside the negative-test audit itself. A test meant to prove a proximity-window check used the fixture text "private key rotation policy," but the guard's own label regex only matches a hyphen or underscore join (private-key or private_key), never a space. So the test passed for a reason unrelated to the window it claimed to prove, because the label never matched at all in that fixture. Caught by independent review re-running the exact break-and-restore proof rather than re-reading the write-up, fixed by renaming the fixture to the hyphenated form and re-confirming the specific mutation now fails only that test. Source: docs/backlog/generated/cybersecurity-guard-negative-test-audit-receipt-2026-08-11.md, the paragraph beginning "A third instance."
  3. The address-exemption regression above. Step 3's real weakening (an exemption that would have spared an explicitly-named secret when its value happened to be 40 hex characters) was found the same way: an independent pass re-ran the reasoning against the actual code rather than accepting the receipt's own claim of "safe," and found the claim did not hold. Source: docs/backlog/generated/secret-scanner-widening-receipt-2026-08-11.md.

None of the three were caught by a first read of the write-up. All three were caught by re-running the guard's own logic directly, which is the entire argument for step 4 belonging in this checklist rather than being optional.

Run this against your own repo

Nothing above is specific to TypeScript, to regex guards, or to this codebase. The four steps translate directly:

  1. List your guards with a documented exception. Grep your own CI checks, linters, and validators for a comment that says "except," "not a violation when," "spared," "allowed," or similar. Each hit is a candidate.
  2. For each, find the test. If none exists, write one: assert the guard stays quiet on the exact documented exception case, phrased as close to the comment's own wording as you can get.
  3. Break it, run it, restore it. Comment out or invert the exemption's condition, rerun just that test file, and read the output. Exactly the test(s) naming that exception should fail, and nothing else. If the wrong thing happens, the test is not proving what it claims to.
  4. On any guard you widen, ask what any existing exemption now spares. Write the answer down before you trust it, and add a test for the case the exemption must still refuse.
  5. Have someone, or something, independent re-run step 3 rather than re-read your prose. A summary that says "verified" is not the verification.

What this does not prove

This discipline proves a guard's documented exception is exercised by a test, and that the test is not vacuous at the moment it was written. It does not prove the guard's positive detection is complete (our own audit found and left open a real, wider gap in a secret scanner's detection side, recorded honestly rather than quietly closed: docs/backlog/generated/cybersecurity-guard-negative-test-audit-receipt-2026-08-11.md, "Incidental finding, not fixed"). It does not replace a second reviewer with a checklist: step 4's three examples were all found by a person or process re-running the logic, not by following these four steps alone. And it says nothing about guards nobody has documented an exception for in the first place, which is a different, earlier problem this checklist does not reach.

The full security program · Checked against the Pro-Human AI Declaration · Run the safety checks

EcoWealth Corporation · [email protected] Home Work Receipts About