Skip to content

fix(agent): skip memory injection when system prompt buffer is full - #75

Merged
adriannoes merged 3 commits into
developmentfrom
cursor/high-severity-issues-5d6d
Sep 13, 2026
Merged

adriannoes merged 3 commits into
developmentfrom
cursor/high-severity-issues-5d6d

Conversation

@cursor

@cursor cursor Bot commented Aug 27, 2026 •

Copy link
Copy Markdown

Summary

  • Skip append_memories_to_system when the system prompt already fills SYSTEM_PROMPT_MAX (64 KiB). The old clamp set recall_len to 0 but still memcpy'd the 22-byte Relevant memories prefix (and a NUL) past the heap buffer because prefix_len + recall_len == 0 never fired.
  • Truncate recall only when prefix + at least one recall byte + NUL fit. Do not write a dangling header.

Reimplemented on current development (includes #70). Not a cherry-pick of the Bot commit.

Test plan

  • make test_agent — regression test_full_system_prompt_skips_memory_append_without_overflow (65535-byte SOUL + FTS memory; system prompt stays 65535)
  • CI=true make test
  • make static

Refs: #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_system when the system prompt already consumes the full 64 KiB (SYSTEM_PROMPT_MAX). Previously, recall text was clamped to zero but the 22-byte Relevant memories: prefix (and NUL) were still copied past the allocation; the ineffective prefix_len + recall_len == 0 guard never ran because the prefix is non-empty.

append_memories_to_system now bails out unless there is room for the prefix, at least one recall byte, and a terminator—logging agent: skip memory injection when 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.

@adriannoes
adriannoes force-pushed the cursor/high-severity-issues-5d6d branch from 447dd09 to cd236d9 Compare September 13, 2026 19:51
@adriannoes
adriannoes changed the base branch from main to development September 13, 2026 19:51
@adriannoes
adriannoes marked this pull request as ready for review September 13, 2026 19:51
@adriannoes
adriannoes self-requested a review as a code owner September 13, 2026 19:51

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

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. 🤖 ✨

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

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

  1. Silent skip on a full prompt (src/core/agent.c 225–229). A SOUL.md that fills 65535 bytes now drops FTS memories on every turn with no stderr line. agent_persist_session logs cap skips; the same shape (len, buf_size) would make this stuck state visible. Per-turn noise is the cost.
  2. 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 >= 1 has no fixture.

Nice to Have

  • After the + 2U guard, if (recall_len == 0) return len; is unreachable for a non-empty recall_buf. Harmless defense; can stay.
  • Several goto cleanup paths in test_full_system_prompt_skips_memory_append_without_overflow have no FAIL line (only the length mismatch and agent_run rc print). A failed memory_save / soul write looks like a silent test fail.
  • CHANGELOG says “clamping a truncated Relevant memories prefix”. The prefix was not truncated; recall_len was clamped to 0 and the full 22-byte prefix was written past the allocation.

Positive Highlights

  • The comment in append_memories_to_system states 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_overflow forces both a full prompt and a live FTS hit, then checks strlen(messages[0].content) rather than a 4 KiB strstr. That would have failed on the old OOB write even without ASan (strlen becomes 65557).
  • spy_chat now copies n bytes and writes the NUL itself. The previous memcpy(..., n + 1) with n == SPY_CONTENT_SIZE - 1 left 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.

Open in Web View Automation 

Sent by Cursor Automation: Adrianno’s personal code review

Comment thread src/core/agent.c Outdated
Comment thread tests/test_agent.c
Cover the one-byte recall clip path.

Refs: #75

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

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.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@adriannoes
adriannoes merged commit 805edcb into development Sep 13, 2026
4 checks passed
@adriannoes
adriannoes deleted the cursor/high-severity-issues-5d6d branch September 13, 2026 20:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant