[ENG-4058] Apply header-only rules when the body is not screened - #297
Merged
Merged
Conversation
|
Comprehensive feature addition with extensive streaming and header-only screening logic. 🎯 Quality: 75% Good · 📦 Size: Oversized — strongly consider breaking this down 🛡️ Standards: Not checked — 1,672 lines changed, over your team's 400-line limit, and nothing checked before it was opened. Coding agents can call the 🤖 Authorship: Agent-written — Claude Code, going by its own attribution. Whether a person read it is unknown; coding agents can call the 📈 This month: Your 164th PR — above team average · Averaging Good |
patchstackdave
added this pull request to stack #317
September 29, 2026 08:03
Contributor
Author
|
/review |
devlob
approved these changes
Sep 29, 2026
A redaction that reads response headers only is now applied to responses whose body is not screened (over the cap, binary, a live stream, or content-encoded), on both the fetch and Node paths. A rule's prefilter anchors are looked for in header values as well as the body. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An explicit block rule that reads response headers only is now enforced on responses whose body is not screened, on both paths, while the head can still change. If it has already been sent, the match is reported and recorded as a headers-sent skip. The Node withheld response carries only its own framing headers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An explicit flushHeaders() now runs the rules decided on headers alone before the head goes out: a block withholds the response and a redaction masks the header. They run once per response, so the screen at end does not report them again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A span redaction from a response.header.<name> condition now masks that header only, and one from response.headers masks the headers only; neither rewrites the body. A body condition masks the body and the same text in the headers, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A condition that names a list of parameters now masks the union of its members' scopes, so a list of response headers masks those headers only. Structural masking also applies when the body is read through a list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The status and request sources keep their broad mask, listed explicitly. A condition whose parameter the contract refuses, or that names no place in the response, now masks nothing and is reported through onError when the rules load; a rule left with nothing to mask withholds the response. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A parameter list member that is not a non-empty string, or a condition that is not an object, no longer makes rule loading throw. Such a condition masks nothing and is reported through onError, on the first load and on a refresh, and any error while reading a rule's redactions is reported without failing the load. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
patchstackdave
force-pushed
the
fix/header-redaction-without-body
branch
from
September 29, 2026 09:25
6d3c704 to
af79e62
Compare
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.
Response rules that read only response headers are now enforced on responses whose body is not screened: over the cap, binary, a live stream, or content-encoded. Both the fetch and Node paths are covered. These rules are decided from the headers alone, so the outcome doesn't depend on whether the body was read.
Header redactions
redactrule reads at least oneresponse.header.*/response.headersparameter and nothing that depends on the body, has a span to mask, and carries no decoding mutations. It masks only the header values it matched. Protecting the body takes a rule onresponse.body.Where a redaction masks
response.header.<name>condition masks that header only, entry by entry for a multi-valued header. Aresponse.headerscondition masks the headers only. Neither rewrites the body, so a header rule no longer changes body text, touches an unrelated header, or withholds a JSON response whose keys contain the same text.response.bodycondition masks the body and the same text in the headers, as before. A rule with both masks each where it reads.encodeapplies to the body only when its condition reads the body.blockis unaffected.get,post,request,cookie,files,server,raw,all) name no place in the response, and keep the broad mask they have always had: the body and every header.onErrorwhen the rules load. That covers a parameter the rule contract refuses (an empty or nested list, an unknown source or key, a value that is not a parameter),egress.*, or no parameter at all. A rule left with nothing to mask withholds the response rather than reporting a mask it didn't make.null), or a condition that isn't an object, is handled the same way. Rule loading never fails because of one: on the first load, a refresh or a push, the condition masks nothing and is reported throughonError, and any error while reading a rule's redactions is reported and the rule loaded without them. A delivered update the rule contract refuses is still rejected whole by the rule source, keeping the previous rules.response.body, so their behaviour is unchanged.Header blocks
blockaction that reads only response headers withholds the response with the existing generic response.endcallback still runs once.Both kinds
flushHeaders(), before the head goes out, or on a branch that doesn't screen the body. The screen atendleaves them out, so a match is reported once.headers-sentskip. The skip names the headers that could not be masked, oraction: "block"for a block that could not be enforced.endas before. The head has gone, so a body rule that matches then can only rewrite a chunked body; otherwise the response is cut off rather than sent under a length that no longer fits.Also
content-type,content-length), matching the fetch path. Headers the application set no longer go out beside it.prefilteranchors are now looked for in header values as well as the body, so a header rule with a prefilter is no longer gated on the body's contents.screenResponseandresponseRulesdocumentation inprotect.d.tsnow says this: a rule that reads only response headers redacts or blocks on the headers and is enforced even when the body can't be screened; a redaction masks only the header it matched; body protection needs aresponse.bodyrule, which also masks the same text in headers; and a withheld response carries only its owncontent-typeandcontent-length.Tests:
tests/protect/redaction-scope-malformed-parameters.test.tscovers malformed list members and conditions on the first load, a refresh and the push endpoint.tests/protect/redaction-scope-parameter-forms.test.tscovers string, list and group forms on the ordinary, early-flush and unscreened-body paths (fetch and Node), legacy broad parameters, and each shape with no place to mask.tests/protect/response-redaction-scope.test.tscovers redaction scope on both paths: header, all-headers, body and combined rules; multi-valued and mixed-case headers; literal and token-claim matches;encode; early flush and unscreened bodies.tests/protect/header-redaction-without-a-body.test.tsandtests/protect/header-block-without-a-body.test.tscover:writeHead();flushHeaders()before a streamed text or binary body;Validation: full suite (3,931 passed, 7 skipped), typecheck, and build.
Part of ENG-4058.
🤖 Generated with Claude Code