Skip to content

fix(mcp): header safety validation and Authorization conflict bug from #704#752

Merged
zmanian merged 3 commits intonearai:stagingfrom
reidliu41:fix/639-mcp-header-safety
Mar 11, 2026
Merged

fix(mcp): header safety validation and Authorization conflict bug from #704#752
zmanian merged 3 commits intonearai:stagingfrom
reidliu41:fix/639-mcp-header-safety

Conversation

@reidliu41
Copy link
Copy Markdown
Contributor

fix(mcp): add CRLF header validation and fix Authorization overwrite bug

Port the unique security contributions from #704 that #721's transport
refactor did not cover.

  • Validate custom header names and values at config time to reject CRLF
    injection sequences that could smuggle extra headers into HTTP requests.
    Reject empty header names for the same reason.
  • Fix Authorization header conflict: build_request_headers() previously
    overwrote any user-configured Authorization header with the OAuth
    Bearer token unconditionally. Servers using API-key auth via a custom
    Authorization header would silently lose their credentials. Now skip
    OAuth injection when has_custom_auth_header() detects user-provided
    auth (case-insensitive match).
  • Add wire-level echo server integration tests that spin up a real HTTP
    server to verify custom headers arrive on the wire, per-request headers
    override custom headers correctly, and custom Authorization is preserved
    when no OAuth token is injected.

@github-actions github-actions bot added scope: tool/mcp MCP client size: L 200-499 changed lines risk: medium Business logic, config, or moderate-risk modules contributor: regular 2-5 merged PRs labels Mar 9, 2026
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly improves the security and robustness of the MCP client's HTTP request handling. It addresses potential vulnerabilities related to header injection and resolves a critical issue where custom authentication headers were being inadvertently overwritten. The changes are backed by new, thorough integration tests that validate the behavior at the network level, ensuring reliable and secure communication.

Highlights

  • Header Safety Validation: Implemented validation for custom header names and values to prevent CRLF injection attacks and reject empty header names, enhancing security.
  • Authorization Header Conflict Resolution: Fixed a bug where the OAuth Bearer token unconditionally overwrote user-configured Authorization headers, ensuring custom API-key authentication is preserved.
  • Wire-Level Integration Tests: Added comprehensive integration tests using a real HTTP echo server to verify that custom headers are correctly sent and Authorization header logic functions as expected end-to-end.
Changelog
  • src/tools/mcp/client.rs
    • Modified build_request_headers to conditionally inject OAuth tokens, preventing the overwrite of user-provided Authorization headers.
    • Added a check for the presence of custom Authorization headers before applying OAuth token injection.
  • src/tools/mcp/config.rs
    • Implemented validation logic within McpServerConfig::validate to reject custom header names and values containing CRLF characters.
    • Added a check to prevent empty custom header names.
    • Introduced a new has_custom_auth_header method to detect if a custom Authorization header has been configured.
    • Included new unit tests to cover CRLF injection rejection, empty header name rejection, and case-insensitive detection of custom Authorization headers.
  • src/tools/mcp/http_transport.rs
    • Added a spawn_echo_server utility function to create a temporary HTTP server for integration testing.
    • Implemented test_wire_custom_headers_sent to verify that custom headers are correctly transmitted over the wire.
    • Added test_wire_per_request_headers_override_custom to confirm that per-request headers take precedence over custom headers.
    • Included test_wire_custom_auth_preserved_when_no_per_request_auth to ensure custom Authorization headers are maintained when no OAuth token is injected.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces important security enhancements and bug fixes for the MCP client. It adds validation against CRLF injection in custom headers, which is a crucial step to prevent HTTP header smuggling. The fix to prevent overwriting user-provided Authorization headers is also a critical correction that ensures API-key based authentication is not silently broken. The addition of wire-level integration tests using a real echo server is an excellent practice and greatly increases confidence in the correctness of the header handling logic. My review includes a suggestion to further strengthen the header validation to check for all control characters, not just CRLF, for even better security.

