Skip to content

protect: validate delivered rule bundles; bound patterns; harden the telemetry origin - #119

Merged
patchstackdave merged 2 commits into
mainfrom
hardening/rule-bundle-validation
Aug 13, 2026
Merged

protect: validate delivered rule bundles; bound patterns; harden the telemetry origin#119
patchstackdave merged 2 commits into
mainfrom
hardening/rule-bundle-validation

Conversation

@patchstackdave

Copy link
Copy Markdown
Contributor

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:

Bound Limit
rules / whitelists per bundle 5000 / 2000
conditions per rule 250
condition nesting depth 12
regex pattern length 1000
match-value length 8192

plus a known phase (request/response/egress), a known action, a non-empty rule_v2, and a positive max_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

safeRegExp now 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_BASE is the origin the site api_key is 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-dns tests bind a loopback listener, which some sandboxed/CI environments refuse with EPERM — 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.

…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>
@coderbuds

coderbuds Bot commented Aug 13, 2026

Copy link
Copy Markdown

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

See how your team is trending →

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

Copy link
Copy Markdown
Contributor Author

/review

@patchstackdave
patchstackdave merged commit 19428c7 into main Aug 13, 2026
5 checks passed
@patchstackdave
patchstackdave deleted the hardening/rule-bundle-validation branch August 13, 2026 11:55
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