|
| 1 | +# Pentest suite |
| 2 | + |
| 3 | +Deterministic penetration tests for the Anchr worker API. All tests run |
| 4 | +against `localhost` only. Never connects to external services. |
| 5 | + |
| 6 | +## What this suite IS |
| 7 | + |
| 8 | +A **smoke-grade defense baseline.** Catches: |
| 9 | + |
| 10 | +- Crashes (5xx) on malformed, oversized, or adversarial input |
| 11 | +- Unauthenticated access to write endpoints |
| 12 | +- Trivial rate-limit bypass via client-supplied headers |
| 13 | +- Preimage leakage on the unverified-result path |
| 14 | +- Prototype pollution reaching the test-process global |
| 15 | +- JSON-response content-type on routes that should never return HTML |
| 16 | + |
| 17 | +## What this suite is NOT |
| 18 | + |
| 19 | +A proof that defenses work. Most assertions use the shape `<500` — that |
| 20 | +only proves the server didn't crash. A malicious response that returns |
| 21 | +200 with leaked data would pass a `<500` assertion. |
| 22 | + |
| 23 | +Specifically, the following attacks are **detected only if they crash |
| 24 | +the server**, not if they succeed silently: |
| 25 | + |
| 26 | +| Attack class | Current assertion | What a real proof would need | |
| 27 | +|---|---|---| |
| 28 | +| SSRF | response body does not echo `"meta-data"` | listener bound to `169.254.169.254`, assert zero inbound connections during the test run | |
| 29 | +| Prototype pollution | test-process `{}.isAdmin` is undefined | server-process introspection endpoint that reveals prototype state (test-process check doesn't see server pollution) | |
| 30 | +| Store pollution / TTL cleanup | `expire-*` entries missing from `GET /queries` | admin endpoint exposing raw store size, to distinguish "cleaned up" from "filtered at read" | |
| 31 | +| Fuzz payloads (18 variants) | `<500` | per-payload assertion on expected rejection code (400 vs 413 vs 422) | |
| 32 | +| ReDoS in regex conditions | `<500` on creation | end-to-end verification path exercised with a time budget | |
| 33 | + |
| 34 | +## Why ship a smoke-grade suite at all |
| 35 | + |
| 36 | +Crash-free handling of adversarial input is a real property. Catching a |
| 37 | +regression that makes `POST /queries` panic on a 10 MB body is |
| 38 | +cheaper as a test than as a prod incident. The tests also guard against |
| 39 | +silent removal of the rate limiter, the write-auth middleware, or the |
| 40 | +preimage-release guard — all things an innocent refactor could break. |
| 41 | + |
| 42 | +Treat green checks as "no crash, no obvious regression." Not as |
| 43 | +"hardened." |
| 44 | + |
| 45 | +## Running locally |
| 46 | + |
| 47 | +```bash |
| 48 | +# Starts a pentest server on port 8091 with HTTP_API_KEYS=pentest-key-001 |
| 49 | +deno task test:all # includes pentest phase |
| 50 | + |
| 51 | +# Or run only the pentest suite against an already-running server: |
| 52 | +PENTEST_APP_URL=http://localhost:8091 HTTP_API_KEYS=pentest-key-001 \ |
| 53 | + deno task test:pentest |
| 54 | +``` |
| 55 | + |
| 56 | +## Adding new tests |
| 57 | + |
| 58 | +Follow the existing file conventions: |
| 59 | + |
| 60 | +- One file per attack class (`auth-bypass.test.ts`, `ssrf.test.ts`, ...) |
| 61 | +- Use `helpers.ts` for `APP_BASE`, `API_KEY`, `authedHeaders` |
| 62 | +- Always `await res.body?.cancel()` after `fetch()` to avoid leaks |
| 63 | +- Prefer specific assertions over `<500`. `<500` is the floor, not the goal. |
| 64 | + |
| 65 | +## Known coverage gaps |
| 66 | + |
| 67 | +Not currently exercised: |
| 68 | + |
| 69 | +- `POST /queries/:id/upload` (file size, content-type smuggling, traversal on filename) |
| 70 | +- `POST /queries/:id/quotes` (Cashu quote fuzz) |
| 71 | +- `POST /marketplace/data/:id` (payment middleware: malformed token, replay, zero/negative amount) |
| 72 | +- `POST /marketplace/listings/:id/announce` (relay-publish input fuzz) |
| 73 | +- FROST authz: can signer_index N submit commitments for signer_index M? |
| 74 | + |
| 75 | +These are higher marginal value than deepening assertions on |
| 76 | +already-covered endpoints. |
0 commit comments