protect: validate delivered rule bundles; bound patterns; harden the telemetry origin - #119
Merged
Merged
Conversation
…telemetry origin Defense-in-depth follow-ups from an external review. Delivered rules are policy fetched over the network and executed on every request, so an upstream problem (schema drift, a corpus mistake, a compromised response) could hand the engine unbounded work or silently unenforceable rules. - NEW rules/validate.js + wired into normalizeBundle, the single chokepoint every rule path (live fetch, cache, bundled fallback) already funnels through. Bounds rule count, whitelist count, conditions per rule, nesting depth, regex length and match-value length, and requires a known phase/action. A failing rule is DROPPED WITH A REPORTED REASON (`onRuleRejected`, else a one-line warning) — never kept while protecting nothing. Whitelists are validated too: a malformed whitelist suppresses real rules, so it's a protection risk, not just a detection one. - safeRegExp now refuses a pattern over 1000 chars as a backstop for caller-supplied bundles that never passed through the validator. (A complete ReDoS analysis still isn't possible statically — a bounded/off-loop matcher remains the real fix.) - resolveApiBase: PATCHSTACK_API_BASE is the origin the site api_key is exchanged against, so an injected value was a credential-exfiltration path. It must now be https (localhost permitted for local testing); anything else is refused with a warning and falls back to the default origin. - egress-dns tests bind a loopback listener, which some sandboxed/CI environments refuse (EPERM) — they now skip cleanly instead of failing/hanging. The suite is green both with and without a binding-capable environment. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Comprehensive rule validation and origin hardening greatly improves security robustness. 🎯 Quality: 100% Elite · 📦 Size: Large — consider splitting if possible 📈 This month: Your 53rd PR — above team average · Averaging Excellent |
The previous commit hardened only the telemetry API base, but the rule endpoint is the more security-relevant of the two: rules are policy the engine executes on every request, so an injected PATCHSTACK_PULSE_RULES_URL / PATCHSTACK_WAF_API_URL (or a baseUrl passed by a compromised config) could remove protection wholesale by serving an empty bundle, or serve a deliberately expensive ruleset. Both rule clients now accept a non-default base only when it is https — localhost is permitted so local development, the Pulse-chain demo and tests keep working — otherwise they warn once and fall back to the default origin. Extracted the check into src/protect/safe-origin.js so the rule clients and the telemetry reporter share one policy instead of duplicating it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
Author
|
/review |
devlob
approved these changes
Aug 13, 2026
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.
Defense-in-depth follow-ups from an external review. Delivered rules are policy fetched over the network and executed on every request, so an upstream problem (schema drift, a corpus mistake, a compromised or unsafe endpoint) could hand the engine unbounded work — or leave rules that look loaded but protect nothing.
Rule-bundle validation (new
rules/validate.js)Wired into
normalizeBundle, the single chokepoint every rule path already funnels through (live fetch, last-known-good cache, bundled fallback). It bounds what the engine will walk and requires a sane shape:plus a known
phase(request/response/egress), a knownaction, a non-emptyrule_v2, and a positivemax_bytes.Rejection is explicit, not silent: a failing rule is dropped with a reason, reported via
onRuleRejected(or a single warning line). An unenforceable rule must never appear enforced. Whitelists are validated too — a malformed whitelist suppresses real rules, so it's a protection risk, not just a detection one.Pattern-length backstop
safeRegExpnow refuses a pattern over 1000 chars, covering caller-supplied bundles that never passed the validator. Being honest about the limit: a complete static ReDoS analysis isn't possible — a bounded/off-loop matcher remains the real fix and stays on the backlog.Telemetry origin
PATCHSTACK_API_BASEis the origin the siteapi_keyis exchanged against, so an injected value (CI/env) was a credential-exfiltration path. It must now be https (localhost allowed for local testing); anything else is refused with a warning and falls back to the default origin.Test hygiene
egress-dnstests bind a loopback listener, which some sandboxed/CI environments refuse withEPERM— they now skip cleanly instead of failing and timing out. The suite is green both in a binding-capable environment and in a restricted one (724 passing either way); typecheck + build clean.Notably, validation rejected nothing in the existing corpus/demo rule sets — the caps sit far above real rules.