/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ /** * Escapes a string for use in a regular expression. */ export function escapeRegex(text: string): string { return text.replace(/[-[\]{}()*+?.,\\^$|#\s"]/g, '\\$&'); } /** * Basic validation for regular expressions to prevent common ReDoS patterns. * This is a heuristic check and not a substitute for a full ReDoS scanner. */ export function isSafeRegExp(pattern: string): boolean { try { // 1. Ensure it's a valid regex new RegExp(pattern); } catch { return false; } // 2. Limit length to prevent extremely long regexes if (pattern.length > 2048) { return false; } // 3. Heuristic: Check for nested quantifiers which are a primary source of ReDoS. // Examples: (a+)+, (a|b)*, (.*)*, ([a-z]+)+ // We look for a group (...) followed by a quantifier (+, *, or {n,m}) // where the group itself contains a quantifier. // This matches a '(' followed by some content including a quantifier, then ')', // followed by another quantifier. const nestedQuantifierPattern = /\([^)]*[*+?{].*\)[*+?{]/; if (nestedQuantifierPattern.test(pattern)) { return false; } return true; } /** * Builds a list of args patterns for policy matching. * * This function handles the transformation of command prefixes and regexes into * the internal argsPattern representation used by the PolicyEngine. * * @param argsPattern An optional raw regex string for arguments. * @param commandPrefix An optional command prefix (or list of prefixes) to allow. * @param commandRegex An optional command regex string to allow. * @returns An array of string patterns (or undefined) for the PolicyEngine. */ export function buildArgsPatterns( argsPattern?: string, commandPrefix?: string | string[], commandRegex?: string, ): Array { if (commandPrefix) { const prefixes = Array.isArray(commandPrefix) ? commandPrefix : [commandPrefix]; return prefixes.map((prefix) => { // JSON.stringify safely encodes the prefix in quotes. // We remove ONLY the trailing quote to match it as an open prefix string. const encodedPrefix = JSON.stringify(prefix); const openQuotePrefix = encodedPrefix.substring( 0, encodedPrefix.length - 1, ); // Escape the exact JSON literal segment we expect to see const matchSegment = escapeRegex(`"command":${openQuotePrefix}`); // We allow [\s], ["], or the specific sequence [\"] (for escaped quotes // in JSON). We do NOT allow generic [\\], which would match "git\status" // -> "gitstatus". return `${matchSegment}(?:[\\s"]|\\\\")`; }); } if (commandRegex) { return [`"command":"${commandRegex}`]; } return [argsPattern]; } /** * Builds a regex pattern to match a specific parameter and value in tool arguments. * This is used to narrow tool approvals to specific parameters. * * @param paramName The name of the parameter. * @param value The value to match. * @returns A regex string that matches "": in a JSON string. */ export function buildParamArgsPattern( paramName: string, value: unknown, ): string { const encodedValue = JSON.stringify(value); // We wrap the JSON string in escapeRegex and prepend/append \\0 to explicitly // match top-level JSON properties generated by stableStringify, preventing // argument injection bypass attacks. return `\\\\0${escapeRegex(`"${paramName}":${encodedValue}`)}\\\\0`; } /** * Builds a regex pattern to match a specific file path in tool arguments. * This is used to narrow tool approvals for edit tools to specific files. * * @param filePath The relative path to the file. * @returns A regex string that matches "file_path":"" in a JSON string. */ export function buildFilePathArgsPattern(filePath: string): string { return buildParamArgsPattern('file_path', filePath); } /** * Builds a regex pattern to match a specific directory path in tool arguments. * This is used to narrow tool approvals for list_directory tool. * * @param dirPath The path to the directory. * @returns A regex string that matches "dir_path":"" in a JSON string. */ export function buildDirPathArgsPattern(dirPath: string): string { return buildParamArgsPattern('dir_path', dirPath); } /** * Builds a regex pattern to match a specific "pattern" in tool arguments. * This is used to narrow tool approvals for search tools like glob/grep to specific patterns. * * @param pattern The pattern to match. * @returns A regex string that matches "pattern":"" in a JSON string. */ export function buildPatternArgsPattern(pattern: string): string { return buildParamArgsPattern('pattern', pattern); }