Skip to content

SQL safety: unallowlisted table identifier + unescaped LIKE wildcards + unscoped by-id ops #19

Open

Description

@codenamekt

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.

Activity

  1. added
    bugSomething isn't working
    priority: highImportant correctness/UX bug
    code-reviewFiled from the 2026-07 codebase review
    on Jul 7, 2026
  2. codenamekt commented on Jul 7, 2026

    @codenamekt
    OwnerAuthor

    Carried over from #13 (security follow-ups)

    PR #28 closed the network-surface parts of #13 — HTTP bearer-token auth (HEXUS_API_TOKEN), loopback-by-default bind, gated /metrics, and the memory_cleanup dry-run/confirm gate. #13 is now closed; the remaining, deeper isolation work lands here (this issue already owns the "unscoped by-id ops" item, which is the same theme).

    Remaining items now tracked in this issue:

    A. Agent identity is client-asserted (high/critical). X-Hermes-Session-Key is never read by the MCP server; every tool takes agent_identity as a plain caller-chosen string. Even an authenticated client can pass any identity. Fix: derive agent_identity server-side from the authenticated credential (map bearer token / session key → identity) and treat the tool argument as a scope hint within the caller's own identity, not a free choice. This is the root cause behind the by-id scoping below.

    B. By-id reads/mutations are unscoped (already listed here). store.fetch_full (memory_retrieve/headroom_retrieve), confirm_entry/reject_entry (memory_confirm/memory_reject), and memory_summarize_session operate on a bare sequential BIGSERIAL id with no agent_identity filter — enumerable cross-agent read/mutate. Add agent_identity scoping to these store methods and thread the caller's identity through the tools.

    C. agent_identity default asymmetry (medium). Empty agent_identity means "this agent" in memory_search/memory_count (tools.py:291,476) but "ALL agents" in memory_recall (tools.py:232-234). Make the empty-identity semantics consistent across tools (security-relevant: the "all agents" default is a silent cross-agent read).

    Together with the existing SQL-safety items in this issue (table-identifier allowlist, LIKE-wildcard escaping), this issue now covers "enforce real multi-agent isolation at the data layer."

  3. added
    priority: criticalMust fix; data loss, security, or broken release
    and removed
    priority: highImportant correctness/UX bug
    on Jul 7, 2026
  4. added a commit that references this issue on Jul 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: securityArea: securitybugSomething isn't workingcode-reviewFiled from the 2026-07 codebase reviewpriority: criticalMust fix; data loss, security, or broken release

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions