From f2f610b27d7355b655eefa018c0d13ebf304a76f Mon Sep 17 00:00:00 2001 From: jreakin Date: Mon, 23 Mar 2026 03:31:56 -0500 Subject: [PATCH] =?UTF-8?q?fix:=20add=20/=20back=20to=20isSafeId=20?= =?UTF-8?q?=E2=80=94=20node=20IDs=20contain=20file=20paths?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Node IDs are generated as Label:filePath (e.g., Function:src/foo.ts:bar), so forward slashes are expected in legitimate IDs. The over-tightened regex was dropping all path-based IDs from Cypher queries. Co-Authored-By: Claude Opus 4.6 (1M context) --- gitnexus-web/src/components/ProcessesPanel.tsx | 4 ++-- gitnexus-web/test/unit/security-guards.test.ts | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/gitnexus-web/src/components/ProcessesPanel.tsx b/gitnexus-web/src/components/ProcessesPanel.tsx index b591531a68..4f3c851d99 100644 --- a/gitnexus-web/src/components/ProcessesPanel.tsx +++ b/gitnexus-web/src/components/ProcessesPanel.tsx @@ -11,8 +11,8 @@ import { useAppState } from '../hooks/useAppState'; import { ProcessFlowModal } from './ProcessFlowModal'; import type { ProcessData, ProcessStep } from '../lib/mermaid-generator'; -/** Validate that an ID contains only expected node identifier characters (no Cypher metacharacters, spaces, or slashes) */ -const isSafeId = (id: string): boolean => /^[a-zA-Z0-9_:.\-]+$/.test(id); +/** Validate that an ID contains only expected node identifier characters (no Cypher metacharacters or spaces) */ +const isSafeId = (id: string): boolean => /^[a-zA-Z0-9_:.\-/]+$/.test(id); export const ProcessesPanel = () => { const { graph, runQuery, setHighlightedNodeIds, highlightedNodeIds } = useAppState(); diff --git a/gitnexus-web/test/unit/security-guards.test.ts b/gitnexus-web/test/unit/security-guards.test.ts index 63ee4946c6..4f6a2fe06b 100644 --- a/gitnexus-web/test/unit/security-guards.test.ts +++ b/gitnexus-web/test/unit/security-guards.test.ts @@ -18,7 +18,7 @@ const validRelType = (t: string): boolean => (REL_TYPES as readonly string[]).includes(t); const isSafeId = (id: string): boolean => - /^[a-zA-Z0-9_:.\-]+$/.test(id); + /^[a-zA-Z0-9_:.\-/]+$/.test(id); const isWriteQuery = (cypher: string): boolean => /\b(CREATE|DELETE|SET|MERGE|REMOVE|DROP|DETACH)\b/.test(cypher.toUpperCase()); @@ -102,6 +102,8 @@ describe('isSafeId – identifier allowlist regex', () => { ['dotted name', 'Module:path.to.thing'], ['with hyphen', 'File:my-file.ts'], ['community id', 'comm_5'], + ['file path id', 'File:src/index.ts'], + ['nested path id', 'Function:src/utils/helpers.ts:doStuff'], ])('accepts valid ID: %s', (_desc, id) => { expect(isSafeId(id)).toBe(true); }); @@ -109,8 +111,7 @@ describe('isSafeId – identifier allowlist regex', () => { it.each([ ['with spaces', 'Process:my process'], ['with @ symbol', 'Module:@scope/pkg'], - ['with forward slash', 'File:src/index.ts'], - ])('rejects ID with overly permissive chars: %s', (_desc, id) => { + ])('rejects ID with unsafe chars: %s', (_desc, id) => { expect(isSafeId(id)).toBe(false); });