feat(protect): rule-driven Tier-3 (response redaction + egress SSRF) + array-param fix#57
Merged
Merged
Conversation
…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>
|
Well-structured feature adding response redaction and egress SSRF with array-param support. 🎯 Quality: 100% Elite · 📦 Size: Large — consider splitting if possible 🛡️ Standards: no pre-flight fit check ran for this change — wire 📈 This month: Your 23rd PR — above team average · Averaging Excellent |
Contributor
Author
|
/review |
node() consumes the request stream to screen it, then called next() without setting req.body — so a downstream handler (or a body-parser mounted after the guard) saw an empty stream. Now sets req.body to the parsed body on allow (only if not already set by an upstream parser), matching the engine's createNodeMiddleware. Adds node-body tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
patchstackdave
added a commit
that referenced
this pull request
Jul 14, 2026
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings the output-side protection into the vendored
@patchstack/connect/protectruntime, fully rule-driven (nothing hardcoded) via rule phases.New capabilities
phase(request(default) /response/egress) +category.src/protect/engine): resolvesresponse.status|body|header.*andegress.url|host|method; newinternal_hostmatch type (private / loopback / link-local / cloud-metadata ranges).createProtection): the fetch path now screens the response; opt-inegress: truewraps globalfetchto screen the app's outbound calls (SSRF). Both driven by default bundles indefaults.js, overridable viaresponseRules/egressRules.Response leak handling — per-rule
actionredact(default for the secret rules) — mask only the offending span(s) and still serve the page. A legitimate response that accidentally leaks one key gets that key masked ([REDACTED]), not the whole response withheld. Mask viamaskWith.block— withhold the whole response.Enforcement only in
blockmode;dry-runobserves (onDetectgetsphase/category) and serves the original. Fail-open throughout.createSupabaseGuard/createServerFnGuardunchanged.Bug fix (found by the test-bed)
Array
parameter(["get.a","post.b"]) — used pervasively inrule_v2— silently never matched:resolve()didparameter.indexOf('.'), which on an array returns-1.#evaluateConditionnow resolves each element and ORs them. Regression covered.Tests / build
tests/protect.test.ts+6 (array params, response redact/block, egress block/dry-run). Full suite 151 pass,typecheckclean,buildbundlesdefaults.js/egress.jsintodist/protect.js. No changes to the install prompt /AGENT-INSTALL/guidesurface (no field-test impact).🤖 Generated with Claude Code