Skip to content

[ENG-3567] protect: honour a per-rule dry-run, so a generated rule can detect while others block - #135

Merged
patchstackdave merged 2 commits into
mainfrom
davejong/eng-3567-honour-per-rule-enforcement
Aug 14, 2026
Merged

[ENG-3567] protect: honour a per-rule dry-run, so a generated rule can detect while others block#135
patchstackdave merged 2 commits into
mainfrom
davejong/eng-3567-honour-per-rule-enforcement

Conversation

@patchstackdave

Copy link
Copy Markdown
Contributor

The client half of ENG-3567. The server half (per-rule enforcement on generated rules, plus a promotion guard) is on the saas stack.

Enforcement was per-site: the whole bundle blocked or the whole bundle only detected. That left no safe way to serve an auto-generated rule — its coordinate comes from best-effort static analysis, so it should detect until a probe or a human justifies it, but on a protected site it inherited block immediately, and the only alternative was dropping the site's real protection to dry-run as well.

hand-authored rule (no field)  →  follows the site, exactly as before
generated rule (dry-run)       →  detected, reported, NOT blocked, even where the site blocks

onDetect now reports the mode the detection was actually handled under rather than the site's, because a consumer counting blocks would otherwise over-report — and a dry-run rule not blocking is the whole point.

Two compatibility properties, both tested

  • A rule with no enforcement behaves identically to today, since an older server never sends the field.
  • An unrecognised value follows the site rather than reading as "do not block". For a protection control the conservative reading of an unknown value is to keep enforcing — treating it as opt-out would turn a future field name into a silent bypass.

The test worth reading is the third: both rules in one bundle, the authored one returning 403 and the generated one 200, from the same pair of requests.

Rules pass through validateBundle by reference, so the field survives validation untouched — no change needed there.

933 tests, typecheck clean.

…ile others block

Enforcement was per-site: the whole bundle blocked or the whole bundle only detected. That left no way to
serve an auto-generated rule safely — its coordinate comes from best-effort static analysis, so it should
detect until a probe or a human justifies it, but on a protected site it inherited `block` immediately and
the only alternative was to drop the site's real protection to dry-run too.

A rule may now carry `enforcement: 'dry-run'`, which wins over block mode for that rule alone:

    hand-authored rule (no field)  ->  follows the site, exactly as before
    generated rule (dry-run)       ->  detected, reported, NOT blocked, even where the site blocks

`onDetect` reports the mode the detection was actually handled under rather than the site's, because a
consumer counting blocks would otherwise over-report — and a dry-run rule not blocking is the entire point.

Two compatibility properties, both tested: a rule with no `enforcement` behaves identically to today (an
older server never sends the field), and an UNRECOGNISED value follows the site rather than reading as
"do not block". For a protection control the conservative reading of an unknown value is to keep enforcing;
treating it as opt-out would turn a future field name into a silent bypass.

The interesting test is the third one: both rules in one bundle, the authored one returning 403 and the
generated one 200, from the same request pair.

933 tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderbuds

coderbuds Bot commented Aug 14, 2026

Copy link
Copy Markdown

Per-rule dry-run enforcement added consistently across request, response, and egress.

🎯 Quality: 100% Elite · 📦 Size: Medium

📈 This month: Your 67th PR — above team average · Averaging Excellent

See how your team is trending →

@patchstackdave

Copy link
Copy Markdown
Contributor Author

/review

… too

Review caught the first version honouring `enforcement: 'dry-run'` only on the request path. Response rules
and the egress screen still read the site-wide mode, so a generated rule in dry-run could still redact a
body, withhold a response, or prevent an outbound request on a blocking site.

That is the same defect the change exists to prevent, one phase over: "detect until justified" is
meaningless if the rule still rewrites what the user sees or stops a call the app makes. Blocking a request
the app SENDS is at least as disruptive as blocking one it receives.

All three phases now resolve the effective mode through the same `ruleMode(rule)`, and each reports the mode
it actually acted under so a consumer counting blocks stays accurate.

Tests for both new paths, in each direction: a dry-run response rule leaves the secret in the body while an
opted-in rule redacts it; a dry-run egress rule records the outbound call and lets it through while an
opted-in rule makes it throw. 937 tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@patchstackdave
patchstackdave merged commit 7afb438 into main Aug 14, 2026
5 checks passed
@patchstackdave
patchstackdave deleted the davejong/eng-3567-honour-per-rule-enforcement branch August 14, 2026 12:12
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