fix(security): use negative lookahead for CALL allowlist, remove dead CALL\s*\{ branch

- Replace two-regex CALL check with single negative-lookahead
  CYPHER_CALL_DISALLOWED_RE = /\bCALL\s+(?!db\.|apoc\.meta\.)/i
  This correctly blocks queries containing ANY disallowed CALL target,
  even when bundled alongside an allowed CALL db.* or CALL apoc.meta.*.
- Remove dead CALL\s*\{ alternative from CYPHER_WRITE_RE — the trailing
  \b never matched because { is a non-word character.
- Both copies updated identically.

Co-Authored-By: Allaun Silverfox <bigdataiscoming+9i37y6j2@protonmail.com>
This commit is contained in:
Devin AI 2026-06-15 00:47:53 +00:00
parent 3555cbb26c
commit 4986d75740
2 changed files with 6 additions and 8 deletions

View file

@ -197,13 +197,12 @@ router.post("/obsidian/search", async (req, res) => {
}
});
const CYPHER_WRITE_RE = /\b(CREATE|MERGE|DELETE|DETACH|SET|REMOVE|DROP|CALL\s*\{)\b/i;
const CYPHER_CALL_RE = /\bCALL\b/i;
const CYPHER_CALL_ALLOWED_RE = /\bCALL\s+(db\.|apoc\.meta\.)/i;
const CYPHER_WRITE_RE = /\b(CREATE|MERGE|DELETE|DETACH|SET|REMOVE|DROP)\b/i;
const CYPHER_CALL_DISALLOWED_RE = /\bCALL\s+(?!db\.|apoc\.meta\.)/i;
function cypherReadOnlyViolation(cypher) {
if (CYPHER_WRITE_RE.test(cypher)) return "readOnly mode forbids write clauses (CREATE/MERGE/DELETE/SET/REMOVE/DROP)";
if (CYPHER_CALL_RE.test(cypher) && !CYPHER_CALL_ALLOWED_RE.test(cypher)) return "readOnly mode only allows CALL db.* and CALL apoc.meta.*";
if (CYPHER_CALL_DISALLOWED_RE.test(cypher)) return "readOnly mode only allows CALL db.* and CALL apoc.meta.*";
return null;
}

View file

@ -197,13 +197,12 @@ router.post("/obsidian/search", async (req, res) => {
}
});
const CYPHER_WRITE_RE = /\b(CREATE|MERGE|DELETE|DETACH|SET|REMOVE|DROP|CALL\s*\{)\b/i;
const CYPHER_CALL_RE = /\bCALL\b/i;
const CYPHER_CALL_ALLOWED_RE = /\bCALL\s+(db\.|apoc\.meta\.)/i;
const CYPHER_WRITE_RE = /\b(CREATE|MERGE|DELETE|DETACH|SET|REMOVE|DROP)\b/i;
const CYPHER_CALL_DISALLOWED_RE = /\bCALL\s+(?!db\.|apoc\.meta\.)/i;
function cypherReadOnlyViolation(cypher) {
if (CYPHER_WRITE_RE.test(cypher)) return "readOnly mode forbids write clauses (CREATE/MERGE/DELETE/SET/REMOVE/DROP)";
if (CYPHER_CALL_RE.test(cypher) && !CYPHER_CALL_ALLOWED_RE.test(cypher)) return "readOnly mode only allows CALL db.* and CALL apoc.meta.*";
if (CYPHER_CALL_DISALLOWED_RE.test(cypher)) return "readOnly mode only allows CALL db.* and CALL apoc.meta.*";
return null;
}