Every request is an in-process WHATWG Request
Attack code, not people.
No sockets. Ever.
Crashtest drives a seeded adversarial suite against your real handlers — routing, guards, state, volume — through a fake transport. The output is a hardening ledger your CI fails on new EXPLOITED findings or REFUSED→EXPLOITED regressions. Same commit, same findings, forever.
# 60 seconds — point it at your handlers npx crashtest init # edit crashtest.config.js — routes list your handlers, env the fake d1/kv/secrets npx crashtest run # → labs/hardening.md · labs/crashledger.json · exit 0 = hold, exit 1 = a finding regressed 🛡 EFF. auth/secret-watermark 🛡 guards/burst-wall 🛡 state/enumeration-oracle ⚡ guards/hygiene-no-store (2xx write with no Cache-Control — real findings look like this)
The pipeline
How a run turns handlers into evidence.
Request; the fake d1/kv/env mirrors your backend. Nothing leaves the process.Why it exists
LLMs write programs fast. Programs that route, store, and spend budget need their own fire drill.
§Fail at cause, loudly
No swallowed errors, no phantom 200s — a handler that never ends its response is surfaced as a finding, not papered over.
§Deterministic attacks
Seeded RNG from commit to commit. A scratch-run today reproduces bit-for-bit in CI tomorrow.
§Cost-aware from day one
Every request is accounted in ops — amplification attacks get named per-request and bounded, not vibes.
§Fits CI, not just dev
Zero runtime deps, plain Node, one command. Plus a composite GitHub Action to gate other repos.