From 4986d75740c88d4614c3d1e1484cc8a19b0747f7 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 15 Jun 2026 00:47:53 +0000 Subject: [PATCH] fix(security): use negative lookahead for CALL allowlist, remove dead CALL\s*\{ branch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .../topological-engine/neo4j_obsidian_connector_router.js | 7 +++---- .../topological-engine/neo4j_obsidian_connector_router.js | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/4-Infrastructure/NoDupeLabs/connectors/topological-engine/neo4j_obsidian_connector_router.js b/4-Infrastructure/NoDupeLabs/connectors/topological-engine/neo4j_obsidian_connector_router.js index e112ab95..2e53e45b 100644 --- a/4-Infrastructure/NoDupeLabs/connectors/topological-engine/neo4j_obsidian_connector_router.js +++ b/4-Infrastructure/NoDupeLabs/connectors/topological-engine/neo4j_obsidian_connector_router.js @@ -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; } diff --git a/5-Applications/nodupe/connectors/topological-engine/neo4j_obsidian_connector_router.js b/5-Applications/nodupe/connectors/topological-engine/neo4j_obsidian_connector_router.js index f30d4128..e0267c65 100644 --- a/5-Applications/nodupe/connectors/topological-engine/neo4j_obsidian_connector_router.js +++ b/5-Applications/nodupe/connectors/topological-engine/neo4j_obsidian_connector_router.js @@ -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; }