Skip to content

test(protect): port engine unit suite + restructure tests/protect/#58

Merged
patchstackdave merged 5 commits into
mainfrom
test/protect-suite-coverage
Jul 14, 2026
Merged

test(protect): port engine unit suite + restructure tests/protect/#58
patchstackdave merged 5 commits into
mainfrom
test/protect-suite-coverage

Conversation

@patchstackdave

Copy link
Copy Markdown
Contributor

⚠️ Stacked on #57 (feat/protect-tier3-response-egress). Base retargets to main when #57 merges.

When the node-waf engine was vendored into src/protect/engine/, its ~180 unit tests didn't come along — connect only had the runtime integration test, so matchValue / resolver / normalizer / mutations / adapters had no direct coverage. This ports them and groups all protect tests under tests/protect/.

file tests covers
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 / node.test.ts 6 / 5 the fetch + node request adapters
middleware.test.ts 16 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

Adapted from the upstream engine tests to vitest (paths + describe/it import); node:assert kept. Engine source unchanged. Full suite 333 pass, typecheck + build green.

🤖 Generated with Claude Code

patchstackdave and others added 2 commits July 14, 2026 11:40
…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>
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>
@coderbuds

coderbuds Bot commented Jul 14, 2026

Copy link
Copy Markdown

Comprehensive and well-structured test suite covering engine and middleware modules.

🎯 Quality: 88% Excellent · 📦 Size: Oversized — strongly consider breaking this down

🛡️ Standards: no pre-flight fit check ran for this change — wire assess-change-fit into your coding agents to catch size before opening.

📈 This month: Your 23rd PR — above team average · Averaging Good

See how your team is trending →

…id 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>
@patchstackdave

Copy link
Copy Markdown
Contributor Author

/review

…essRules 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>
Base automatically changed from feat/protect-tier3-response-egress to main July 14, 2026 10:13
Resolves the conflict from #57 (squash-merged): #57 modified tests/protect.test.ts while
this branch renamed it to tests/protect/runtime.test.ts — accepted the rename (its content
is preserved there). Picks up #57's node() req.body fix + node-body.test.ts from main, and
flips the now-obsolete "not re-exposed" note in runtime-guards to a real assertion.

Full suite 358 pass; typecheck green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@patchstackdave patchstackdave merged commit 3cbcea5 into main Jul 14, 2026
4 checks passed
@patchstackdave patchstackdave deleted the test/protect-suite-coverage branch July 14, 2026 10:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants