Repository navigation
fix: remove reachable panics from untrusted remote and repo input - #114
Conversation
Four sites could be crashed by input from a remote server, a fetched page, a model-generated command, or a repo's .mcp.json: - code-tools WebFetch extract_title: lowercased the body with Unicode to_lowercase and used the resulting offsets on the raw body. Characters whose lowercase form has a different byte length shifted the offsets and panicked. Use to_ascii_lowercase, which keeps offsets stable. - code-tools extract_search_hits: `after_tag[1..]` panicked on an empty or multi-byte tail after an anchor with no closing tag. Advance by one char. - code-runtime bash_validation extract_first_command: sliced the untrimmed value with an offset computed on the trimmed value, which can land inside a multi-byte character. Slice the trimmed value. - code-integrations MCP percent_decode (CCR proxy URL): sliced the str at byte offsets after '%', which panics inside a multi-byte character. Decode on bytes. - code-integrations MCP stdio read_frame: preallocated a payload buffer of the peer-declared Content-Length, which panics or aborts for huge values. Read through take() instead and return UnexpectedEof on a short stream. Each fix has a regression test. Valid input behaviour is unchanged. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Bounded frame reading crates/code-integrations/src/mcp/stdio.rs |
McpStdioProcess::read_frame delegates to incremental payload reading. Incomplete payloads return UnexpectedEof. Tests cover incomplete and valid frames. |
MCP percent decoding
| Layer / File(s) | Summary |
|---|---|
Byte-safe percent decoding crates/code-integrations/src/mcp/utils.rs |
percent_decode checks escape bytes without slicing across UTF-8 character boundaries. A regression test covers malformed escapes involving a multibyte character. |
Bash command validation
| Layer / File(s) | Summary |
|---|---|
Command extraction and read-only validation crates/code-runtime/src/bash_validation.rs |
Command extraction handles empty assignments and multibyte quoted values. Read-only validation blocks commands containing whitespace other than space, tab, or newline. Tests cover these cases. |
HTML search parsing
| Layer / File(s) | Summary |
|---|---|
UTF-8-safe HTML parsing crates/code-tools/src/lib.rs |
Title matching uses ASCII-only lowercasing. Search-hit parsing skips a complete Unicode character when an anchor lacks a closing tag. Regression tests cover both cases. |
Priority:
Estimated code review effort: 3 (Moderate) | ~20 minutes
Change: Bug fix
Merge Risk: 
Minimal · up to a7ac7
-
This change removes several reachable panics and reduces memory exposure from MCP Content-Length values. No concrete merge-blocking risk remains; a maximum frame size could be added later as optional hardening.
Pre-merge checks: 
4 · 
1
-
Failed checks: 1 warning Passed checks: 4 passed-
Full details: Description check
-
Explanation
The description provides detailed motivation, affected sites, root causes, fixes, regression tests, verification results, and follow-up context. It does not use the template's Type of Change or Checklist sections, but the required change and testing information is otherwise complete.
-
Full details: Docstring Coverage
-
Explanation
Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 20 functions across 4 files.
Resolution
Write docstrings for the functions missing them to satisfy the coverage threshold.
-
Finishing Touches: 💡 1
-
📝 Generate docstrings: 💡
-
- Commit to this branch
- Create a new PR
🧪 Generate unit tests (beta)
-
- Commit to this branch
- Create a new PR
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
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.
Comment @coderabbitai help to get the list of available commands.
With whitespace right after '=', bash makes the assignment empty and runs the next word. Trimming before measuring the value consumed that word, so the permission check saw the following argument as the command (A= rm x was reported as x). Start the value at '=' and only skip a word when the value is non-empty. Correct the earlier expectation that A= 'é' ls reports ls: bash runs 'é' there. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
|
Follow-up commit 2b2bbf3 addresses an automated security review finding. With whitespace after |
The parser trimmed and split on Unicode whitespace, but bash only separates words on space, tab and newline. A command such as A=<NBSP>ls rm -rf / was reported as ls, while bash runs rm. In read-only mode, refuse any command containing other whitespace characters rather than guessing how bash will split it. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
|
Commit security review follow-up: a3 fix for Unicode whitespace. |
Summary
A panic audit of in-scope parsing paths (server/webhook handlers, provider and MCP transports, WebFetch/search HTML, runtime bash validation, CLI and config parsing, Rust SDK). Four sites could be crashed by input an attacker or a malicious remote controls. Each is fixed with a regression test. Valid-input behaviour is unchanged.
Fixed sites
crates/code-tools/src/lib.rsextract_title(~L3732)to_lowercase, then offsets from the lowercased copy were used on the raw body. Characters whose lowercase form has a different byte length (e.g.İ) shifted the offsets and panicked on a char boundary or out of range.to_ascii_lowercase(), which keeps byte offsets identical. Tests:title_extraction_survives_unicode_that_changes_length_when_lowercased,title_extraction_handles_unclosed_title_with_unicode_prefix.crates/code-tools/src/lib.rsextract_search_hits(~L3827)result__aanchor whose>is followed by end-of-input or a multi-byte character and no</a>.after_tag[1..]panicked on an empty string or a non-boundary index.skip_one_charthat advances by one char. Tests:search_hit_parsing_survives_anchor_ending_at_end_of_input,search_hit_parsing_survives_multibyte_char_after_anchor_tag.crates/code-runtime/src/bash_validation.rsextract_first_command(~L639)=, and a multi-byte quoted value, e.g.A= 'é' ls. Commands come from model output, so this is reachable from a remote API user.find_end_of_valuereturns an offset into the trimmed value, but it was applied to the untrimmed text, so the slice could land inside a character.env_prefix_with_whitespace_and_multibyte_quoted_value_does_not_panic.crates/code-integrations/src/mcp/utils.rspercent_decode(~L185)mcp_urlquery value in a CCR proxy URL. A repo's.mcp.jsoncan set this URL, and signature computation runs on it.&value[index+1..index+3]sliced the str at byte offsets, which panics when the window ends inside a multi-byte character (e.g.%aé).hex_nibblehelper. Test:unwrapping_ccr_proxy_url_with_multibyte_percent_sequence_does_not_panic.crates/code-integrations/src/mcp/stdio.rsread_frame(~L1237)Content-Lengthheader from an MCP stdio server.vec![0_u8; content_length]preallocated the peer-declared size. Very large values panic with "capacity overflow" or abort on allocation.read_frame_from<R: AsyncBufRead>, which reads throughtake()and returnsUnexpectedEofon a short stream. Tests:huge_declared_content_length_returns_error_instead_of_panicking,reads_well_formed_content_length_frame.Verification
title_extraction_survives_unicode...,search_hit_parsing_survives_anchor_ending_at_end_of_input, andsearch_hit_parsing_survives_multibyte_char_after_anchor_tagall panic on the old code (checked by temporarily restoring it; the fixed code was restored afterwards).cargo fmt --all -- --check: passcargo clippy -p code-tools -p code-runtime -p code-integrations --all-targets -- -D warnings: passcargo test -p code-tools -p code-runtime -p code-integrations: all pass, including the new tests (rebased onto current origin/main and re-run)Reviewed and left as-is
.lock().unwrap()/.expect("... poisoned")in server, runtime, memory, and CLI code: these only panic after another thread panicked while holding the lock, so they are secondary to the panics fixed here.code-providers/src/providers/anthropic.rslast_error.expect(...): the first failed attempt always setslast_errorbefore any break, so it is unreachable.expect/next().expectcalls incode-runtime/src/json.rs: guarded bypeek().code-webhooksHMACnew_from_slice(...).expect: HMAC accepts any key length.compact.rs,file_ops.rs,policy.rs,permissions.rs,summary_compression.rs,sse.rs, the openai args delta,prompt_cache.rs(ASCII-only input),oauth.rspercent decode and base64,worker_boot.rs,lane_events.rs, the Neo4j row parser, markdown section extraction, and the webhook receiver: each index is bounded or on a char-safe offset.Instantarithmetic on operator policy values incode-server: these are saturating or comparison-only.overflow-checks, so arithmetic overflow wraps rather than panicking.🤖 Generated with Claude Code
Summary by CodeRabbit