Scenario catalog / v1.2

All shipped attacks

Drop extra modules into labs/scenarios/ (or any dir via config scenarioDirs); each returns REFUSED 🛡 / EXPLOITED ⚡ / BOUNDED 🔒 / PASSED ✅. BOUNDED requires an owner+bound or it demotes to EXPLOITED - loud, per the PRD principle. PASSED grades a safe-path probe: positive evidence, never a finding (v1.2.0).

ScenarioWhat it doesFault class it targets
auth/identity-spoof-headersFires 54 spoof variants - 9 identity headers (x-is-admin, x-user-role, x-forwarded-user...) each claiming 6 privilege values - at a privileged routeany handler that reads identity straight from wire headers
auth/secret-watermarkPlants fake secrets in env, probes error paths - a leaked secret means some error string echoed ithandlers that template raw error text
auth/privilege-escalationReplays owner-b claims over owner-a writes (writer/victim pairs)routes that accept an owner field without checking the caller
authz/method-matrix-privilegedFires the privileged route on every HTTP method (GET/POST/PUT/PATCH/DELETE, bare and with bodies) - the gate must deny method-blind; a gate that trips only on the tested method is a findinggates keyed to one request method
authz/role-body-spoof-familyThe role-word field family (role, isAdmin, is_admin, admin, scope, permissions, nested user.role) fired in bodies at the privileged route must never flip a deny - identity lives in what the server derives, not in what the attacker declareshandlers that trust body-declared roles
authz/handler-alias-confusionA second route performing the same privileged action (the dev/debug leftover) must deny identically - unauthenticated AND under identity-header spoofing; an alias that honors what the primary ignores is the trapdoorleftover debug/alias routes with weaker gates
guards/hygiene-no-storeEvery 2xx write must carry store class (no-store / private / explicit immutable) - mandated by HTTP cache semanticshandlers that write without cache directives
guards/burst-wall50-request burst; measures 5xx/429/timeout and the Retry-After wallroutes exposed to sustained load
governance/succession-trust-floorA designated successor at trust L inherits only capabilities >= L - the deep-private half stays locked below the floor; a breach resolves ALLOW below the levelsuccessor caches that inherit the whole capability map
governance/succession-single-heirTwo successor declarations for one role must resolve deterministically by declared precedence - never arrival order or randomnessrole maps keyed by insertion order
governance/succession-revocation-carryRevoking an heir propagates into a session opened AFTER the revocation, including any cached capability map - a still-allowed ask is the carry gapcapability caches consulted without checking revocation
governance/escrow-two-key-releaseAn escrowed operation releases only under TWO independent approvals - one principal self-signing twice must never releaseapproval flows that count signatures, not principals
governance/escrow-expiry-is-fail-closedAn escrow past its TTL releases nothing and answers typed - silent past-TTL release is the classic classexpiry checks that only run on read paths
governance/escrow-refusal-is-typedDenied releases answer with a typed reason naming the blocker - raw 5xx or blank errors are findingsrefusal paths that fault instead of refusing
governance/grant-conflict-deterministicTwo conflicting grants (allow/deny) resolve by declared precedence regardless of insertion ordergrant tables without a precedence rule
governance/capability-no-sidewaysA grant scoped to brain/namespace A must not authorize writes into B even when the objects share a parentscope checks that stop at the parent prefix
governance/lock-ttl-honestyA lock past TTL behaves exactly as the app documents it (declare the documented ttlMode) - the disagreement between claim and behavior is the findinglocks whose advertised TTL is aspiration
governance/lock-owner-fencingA stale holder writing through an expired fence token is refused loudly - a deleted-token write that succeeds is the fencing gapfence tokens checked only at acquire time
input/proto-pollution-bodyFeeds __proto__/constructor keys through JSON bodies (JSON.stringify silently DROPS them - hostileSerialize is built to keep them on the wire)recursive-merge handlers that spread untrusted bodies
input/never-fail-mutations64 hostile bodies (huge ids, nested types, unicode tricks) - any failure mode past a 4xx is a findingmutation routes without shaped rejection
input/oversized-field1MB fields, deep nesting, giant arrayshandlers without a body limit
memory/drift-silent-overwriteTwo writers update the same fact without revision bumps - the read must surface the conflict or keep both provenance lines, never a silent blendnote stores without revision discipline
memory/drift-stale-poisons-freshA fact written by a retired writer must not outrank the current writer for the same key - staleness beats recency = the poisonread paths without writer-identity ranking
memory/drift-contradiction-carryA note whose body contradicts its own declared summary must be flagged by the read path - carried silently is the honesty gapread layers that trust the header blindly
memory/hallucinated-fact-probeA claim referencing a note id that never existed (deterministic ghost key) must be detectable on read-backnote stores that accept any reference
memory/citation-driftA note citing a deleted anchor is a dangling claim - the read path must refuse it loudly, never serve it resolvedcitation graphs without liveness checks
memory/phantom-scopeA private-scoped note must not inherit a wider scope when its content claims public provenance - scope lives in the field, not in prosemerges that let content widen scope
memory/identity-merge-collisionTwo agent identities that wrote the same fact keep BOTH provenance lines through the merge - one vanished author is silent lossmerges that overwrite attribution
memory/resolution-order-stabilityResolving N conflicting facts must be a deterministic function of declared inputs - same inputs twice, same winner, twiceresolvers with side-effect timing
memory/delete-is-loudPruning deletes leave traceable residue (tombstone/prune traces) - a forget-claim with no tombstone erases historyprunes that drop rows outright
proto-hygiene/head-get-paritySweeps every declared route: HEAD must be the cheap mirror of GET - no throw, no status drift where GET answered 2xx (only an honest 405 is excusable); HEAD body bytes are an INFO note, not a findinghandlers that never considered HEAD
proto-hygiene/request-shape-floodBare geometry attacks before payloads: duplicate query keys, malformed separators, control bytes, dot-segment paths, header casings - no 5xx, no throw, and answers must not change when only the spelling didparam handlers that dereference what the URL never carried
recursion/deep-nest-stack-overflowA JSON structure nested many thousands deep meets the handler - V8 JSON.parse survives, so any recursive WALK over it (merges, flatteners, normalizers) is where the call stack detonates; an uncaught RangeError is a finding, a depth-capped 4xx refusal holdsrecursive JSON walkers without a depth guard
recursion/redos-catastrophic-patternValidation regexes behind attacker-controlled strings must backtrack linearly - evil/benign pairs differing only in their last character self-control the timing ratio on the SAME handler, so no host clock decides (the library's one timing-relative probe by design)nested-quantifier / alternation patterns on unanchored input
upload/filename-traversal-familyAttacker-shaped filenames (traversal, percent-dots, null splits, double extensions, dotfiles) through multipart POST - the verdict reads the STORE; any derived key that escapes the safe-name grammar is a findingstorage code keyed by the raw client filename
upload/mime-polyglot-familyContent types that lie - an SVG with a script and a text/html document are canonical XSS carriers; stored verbatim is a finding, refused with an honest 4xx holdshandlers without a content-type allow-list
race/double-submit-idempotencyN concurrent POSTs with the SAME idempotency key must land exactly one side effect - two acknowledged charges or an uncaught same-key throw (a storage wall the wire never shaped) is the double-charge class; one row + shaped 409s holdsread-then-insert with an await gap (TOCTOU)
race/concurrent-write-lost-updateTwo concurrent field-split writers must both survive: after both land, every submitted field is present in the store - a record holding only the last writer (or a writer lost to an uncaught conflict) is the silent stompwhole-record writes that replace what a concurrent writer just stored
state/enumeration-oracleArms mustDenyIds, probes each - 200s (or content ripples) mean foreign rows leak by guessable idGET-by-id without ownership checks
state/id-collision-aliasTwo owners claim the same id; the env snapshot diff decides - owner-a's row silently overwritten in place is a findingwrite routes without unique constraints
volume/credential-stuffing60 staged credential attempts across the routeunbounded auth surfaces
volume/storage-fill30 rounds of 8KB unique writes; tracks op cost AND storage growth per roundkv-writing routes without a fill budget
volume/cost-amplification40 identical replays; maxWall sets the accepted ceiling - under it, REFUSED with the measured ops/requesthandlers doing table scans per request
taxonomy/abuse-not-5xx10 abuse shapes (garbage/truncated JSON, wrong or absent content types, oversized canary strings) - every one must land as an honest 4xx; a 5xx or an uncaught throw is a findingmutation routes with no shaped sad path
taxonomy/errors-stay-impersonalSad-path bodies must not echo attacker canaries (values or field names) or leak stack frames (node:internal, at fn(...), file:line:col)error handlers that template raw input or debug text
session/cookie-hygieneThe login transition must mint a Set-Cookie carrying Secure, HttpOnly, and a SameSite policy - any missing flag is a finding; a cookie-less login honestly skips (bearer-token auth is out of scope by design)handlers that mint session cookies without the full flag set
session/session-fixationTwo logins must mint different session ids, and a forged canary sid must never authenticate - a shared sid across the privilege transition is the fixation classiclogins that reissue a static sid or honor unknown cookies
session/logout-invalidatesA logged-out sid must be dead on arrival: me() with the pre-logout cookie must deny (4xx class) - still-200 is a findinglogouts that clear the client cookie but not the server session
session/token-replay-denyFour logins (account A, B, A again, C) must mint pairwise-distinct sids and the A-again re-login must not resurrect its original sid - a reused sid makes every expired-but-known token replayable foreversessions that recycle or re-issue token values

Shipped packs ride the same ledger

Beyond the 49 library attacks above, eight public packs ship - 36 pack attacks total. Seven of them ride the npm package under packs/public/; the eighth, crashtest-site, stays repo-bound. Every pack also carries a self-audit fixture (packs/public/<pack>/test/fixtures/run.mjs) that pins each pack's documented shape - discovered and run 8/8 by test/pack-fixtures.test.js. Same verdicts, same ledger, still zero sockets.

PackAttacksHow it armsSelf-audit fixtureOracle teeth
wp-money-shapes3moduleCtx['wp-money-shapes'] - declare the target's payment vocabularyrun.mjstest/wp-money-shapes.test.js
wp-roles3moduleCtx['wp-roles'] - declare the gated admin-post action + identity seedsrun.mjstest/wp-roles.test.js
wp-stock-smoke (module stockwp)4any WordPress root - degrades to honest skips off-targetrun.mjstest/wp-roles.test.js
cve-feed2always-on - grades against the committed offline KEV/NVD snapshotrun.mjstest/cve-feed.test.js
qs-shapes2any route whose parser expands bracket keysrun.mjstest/qs-shapes.test.js
vite-dev-shapes2any route serving vite dev-file pathsrun.mjstest/vite-shapes.test.js
next-middleware-shapes2middleware-gated routesrun.mjstest/next-mw-shapes.test.js
crashtest-site18repo-bound self-check (not in the npm tarball)run.mjstest/site-pack.test.js
Pack pins: what each oracle is held to

Every pack's oracles are graded by their own teeth: wp-money-shapes's three verdicts plus the envelope-shape law (a "kind: post" envelope can never ride the wire) and an armed stub-WP grade are pinned in test/wp-money-shapes.test.js and test/wp-roles.test.js (whose stock smoke exerciseswp-stock-smoke against the stub WP root); cve-feed's snapshot shape, its 27-row budget and its token-boundary grammar are pinned in test/cve-feed.test.js and test/cve-feed-roundtrip.test.js; qs-shapes, vite-dev-shapes and next-middleware-shapes each carry differential-oracle teeth in their own test file, and the self-site lane is graded by test/site-pack.test.js. The tarball-shape pins (what ships, what stays repo-bound) live in test/packaging.test.js.

Field report: the target we trust least is our own code

This repo dogfoods itself in CI: the committed ledger holds 49 REFUSED findings on our own hardened demo app. The self-site run paid for one real bug: the shipped version endpoint once answered a malformed registry payload with 200 and no version key - found, fixed, and pinned by the version-grammar probe. Plus the session probes: a cookie-less site must mint ZERO cookies, and a forged cookie is never adopted.That is the product working: your handlers - and ours - paying for their own bugs with hardening receipts.