{"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../../src/core/workspace/guard.ts"],"names":[],"mappings":"AAAA,iFAAiF;AAMjF,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAErD;AAED,qEAAqE;AACrE,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAKvD;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAe5E;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAUlF;AAED,sFAAsF;AACtF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAS1D","sourcesContent":["/** Content addressing and lightweight security guards for workspace indexing. */\n\nimport { createHash } from \"node:crypto\";\nimport { realpathSync } from \"node:fs\";\nimport path from \"node:path\";\n\nexport function sha256(input: string | Buffer): string {\n\treturn createHash(\"sha256\").update(input).digest(\"hex\");\n}\n\n/** Constant-time string equality (for internal integrity checks). */\nexport function safeEqual(a: string, b: string): boolean {\n\tif (a.length !== b.length) return false;\n\tlet diff = 0;\n\tfor (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);\n\treturn diff === 0;\n}\n\n/**\n * Validate that a workspace-relative target stays inside the workspace root.\n * Rejects absolute paths, parent traversal, and empty targets.\n */\nexport function assertInsideWorkspace(root: string, relative: string): string {\n\tconst normalized = relative.replace(/\\\\/g, \"/\").replace(/^\\/+/, \"\");\n\tif (!normalized || normalized.includes(\"\\u0000\")) {\n\t\tthrow new Error(`Invalid workspace-relative path: ${JSON.stringify(relative)}`);\n\t}\n\tconst parts = normalized.split(\"/\");\n\tif (parts.some((p) => p === \"..\")) {\n\t\tthrow new Error(`Path escapes workspace: ${JSON.stringify(relative)}`);\n\t}\n\tconst resolved = path.resolve(root, ...parts);\n\tconst rootResolved = path.resolve(root);\n\tif (!resolved.startsWith(rootResolved + path.sep) && resolved !== rootResolved) {\n\t\tthrow new Error(`Path escapes workspace: ${JSON.stringify(relative)}`);\n\t}\n\treturn parts.join(\"/\");\n}\n\n/**\n * Detect a symlink escape: a resolved real path that leaves the workspace root.\n * Returns the escaped path or null when safe.\n */\nexport function detectSymlinkEscape(root: string, candidate: string): string | null {\n\ttry {\n\t\tconst rootReal = realpathSync(root);\n\t\tconst candReal = realpathSync(candidate);\n\t\tif (candReal === rootReal) return null;\n\t\tif (!candReal.startsWith(rootReal + path.sep)) return candReal;\n\t\treturn null;\n\t} catch {\n\t\treturn null; // path does not exist yet — treat as not-an-escape\n\t}\n}\n\n/** Reject characters that could enable an FTS/query injection from untrusted text. */\nexport function sanitizeQueryTerm(term: string): string {\n\treturn term.replace(/[\"'()*:^<>-]/g, \" \");\n}\n\nexport function isSuperficiallyBinary(buf: Buffer): boolean {\n\tconst sample = buf.subarray(0, 8192);\n\tfor (let i = 0; i < sample.length; i++) {\n\t\tconst b = sample[i];\n\t\tif (b === 0) return true;\n\t\t// High control byte ratio heuristic excluding common whitespace/escapes.\n\t\tif (b < 0x09 || (b > 0x0d && b < 0x20)) return true;\n\t}\n\treturn false;\n}\n"]}