Commit 3cbcea5
test(protect): port engine unit suite + restructure tests/protect/ (#58)
* feat(protect): rule-driven Tier-3 — response-leak redaction + egress SSRF; array params
Brings the output-side protection into the vendored runtime, as RULES (nothing hardcoded),
via rule phases:
- new rule properties: `phase` (request|response|egress, default request) + `category`
- engine: resolve `response.status|body|header.*` and `egress.url|host|method`; new
`internal_host` match type (private/loopback/link-local/metadata ranges)
- runtime (createProtection): response phase screens the outgoing body; egress phase
(opt-in `egress:true`) wraps global fetch to screen outbound calls. Driven by default
bundles in defaults.js (overridable via responseRules/egressRules).
Response leak handling supports per-rule `action`:
- `redact` (default for the secret rules) — mask only the offending span(s) and still
serve the page; a legit response that leaks one key gets that key masked, not withheld
- `block` — withhold the whole response
Also fixes a real engine bug: array `parameter` (["get.a","post.b"]) — used pervasively in
rule_v2 — silently never matched (`parameter.indexOf('.')` on an array returns -1).
#evaluateCondition now resolves each element and ORs them.
Enforcement only in block mode; dry-run observes + serves original. Fail-open throughout.
createSupabaseGuard / createServerFnGuard unchanged. Full suite: 151 pass; build + typecheck green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(protect): port the engine unit suite + restructure tests/protect/
When the node-waf engine was vendored into src/protect/engine/, its ~180 unit tests did
NOT come along — connect only had the runtime integration test, leaving matchValue /
resolver / normalizer / mutations / adapters with no direct coverage.
Ports the engine suite from the upstream engine (adapted to vitest) and groups all
protect tests under tests/protect/:
- engine.test.ts (54) — match types incl. internal_host, evaluate, inclusive/OR,
whitelist, nested rules, ctype value-inversion, array parameters, fail-open, safeRegExp
- request.test.ts (38) — resolver: get/post/request/cookie/server/files/raw/all +
response.*/egress.* + wildcards + mutations
- normalizer.test.ts (63) — normalize pipeline + _rawBody prototype-pollution serialization
- fetch.test.ts (6) / node.test.ts (5) — the fetch + node request adapters
- middleware.test.ts (16) — the Express middleware + rule client (mocked fetch)
- runtime.test.ts (11) — createProtection integration (moved from tests/protect.test.ts):
request/response-redact/response-block/egress, dry-run/block, Supabase + server-fn guards
Full suite 333 pass; typecheck + build green. Engine source unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(protect): deep Tier-3 coverage (response, egress, options, invalid rules)
The ported suite covered the engine well but the Tier-3 protect-layer features only had
happy-path coverage. Adds tests/protect/tier3.test.ts (17):
- response redaction: every default secret type; multiple spans in one body; maskWith
(string + per-category fn); contains-literal redaction; redact-with-no-maskable-pattern
falls back to withholding; response.header.* rules
- response gating/robustness: non-text content-type skipped; oversized (content-length)
skipped; malformed rule (no rule_v2 array) skipped; invalid regex never matches / no crash
- egress: every internal range blocked via the guard (127/10/192.168/172.16/localhost/::1/
metadata) + external allowed; allowHosts exemption; onEgressBlock payload; uninstallEgress
restores global fetch
- options/phases: default dry-run; responseRules replaces defaults; rules split by phase
(a phase:response rule screens responses, NOT requests)
protect coverage 193 → 210; full suite 350 pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(protect): runtime node()/express() guards, token+cache path, egressRules override
- runtime .node() (body buffering + block/allow) and .express() guards
- rule source: token path caches the fetched bundle, then falls back to last-known-good
when the API fails; no-token/no-cache → empty request ruleset (allows)
- egressRules override replaces the default SSRF rule
Notes a pre-existing gap: runtime.node() consumes the body to screen it but doesn't
re-expose req.body downstream (follow-up on the feature branch). Full suite 355 pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>1 parent b3e9bdf commit 3cbcea5
10 files changed
Lines changed: 2323 additions & 1 deletion
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
0 commit comments