Skip to content

feat(proxy/blockheaders): emit X-Pipelock-Block-Reason on every block path#469

Open
luckyPipewrench wants to merge 3 commits intofeat/block-reason-headerfrom
feat/block-reason-impl
Open

feat(proxy/blockheaders): emit X-Pipelock-Block-Reason on every block path#469
luckyPipewrench wants to merge 3 commits intofeat/block-reason-headerfrom
feat/block-reason-impl

Conversation

@luckyPipewrench
Copy link
Copy Markdown
Owner

Summary

Wires the X-Pipelock-Block-Reason header set onto every pipelock block path. Stacks on PR #467 (the schema and emit package); merges back to main only after that PR lands.

Done-state criterion 3 of the v2.4 release spec: schema specified AND implemented across forward, intercept, fetch, reverse, MCP HTTP, and WebSocket. No transport split.

Coverage by transport

  • Forward proxy: 9 block sites. Envelope verify, scanner unavailable, missing host, header DLP, scanner result with hint, escalated scanner result, session-airlock detail, escalation level, budget admission.
  • Intercept (TLS-MITM): 14 block sites. Envelope verify, kill switch, authority mismatch, airlock, scanner result with hint, escalated scanner result, A2A header DLP, body-scan blocks (general DLP and A2A flavors), header secret, CEE block, escalation level, envelope-injection failure.
  • Reverse proxy: 7 block sites. writeReverseProxyBlock signature gains a blockreason.Info parameter so every caller is forced to supply a reason.
  • MCP HTTP: method-not-allowed and compressed-response blocks both emit.
  • WebSocket: 13 pre-upgrade http.Error sites use writeBlockedError; seven post-upgrade plwsutil.WriteCloseFrame sites carry the JSON document via Info.CloseFramePayload(), giving WebSocket transport parity through close frames instead of headers (per docs/specs/block-reason-header.md transport-coverage table).
  • Fetch handler: 13 writeJSON-based block sites use the new writeBlockedJSON helper.

Helper layer

  • internal/proxy/blockheaders.go adds reasonFromScanner (maps scanner.Scanner* labels to blockreason.Reason), severityFromReason and retryFromReason classifiers (canonical per spec), blockInfo and blockInfoFor constructors, plus writeBlockedError (drop-in for http.Error) and writeBlockedJSON (drop-in for the fetch handler's writeJSON).
  • All public helpers are 100 percent covered.

Compatibility

  • Existing 403 status codes and JSON response bodies are unchanged. The header set is purely additive.
  • Free-text WebSocket close-frame messages get replaced with the structured CloseFramePayload JSON; that is a wire-format change but it stays inside RFC 6455's 123-byte close-frame ceiling.

Anti-scope-creep

  • No agent-side libraries or SDKs that consume the header. Separate ecosystem deliverable.
  • No header-driven retry orchestration on the pipelock side. Pipelock emits, agent decides.
  • MCP stdio still has no HTTP layer to attach headers to. Block-reason taxonomy in JSON-RPC errors is tracked separately.

Stacked on

First transport in the block-reason header rollout. Adds shared helpers (reasonFromScanner mapper, severityFromReason and retryFromReason classifiers, blockInfo and blockInfoFor constructors, writeBlockedError drop-in for http.Error) and refactors forward proxy's 9 block sites: envelope verify, scanner unavailable, missing host, header DLP, scanner result with hint, escalated scanner result, session-airlock detail, escalation level, and budget admission.

Helper file is 100 percent covered. Existing forward proxy tests all pass with no parity regression. Subsequent commits stack the same pattern onto intercept, fetch, reverse, MCP HTTP, and WebSocket transports.

Stacks on PR #467 (the schema and emit package). Merges back to main only after PR #467 lands.
…reverse, MCP HTTP, WebSocket

Stacks the second batch onto the forward proxy refactor. Five transports now emit the X-Pipelock-Block-Reason header set on every block path. Fetch handler (the writeJSON-based path in proxy.go) lands as a separate stacked commit because its block sites use a different writer than http.Error and warrant their own focused review.

Intercept: 14 block sites covering envelope verify, kill switch, authority mismatch, airlock, scanner result with hint, escalated scanner result, A2A header DLP, body-scan blocks (general DLP and A2A flavors), header secret, CEE block, escalation level, and envelope-injection failure. The 502 BadGateway upstream-error path stays unannotated since it is not a security block.

Reverse: writeReverseProxyBlock signature gains a blockreason.Info parameter so every caller is forced to supply a reason. Seven call sites updated covering scanner unavailable, envelope verify, kill switch, URL DLP, header DLP, body-scan fail-closed and enforce paths, and envelope-injection blocked-error.

MCP HTTP: method-not-allowed and compressed-response blocks both emit. WebSocket: 13 pre-upgrade http.Error sites use writeBlockedError; seven post-upgrade plwsutil.WriteCloseFrame sites carry the JSON document via Info.CloseFramePayload(), giving WebSocket transport parity through close frames instead of headers (per docs/specs/block-reason-header.md transport-coverage table). Free-text closeReason is preserved for receipt logging; only the wire payload swaps.

session_api.go gets a small tierNone constant extraction unrelated to the header work; goconst tripped because the new package raised the cross-package none/transient/policy literal count over the threshold.

Full proxy and mcp test suites pass with no parity regression. Stacks on PR #467.
Closes the transport refactor. Fetch handler now emits the X-Pipelock-Block-Reason header set on every block path, completing parity with forward, intercept, reverse, MCP HTTP, and WebSocket.

Adds writeBlockedJSON helper (the writeJSON analogue of writeBlockedError). The fetch endpoint's block sites use writeJSON to deliver a structured FetchResponse; setting the header set before writeJSON keeps the response headers consistent with the JSON body's BlockReason field.

Thirteen block sites refactored: envelope verify, airlock drain (twice — initial fetch and pre-budget recheck), session escalation level (twice), header secret, CEE block, envelope-injection failure, redirect/upstream blocked-error, compressed-response fail-closed, browser shield oversize, media policy, and three response-scan prompt-injection blocks (enforce, ask-no-approver, ask-deny). Status arg kept on the helper signature for forward compat with future non-403 fetch block paths.

Full proxy test suite passes (38s, no parity regression). Lint clean. Stacks on PR #467.
@brin-security-scanner brin-security-scanner Bot added the contributor:verified Contributor passed trust analysis. label May 2, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 2, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e79212e9-57ce-4d57-bd14-1ce6302e744f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/block-reason-impl

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@brin-security-scanner brin-security-scanner Bot added the pr:verified PR passed security analysis. label May 2, 2026
@luckyPipewrench luckyPipewrench self-assigned this May 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor:verified Contributor passed trust analysis. pr:verified PR passed security analysis.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant