Summary
Two SQL-safety bugs in the data layer: an identifier interpolated without an allowlist, and unescaped LIKE wildcards on caller-influenced text.
Findings
increment_recall_counts interpolates the table name (high)
store.py:2060-2071 — table is interpolated into SQL via f-string with no allowlist. It's a public method on MemoryStore; a caller passing a derived table name gets arbitrary SQL (table = "memory_entries; DROP TABLE memory_entries; --" runs as a multi-statement via psycopg). Internal callers pass literals today, but nothing enforces it.
- Fix:
assert table in {"memory_entries","conversations","delegations"}.
replace()/remove() don't escape LIKE wildcards (medium)
store.py:917, 942, 974 — build LIKE '%' || old_text || '%' without escaping %/_. old_text comes from memory-tool arguments (conversation-influenced).
- Scenario:
remove(old_text="%") deletes every entry in the (agent, target) scope; _ matches any character, so replaces/removes hit unintended rows.
- Fix: escape
\ % _ and add ESCAPE '\'.
Related: by-id operations lack agent scoping (medium)
store.py:1298-1315, 2024-2058 — fetch_full, confirm_entry, reject_entry take a bare sequential id with no agent_identity filter. (Cross-referenced from the security issue; fix location is the store layer.)
Suggested direction
Allowlist the table identifier; escape LIKE metacharacters; add agent_identity scoping to by-id reads/mutations.
Filed from the 2026-07 codebase review.
Summary
Two SQL-safety bugs in the data layer: an identifier interpolated without an allowlist, and unescaped
LIKEwildcards on caller-influenced text.Findings
increment_recall_countsinterpolates the table name (high)store.py:2060-2071—tableis interpolated into SQL via f-string with no allowlist. It's a public method onMemoryStore; a caller passing a derived table name gets arbitrary SQL (table = "memory_entries; DROP TABLE memory_entries; --"runs as a multi-statement via psycopg). Internal callers pass literals today, but nothing enforces it.assert table in {"memory_entries","conversations","delegations"}.replace()/remove()don't escape LIKE wildcards (medium)store.py:917, 942, 974— buildLIKE '%' || old_text || '%'without escaping%/_.old_textcomes from memory-tool arguments (conversation-influenced).remove(old_text="%")deletes every entry in the (agent, target) scope;_matches any character, so replaces/removes hit unintended rows.\ % _and addESCAPE '\'.Related: by-id operations lack agent scoping (medium)
store.py:1298-1315, 2024-2058—fetch_full,confirm_entry,reject_entrytake a bare sequential id with noagent_identityfilter. (Cross-referenced from the security issue; fix location is the store layer.)Suggested direction
Allowlist the table identifier; escape LIKE metacharacters; add
agent_identityscoping to by-id reads/mutations.Filed from the 2026-07 codebase review.