fix(providers): fail closed when Anthropic parse realloc fails - #80
Conversation
Publish text_cap/tool_cap only after realloc succeeds. On grow failure, free parse scratch and return an error instead of memcpy/index against an inflated cap. Refs: #80
f2a60f6 to
e489f0b
Compare
There was a problem hiding this comment.
Not approved: Bugbot and Security Agent both passed with no findings, but GitHub blocked this automation from approving its own PR. Human review is still required; adriannoes is already assigned and no additional reviewers were added.
Sent by Cursor Approval Agent: Pull Request Router and Approver
There was a problem hiding this comment.
Summary
ShellClaw is a C99/C11 edge agent (Jetson / Raspberry Pi) with a ReAct loop over LLM providers, tools, channels, an embedded gateway, and ASAP. Providers sit behind provider_t and share curl/JSON helpers in provider_common.c.
This PR fixes a real heap misuse in Anthropic parse_response_body: the old loop did text_cap *= 2 / tool_cap *= 2 before realloc, then memcpy or indexed tool_calls[tool_count] against the inflated cap when the grow failed. The new ensure_text_cap / ensure_tool_cap helpers publish capacity only after anth_realloc succeeds; failure goes through parse_fail (scratch freed, OOM error) instead of copying into a 256-byte block with a 512-byte cap. Scope is src/providers/anthropic.c, tests/test_anthropic.c, and a CHANGELOG line. That is the right layer and the right size.
No merge blockers. I re-ran make static (cppcheck 2.13), CI=true test_anthropic (-Werror), and GCC ASan+UBSan with detect_leaks=1 on ./build/test_anthropic. All passed. GitHub static, test, release was still in progress at review time.
Must Fix
None.
Should Fix
None.
Nice to Have
ensure_tool_cap(src/providers/anthropic.c83-104) doubles once, whileensure_text_caploops until*text_cap >= need. Safe today becauseappend_tool_useonly ever asks for*tool_count + 1. Awhile (tool_count >= *tool_cap)grow would make the helper correct if a later caller jumps the count.test_parse_text_realloc_failure_is_error(tests/test_anthropic.c215-233) does not assertresponse.tool_calls == NULL/tool_calls_count == 0. The tool-OOM test does. A regression that published a stale tool array on text OOM would not be caught.append_tool_usestill returns 0 ifprovider_dup_strorcJSON_PrintUnformattedreturns NULL (src/providers/anthropic.c139-146). The parse succeeds with a NULL id/name/args. Pre-existing;agent.ctreats a NULL name as an unknown tool. Not the overflow this PR claims to fix.s_fail_next_reallocsis reset by each OOM test after the parse. Resetting it insideanthropic_parse_response_for_testwould stop a forgotten hook from poisoning a later case.
Positive Highlights
- Cap is published only after realloc, using a temp
grownpointer, so a failed grow cannot NULL the original block or lie about its size. That matchesprovider_write_cbinprovider_common.c(42-45). parse_failfrees eachid/name/argumentsup totool_countbefore freeing the array, so a failed 5thtool_usedoes not leak the first four.- Tests cover both success growth (300-byte text past 256, 6
tool_usepast cap 4) and injected realloc failure. The hook is#ifdef SHELLCLAW_TESTonly; productionanthropic.ois a plainrealloc. - CHANGELOG Unreleased Fixed line is in its own docs commit. OpenAI/local stay on
provider_parse_chat_completions_json, which mallocs the exact JSON array size, so this grow-then-index bug was Anthropic-specific. parse_failusesfor (size_t i = 0;rather than a function-scopei, which is thevariableScopehit that failedmake staticon the earlier Bot attempt. cppcheck is clean on this tree.
Sent by Cursor Automation: Adrianno鈥檚 personal code review


Summary
parse_response_bodynow publishestext_cap/tool_caponly afterreallocsucceeds. Grow failure fails the parse, frees scratch state, and sets an OOM error instead ofmemcpy/indexing against an inflated cap.development(includes fix(asap): avoid double-free on malformed JSON-RPC results聽#84). Not a cherry-pick of the original Bot commit.src/providers/anthropic.candtests/test_anthropic.c. The original Bot PR failedmake static(variableScopeonsize_t i).Test plan
test_parse_text_past_initial_cap(300-byte text grows past 256)test_parse_six_tool_use_blocks(6thtool_usegrows past cap 4)test_parse_text_realloc_failure_is_error(red: success/overflow, then green fail-closed)test_parse_tool_realloc_failure_is_errormake test_anthropic/ ASan+UBSan ontest_anthropicCI=true make testmake staticRefs: #80
Note
Low Risk
Scoped to Anthropic response parsing and test-only realloc hooks; behavior change is stricter error handling on OOM instead of possible buffer misuse.
Overview
Fixes #80: Anthropic
contentparsing no longer bumpstext_cap/tool_capbeforereallocsucceeds, which could letmemcpyor tool indexing run against a larger cap than the allocated buffer.Parsing now grows text and
tool_usearrays through helpers that only publish capacity after a successful grow, return -1 with an OOM message on failure, and tear down partial state viaparse_fail. UnderSHELLCLAW_TEST,anthropic_test_fail_next_reallocssimulates failed grows for regression tests (long text past 256 bytes, sixtool_useblocks, and explicit realloc-failure paths). CHANGELOG documents the fix.Reviewed by Cursor Bugbot for commit e489f0b. Bugbot is set up for automated code reviews on this repo. Configure here.