fix(agent): skip memory injection when system prompt buffer is full - #75
Conversation
447dd09 to
cd236d9
Compare
There was a problem hiding this comment.
Stale comment
Cannot approve: GitHub blocks self-approval because this automation shares the cursor[bot] author of the PR. Cursor Bugbot and Cursor Security Agent both completed successfully with no findings that need human review. Reviewer adriannoes is already assigned. 🤖 ✨
Sent by Cursor Approval Agent: Pull Request Router and Approver
There was a problem hiding this comment.
Summary
ShellClaw is a C99 ReAct agent for Jetson / Raspberry Pi: src/core owns the loop, SQLite memory, and the 64 KiB system prompt; tools, providers, channels, gateway, and ASAP sit behind that. This PR fixes a real heap overflow in append_memories_to_system when that prompt is already full, then documents it in CHANGELOG Unreleased. The change is small, matches the fail-closed style of #70/#61, and the new regression is aimed at the actual OOB write.
Must Fix
None. Old code clamped recall_len to 0 when buf_size <= len + prefix_len but still memcpy'd the 22-byte Relevant memories prefix from system_buf + len (NUL at index 65535 overwritten, 21 bytes past the malloc(65536), then another NUL at 65557) because prefix_len + recall_len == 0 never fired. The new guards require len + prefix_len + 2 <= buf_size (prefix + one recall byte + NUL) and skip otherwise. After that check, remain = buf_size - len - prefix_len - 1 is at least 1, so the following memcpys cannot write past buf_size - 1. Empty recall still returns before any copy. I ran CI=true make test_agent, cppcheck 2.13 on src/core/agent.c, and GCC ASan+UBSan ./build/test_agent; all passed. GitHub static, test, release was still in progress at review time.
Should Fix
- Silent skip on a full prompt (
src/core/agent.c225–229). A SOUL.md that fills 65535 bytes now drops FTS memories on every turn with no stderr line.agent_persist_sessionlogs cap skips; the same shape (len,buf_size) would make this stuck state visible. Per-turn noise is the cost. - Truncation path is untested. The new test only covers the fully-full skip (65535-byte SOUL). The path that still writes prefix + clipped recall when
remain >= 1has no fixture.
Nice to Have
- After the
+ 2Uguard,if (recall_len == 0) return len;is unreachable for a non-emptyrecall_buf. Harmless defense; can stay. - Several
goto cleanuppaths intest_full_system_prompt_skips_memory_append_without_overflowhave no FAIL line (only the length mismatch andagent_runrc print). A failedmemory_save/ soul write looks like a silent test fail. - CHANGELOG says “clamping a truncated
Relevant memoriesprefix”. The prefix was not truncated;recall_lenwas clamped to 0 and the full 22-byte prefix was written past the allocation.
Positive Highlights
- The comment in
append_memories_to_systemstates why the old guard was dead, which is the part that would otherwise get “re-simplified” back into an overflow. test_full_system_prompt_skips_memory_append_without_overflowforces both a full prompt and a live FTS hit, then checksstrlen(messages[0].content)rather than a 4 KiBstrstr. That would have failed on the old OOB write even without ASan (strlenbecomes 65557).spy_chatnow copiesnbytes and writes the NUL itself. The previousmemcpy(..., n + 1)withn == SPY_CONTENT_SIZE - 1left the 4 KiB slot unterminated on this 64 KiB system message.
Verified locally: CI=true make test_agent; CC=gcc ASan+UBSan test_agent; cppcheck on agent.c. Not a merge blocker; GitHub CI should still be required-green before merge.
Sent by Cursor Automation: Adrianno’s personal code review
Cover the one-byte recall clip path. Refs: #75
There was a problem hiding this comment.
Cannot approve: GitHub blocks self-approval because this automation shares the cursor[bot] author of the PR. Cursor Bugbot and Cursor Security Agent both completed successfully with no findings that need human review. Reviewer adriannoes is already assigned.
Sent by Cursor Approval Agent: Pull Request Router and Approver


Summary
append_memories_to_systemwhen the system prompt already fillsSYSTEM_PROMPT_MAX(64 KiB). The old clamp setrecall_lento 0 but stillmemcpy'd the 22-byteRelevant memoriesprefix (and a NUL) past the heap buffer becauseprefix_len + recall_len == 0never fired.Reimplemented on current
development(includes #70). Not a cherry-pick of the Bot commit.Test plan
make test_agent— regressiontest_full_system_prompt_skips_memory_append_without_overflow(65535-byte SOUL + FTS memory; system prompt stays 65535)CI=true make testmake staticRefs: #75
Note
Medium Risk
Fixes memory-safety bug in system prompt assembly; behavior change skips memories when the prompt is full instead of overflowing.
Overview
Fixes a heap buffer overflow in
append_memories_to_systemwhen the system prompt already consumes the full 64 KiB (SYSTEM_PROMPT_MAX). Previously, recall text was clamped to zero but the 22-byteRelevant memories:prefix (and NUL) were still copied past the allocation; the ineffectiveprefix_len + recall_len == 0guard never ran because the prefix is non-empty.append_memories_to_systemnow bails out unless there is room for the prefix, at least one recall byte, and a terminator—loggingagent: skip memory injectionwhen injection is skipped. When space is tight, recall is truncated only if at least one byte can fit (no dangling header).Tests add regressions for a 65535-byte SOUL with FTS memory (prompt stays 65535, no overflow, skip log) and a near-full prompt that keeps a single clipped recall byte. CHANGELOG documents the fix (Refs: #75).
Reviewed by Cursor Bugbot for commit 7deaad7. Bugbot is set up for automated code reviews on this repo. Configure here.