Comment thread src/tools/mcp/config.rs Outdated
Comment on lines +198 to +210
if name.bytes().any(|b| b == b'\r' || b == b'\n') {
return Err(ConfigError::InvalidConfig {
reason: format!("Header name '{}' contains invalid CRLF characters", name),
});
}
if value.bytes().any(|b| b == b'\r' || b == b'\n') {
return Err(ConfigError::InvalidConfig {
reason: format!(
"Header value for '{}' contains invalid CRLF characters",
name
),
});
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current validation only checks for \r and \n. To make this validation more robust against header injection and align better with HTTP specifications (RFC 7230), I suggest checking for all ASCII control characters. This provides a stronger guarantee of header safety.

Suggested change
if name.bytes().any(|b| b == b'\r' || b == b'\n') {
return Err(ConfigError::InvalidConfig {
reason: format!("Header name '{}' contains invalid CRLF characters", name),
});
}
if value.bytes().any(|b| b == b'\r' || b == b'\n') {
return Err(ConfigError::InvalidConfig {
reason: format!(
"Header value for '{}' contains invalid CRLF characters",
name
),
});
}
if name.bytes().any(|b| b.is_ascii_control()) {
return Err(ConfigError::InvalidConfig {
reason: format!("Header name '{}' contains invalid control characters", name),
});
}
if value.bytes().any(|b| b.is_ascii_control()) {
return Err(ConfigError::InvalidConfig {
reason: format!(
"Header value for '{}' contains invalid control characters",
name
),
});
}

Copy link
Copy Markdown
Collaborator

@zmanian zmanian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good security fix porting CRLF validation and Authorization conflict resolution from #704. Strong test coverage (unit + wire-level). Two blocking issues:

  1. Validation not enforced on load path -- load_mcp_servers() and load_mcp_servers_from_db() bypass CRLF checks. A corrupted config file with CRLF in headers passes through to McpClient. Validate on load or at McpClient construction.

  2. Header name validation incomplete -- Only checks for \r and \n. HTTP header names (RFC 9110) must be valid tokens -- spaces, colons, null bytes would pass validation but cause undefined behavior. Use http::HeaderName::from_bytes() which does the full RFC check and is already a transitive dependency via reqwest.

Non-blocking:

  • has_custom_auth_header() checks server_config but actual headers come from self.custom_headers -- works today but indirect
  • Wire test comment about "last one wins" isn't guaranteed reqwest behavior for all headers

  Replace hand-written CRLF checks with reqwest::header::HeaderName::from_bytes()
  and HeaderValue::from_str(), catching spaces, colons, null bytes, and all
  non-token characters that the previous validation missed.

  Add validation to load_mcp_servers_from() and load_mcp_servers_from_db() so
  corrupted configs from disk or DB are rejected at load time instead of silently
  flowing through to McpClient. Improve app.rs error handling to distinguish
  "no config" from "corrupted config" (including malformed JSON).

  Also fix build_request_headers() to check self.custom_headers directly instead
  of indirectly via server_config, and clarify the wire test comment about
  HeaderMap::insert replacement semantics.
@reidliu41 reidliu41 force-pushed the fix/639-mcp-header-safety branch from 60f6a87 to 93ea7f0 Compare March 9, 2026 23:48
@henrypark133 henrypark133 changed the base branch from main to staging March 10, 2026 02:19
Copy link
Copy Markdown
Collaborator

@zmanian zmanian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both blocking issues from the previous review are addressed:

  1. Load-path validation -- load_mcp_servers_from() and load_mcp_servers_from_db() now call server.validate() on every server entry. The app.rs error handling distinguishes validation failures (warn) from missing config (debug). Regression test covers the file-based path with a corrupted header name written directly to disk.

  2. RFC 9110 header validation -- Replaced the manual CRLF check with reqwest::header::HeaderName::from_bytes() and HeaderValue::from_str(), which enforce the full RFC token grammar. Test coverage is thorough: CRLF in names and values, spaces, colons, null bytes, empty names.

Non-blocking items also resolved: build_request_headers() now checks self.custom_headers directly for Authorization (no more indirection through server_config), and the wire test comment accurately describes HeaderMap::insert replacement semantics.

CI fix commit (93ea7f0) is trivial -- id: 1 to id: Some(1) matching the McpRequest type.

No new issues found.

@reidliu41
Copy link
Copy Markdown
Contributor Author

I see it's already approved — is this good to merge

@zmanian zmanian merged commit a1b3911 into nearai:staging Mar 11, 2026
22 checks passed
@ironclaw-ci ironclaw-ci bot mentioned this pull request Mar 12, 2026
bkutasi pushed a commit to bkutasi/ironclaw that referenced this pull request Mar 28, 2026
…nearai#704 (nearai#752)

* fix(mcp): header safety validation and Authorization conflict bug from nearai#704

* fix(mcp): enforce RFC 9110 header validation on all config load paths

  Replace hand-written CRLF checks with reqwest::header::HeaderName::from_bytes()
  and HeaderValue::from_str(), catching spaces, colons, null bytes, and all
  non-token characters that the previous validation missed.

  Add validation to load_mcp_servers_from() and load_mcp_servers_from_db() so
  corrupted configs from disk or DB are rejected at load time instead of silently
  flowing through to McpClient. Improve app.rs error handling to distinguish
  "no config" from "corrupted config" (including malformed JSON).

  Also fix build_request_headers() to check self.custom_headers directly instead
  of indirectly via server_config, and clarify the wire test comment about
  HeaderMap::insert replacement semantics.

* fix ci issue
drchirag1991 pushed a commit to drchirag1991/ironclaw that referenced this pull request Apr 8, 2026
…nearai#704 (nearai#752)

* fix(mcp): header safety validation and Authorization conflict bug from nearai#704

* fix(mcp): enforce RFC 9110 header validation on all config load paths

  Replace hand-written CRLF checks with reqwest::header::HeaderName::from_bytes()
  and HeaderValue::from_str(), catching spaces, colons, null bytes, and all
  non-token characters that the previous validation missed.

  Add validation to load_mcp_servers_from() and load_mcp_servers_from_db() so
  corrupted configs from disk or DB are rejected at load time instead of silently
  flowing through to McpClient. Improve app.rs error handling to distinguish
  "no config" from "corrupted config" (including malformed JSON).

  Also fix build_request_headers() to check self.custom_headers directly instead
  of indirectly via server_config, and clarify the wire test comment about
  HeaderMap::insert replacement semantics.

* fix ci issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: regular 2-5 merged PRs risk: medium Business logic, config, or moderate-risk modules scope: tool/mcp MCP client size: L 200-499 changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants