fix(polardb): escape id/user_name in get_children_with_embeddings to prevent Cypher injection#2124
fix(polardb): escape id/user_name in get_children_with_embeddings to prevent Cypher injection#2124sebastiondev wants to merge 2 commits into
Conversation
…prevent Cypher injection (CWE-89)
🤖 Open Code ReviewTarget: PR #2124 🔍 OpenCodeReview found 1 issue(s) in this PR. 1.
|
✅ Automated Test Results: PASSEDAll tests passed (2/2 executed). memos_python_core/changed-python-source: 2/2. Duration: 4s [advisory, non-gating] AI-generated tests on branch test/auto-gen-fd26f6e872521dd7-20260720074300: 35/35 passed — these do NOT affect the PR verdict; review the branch manually. Branch: |
|
Thanks for catching this — you're right. Fixed in ca94467: def escape_sql_string(value: str) -> str:
"""Escape single quotes and neutralize dollar-quote delimiters in SQL string."""
return value.replace("'", "''").replace("$$", "")Stripping (rather than rejecting) keeps behavior non-breaking for legitimate inputs that happen to contain |
Description
This PR fixes a Cypher/SQL injection vulnerability in
PolarDBGraphDB.get_children_with_embeddings(src/memos/graph_dbs/polardb.py).Prior to this change, the
idanduser_namearguments were interpolated directly into the Cypher query text embedded insidecypher('..$$..$$):Neither argument was escaped or bound as a parameter. A single quote in
idoruser_namebreaks out of the Cypher string literal, allowing an attacker to inject arbitrary Cypher clauses (and, through AGE's SQL surface, further SQL) into the executed statement.The rest of this file already uses a helper
escape_sql_string(value)(defined at line 96) for exactly this pattern — this one call site was missed. This patch applies the same helper here, matching the convention used by the hundreds of other Cypher call sites in the module. It also guards theuser_nameWHERE clause so it is only emitted when a value is actually available, preserving existing behavior when the config value is missing.Related issue: N/A — reporting via PR per the project's public contribution flow.
Type of change
Vulnerability details
cypher()).src/memos/graph_dbs/polardb.py→PolarDBGraphDB.get_children_with_embeddings(line 1171).WHERE p.id = '{id}'andAND p.user_name = '{user_name}' AND c.user_name = '{user_name}'.idanduser_nameare ordinary method parameters. They are passed through from higher-level memory APIs (tree/graph traversal helpers). There is no upstream sanitizer for either — the project's convention is to escape at the query site usingescape_sql_string, which every other Cypher-building method in this file does.Fix
idanduser_namewith the existingescape_sql_stringhelper before interpolating them into the Cypher literal.AND p.user_name = ... AND c.user_name = ...clause when auser_nameis actually resolved, so we don't emituser_name = ''when no tenant scope is configured.Diff is 10 additions / 2 deletions, one function, one file.
How Has This Been Tested?
Reproduction / verification steps (self-contained, no DB required — the vulnerability is purely in query construction):
Security analysis
idoruser_nameargument reaching this method can rewrite the Cypher predicate, causing arbitrary rows to be returned, cross-tenant reads (by breaking out of thep.user_name = '...'scope), or, depending on the AGE/PolarDB stacking behavior, execution of additional SQL statements.cypher('graph_name', $$ ... $$)form, whose inner Cypher body is a literal string and cannot be parameter-bound through the outer psycopg2 driver. This is why the module uses an escape helper everywhere — this PR just applies it consistently.Adversarial review
Before submitting, we tried to disprove this: we checked whether the arguments are constrained upstream (they aren't — they're plain method parameters used across the memory system), whether AGE's
cypher()form has some implicit escaping (it doesn't — the$$ ... $$body is a literal), and whether the surrounding module already covers this call (it does not — this is the only remaining call site missing the helper). We also confirmed the fix matches the existing pattern used by neighboring functions, so it should not surprise reviewers or change behavior for legitimate inputs.Checklist
Discovered by the Sebastion AI GitHub App.