import type { ToolPolicy } from "@continuedev/terminal-security"; import { ALL_BUILT_IN_TOOLS } from "src/tools/allBuiltIns.js"; import { PermissionCheckResult, PermissionPolicy, ToolCallRequest, ToolPermissions, } from "./types.js"; /** * Checks if a tool name matches a pattern. * Supports wildcards (*) for pattern matching. * Also handles special Bash command patterns like "Bash(ls*)" */ export function matchesToolPattern( toolName: string, pattern: string, toolArguments?: Record, ): boolean { if (pattern === "*") return true; if (pattern === toolName) return true; // Handle special Bash command patterns like "Bash(ls*)" const bashCommandMatch = pattern.match(/^Bash\((.+)\)$/); if (bashCommandMatch) { // Check if this is a bash/terminal tool const isBashTool = toolName === "Bash"; if (isBashTool && toolArguments?.command) { const commandPattern = bashCommandMatch[1]; const command = toolArguments.command; // Handle command patterns with wildcards if (commandPattern.includes("*") || commandPattern.includes("?")) { // Escape all regex metacharacters except * and ? const escaped = commandPattern.replace(/[.+^${}()|[\]\\]/g, "\\$&"); // Convert * and ? to their regex equivalents const regexPattern = escaped.replace(/\*/g, ".*").replace(/\?/g, "."); const regex = new RegExp(`^${regexPattern}$`); return regex.test(command); } // Exact command match return command === commandPattern; } return false; } // Handle regular wildcard patterns like "external_*" if (pattern.includes("*") || pattern.includes("?")) { // Escape all regex metacharacters except * and ? const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&"); // Convert * and ? to their regex equivalents const regexPattern = escaped.replace(/\*/g, ".*").replace(/\?/g, "."); const regex = new RegExp(`^${regexPattern}$`); return regex.test(toolName); } return false; } /** * Checks if tool call arguments match the specified patterns. * Supports glob patterns with * and ? wildcards. */ export function matchesArguments( args: Record, patterns?: Record, ): boolean { if (!patterns) return true; for (const [key, pattern] of Object.entries(patterns)) { const argValue = args[key]; if (pattern === "*") continue; // Wildcard matches anything // Handle glob patterns with wildcards (only for string patterns) if ( typeof pattern === "string" && (pattern.includes("*") || pattern.includes("?")) ) { // Convert argValue to string for pattern matching const stringValue = String(argValue ?? ""); // Escape all regex metacharacters except * and ? const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&"); // Convert * and ? to their regex equivalents const regexPattern = escaped.replace(/\*/g, ".*").replace(/\?/g, "."); const regex = new RegExp(`^${regexPattern}$`); if (!regex.test(stringValue)) { return false; } } else { // Exact match for non-glob patterns (preserve original behavior) if (argValue !== pattern) { return false; } } } return true; } /** * Converts CLI's PermissionPolicy to core's ToolPolicy */ function permissionPolicyToToolPolicy( permission: PermissionPolicy, ): ToolPolicy { switch (permission) { case "allow": return "allowedWithoutPermission"; case "ask": return "allowedWithPermission"; case "exclude": return "disabled"; default: return "allowedWithPermission"; } } /** * Evaluates a tool call request against a set of permission policies. * Returns the permission for the first matching policy. */ export function checkToolPermission( toolCall: ToolCallRequest, permissions: ToolPermissions, ): PermissionCheckResult { const policies = permissions.policies; // First, get the base permission from static policies let basePermission: PermissionPolicy = "ask"; let matchedPolicy = undefined; for (const policy of policies) { if ( matchesToolPattern(toolCall.name, policy.tool, toolCall.arguments) && matchesArguments(toolCall.arguments, policy.argumentMatches) ) { basePermission = policy.permission; matchedPolicy = policy; break; } } // Check if tool has dynamic policy evaluation const tool = ALL_BUILT_IN_TOOLS.find((t) => t.name === toolCall.name); if (tool?.evaluateToolCallPolicy) { // Convert CLI permission to core policy const basePolicy = permissionPolicyToToolPolicy(basePermission); // Evaluate the dynamic policy const evaluatedPolicy = tool.evaluateToolCallPolicy( basePolicy, toolCall.arguments, ); // If dynamic evaluation says disabled, that ALWAYS takes precedence if (evaluatedPolicy === "disabled") { return { permission: "exclude", matchedPolicy, }; } // Otherwise, user preference wins - return the original base permission return { permission: basePermission, matchedPolicy, }; } // No dynamic evaluation, return static result return { permission: basePermission, matchedPolicy, }; }