fix(http): centralize outbound hop-by-hop sanitization - #887
Open
mkoushni wants to merge 13 commits into
Open
Conversation
…rs (praxis-proxy#669) is_blocked_mcp_header already dropped Connection but left the Keep-Alive field and de-facto Proxy-Connection hop-by-hop header, so client-supplied MCP tool headers could reach the outbound HTTP/1 transport. Signed-off-by: mkoushni <mkoushni@redhat.com>
leseb
requested changes
Sep 2, 2026
Contributor
Author
|
@leseb i choosing option 2 - to complete full implementation |
Signed-off-by: mkoushni <mkoushni@redhat.com>
Praxis core main gated admin endpoints behind praxis-protocol's admin-api feature. test-praxis-main patches that tree in, so the praxis-main feature must turn the admin API on or the server crate does not compile. Signed-off-by: mkoushni <mkoushni@redhat.com>
Cargo rejects praxis-protocol/admin-api while the lockfile is still 0.5.3, so lint and unit tests fail before patch-praxis runs. test-praxis-main stays a known core-compat break until 0.5.4. Signed-off-by: mkoushni <mkoushni@redhat.com>
praxis-bot
reviewed
Sep 3, 2026
praxis-bot
left a comment
Collaborator
There was a problem hiding this comment.
praxis-bot review: fix(http): centralize outbound hop-by-hop sanitization
Purpose
Consolidates five independent hop-by-hop header lists into a single http_hop module and adds Connection-nominated header stripping across all outbound header-copy paths (MCP, OpenAI forward headers, file-search replay, http_callout). Closes the {"connection":"x-smuggle","x-smuggle":"secret"} smuggling vector.
Assessment
No actionable findings. This is a clean, well-tested security hardening PR.
Correctness:
is_hop_by_hopcovers the full RFC 9110 Section 7.6.1 set plus obsoleteProxy-Connection-- correct.connection_nominates_headerfails closed on non-text values (filter_map withto_str().ok()) -- correct security posture.- MCP path uses
connection_nominated_from_json(pre-computedHashSetbefore the filter loop) rather thanconnection_nominates_headerbecause headers are JSON, notHeaderMap-- correct adaptation. is_blocked_mcp_headerremovesCONNECTION,PROXY_AUTHORIZATION,TE,TRAILER,TRANSFER_ENCODING,UPGRADE,keep-alive,proxy-connectionfrom its explicit checks since all are now covered by theis_hop_by_hopdelegation. The remaining matches (AUTHORIZATION,CONTENT_LENGTH,COOKIE,FORWARDED,HOST,SET_COOKIE) are end-to-end headers that belong in the MCP-specific denylist -- correct split.- HTTP callout
is_disallowed_forward_headeris a strict superset of the oldDISALLOWED_FORWARD_HEADERSarray (addskeep-alive,proxy-authenticate,proxy-connection,tewhich were previously missing) -- correct hardening. should_replay_original_headerin file-search callout correctly delegates hop-by-hop checking tois_hop_by_hopand keeps the body-rewrite-stale headers (accept-encoding,content-encoding,content-length,content-md5,signature,signature-input) as the residual check.
Test coverage:
- New
http_hopmodule has unit tests for all four public functions. - MCP: three new tests covering
keep-alive/proxy-connectionstripping,Connection-nominated smuggling, andproxy-authenticateparity. - OpenAI API client:
forward_headers_skips_connection_nominated_fieldstest. - HTTP callout: two integration tests (
hop_by_hop_forward_headers_not_sent,connection_nominated_forward_header_not_sent) that exercise the full filter pipeline viaMockServer. - File-search:
proxy-connectionadded to the existing exclusion test. - Inference fixture header policy updated to delegate, maintaining fixture validation consistency.
Conventions: License header (Apache-2.0) matches peer files in the ai repo. Module ordering in lib.rs is alphabetical. Doc comments on all public items with doctests on is_hop_by_hop.
| Severity | Count |
|---|---|
| Critical | 0 |
| Large | 0 |
| Medium | 0 |
Clean PR. No changes requested.
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.
Summary
is_blocked_mcp_headeralready droppedConnectionbut leftKeep-AliveandProxy-Connection, so client-supplied MCP tool headers survived into the outbound HTTP/1 transport.praxis_ai_apis::http_hop::is_hop_by_hop). MCP, OpenAIforward_headers, file-search replay, experimentalhttp_callout, and fixture replay all delegate to it so those lists cannot drift again. MCP also blocksproxy-authenticatefor parity.Connectiontoken list before droppingConnectionitself, so{"connection":"x-smuggle","x-smuggle":"secret"}cannot smuggle a header onto MCP, API callouts, orhttp_callout.Fixes #669
Test plan
cargo test -p praxis-ai-apis --lib hop_by_hopcargo test -p praxis-ai-apis --lib connection_nominatcargo test -p praxis-ai-apis --lib keep_alive_and_proxycargo test -p praxis-ai-filters --features http-callout-filter --lib hop_by_hop_forwardcargo test -p praxis-ai-filters --features http-callout-filter --lib connection_nominated_forwardmake lint