142 lines
3.6 KiB
TypeScript
142 lines
3.6 KiB
TypeScript
// src/permissions/cli.ts
|
|
// CLI-level permission overrides from command-line flags
|
|
// These take precedence over settings.json but not over enterprise managed policies
|
|
|
|
import {
|
|
canonicalToolName,
|
|
isFileToolName,
|
|
isShellToolName,
|
|
} from "./canonical";
|
|
import { normalizePermissionRule } from "./rule-normalization";
|
|
|
|
/**
|
|
* CLI permission overrides that are set via --allowedTools and --disallowedTools flags.
|
|
* These rules override settings.json permissions for the current session.
|
|
*/
|
|
class CliPermissions {
|
|
private allowedTools: string[] = [];
|
|
private disallowedTools: string[] = [];
|
|
|
|
/**
|
|
* Parse and set allowed tools from CLI flag
|
|
* Format: "Bash,Read" or "Bash(npm install),Read(src/**)"
|
|
*/
|
|
setAllowedTools(toolsString: string): void {
|
|
this.allowedTools = this.parseToolList(toolsString);
|
|
}
|
|
|
|
/**
|
|
* Parse and set disallowed tools from CLI flag
|
|
* Format: "WebFetch,Bash(curl:*)"
|
|
*/
|
|
setDisallowedTools(toolsString: string): void {
|
|
this.disallowedTools = this.parseToolList(toolsString);
|
|
}
|
|
|
|
/**
|
|
* Parse comma-separated tool list into individual patterns
|
|
* Handles: "Bash,Read" and "Bash(npm install),Read(src/**)"
|
|
*
|
|
* Special handling:
|
|
* - "Bash" without params becomes "Bash(:*)" to match all Bash commands
|
|
* - "Read" without params becomes "Read" (matches all Read calls)
|
|
*/
|
|
private parseToolList(toolsString: string): string[] {
|
|
if (!toolsString) return [];
|
|
|
|
const tools: string[] = [];
|
|
let current = "";
|
|
let depth = 0;
|
|
|
|
// Parse comma-separated list, respecting parentheses
|
|
for (let i = 0; i < toolsString.length; i++) {
|
|
const char = toolsString[i];
|
|
|
|
if (char === "(") {
|
|
depth++;
|
|
current += char;
|
|
} else if (char === ")") {
|
|
depth--;
|
|
current += char;
|
|
} else if (char === "," && depth === 0) {
|
|
// Only split on commas outside parentheses
|
|
if (current.trim()) {
|
|
tools.push(this.normalizePattern(current.trim()));
|
|
}
|
|
current = "";
|
|
} else {
|
|
current += char;
|
|
}
|
|
}
|
|
|
|
// Add the last tool
|
|
if (current.trim()) {
|
|
tools.push(this.normalizePattern(current.trim()));
|
|
}
|
|
|
|
return tools;
|
|
}
|
|
|
|
/**
|
|
* Normalize a tool pattern.
|
|
* - "Bash" becomes "Bash(:*)" to match all commands
|
|
* - File tools (Read, Write, Edit, Glob, Grep) become "ToolName(**)" to match all files
|
|
* - Tool patterns with parentheses stay as-is
|
|
*/
|
|
private normalizePattern(pattern: string): string {
|
|
const trimmed = pattern.trim();
|
|
|
|
// If pattern has parentheses, keep as-is
|
|
if (trimmed.includes("(")) {
|
|
return normalizePermissionRule(trimmed);
|
|
}
|
|
|
|
const canonicalTool = canonicalToolName(trimmed);
|
|
|
|
// Bash/shell aliases without parentheses need wildcard to match all commands
|
|
if (isShellToolName(canonicalTool)) {
|
|
return "Bash(:*)";
|
|
}
|
|
|
|
// File tools need wildcard to match all files
|
|
if (isFileToolName(canonicalTool)) {
|
|
return `${canonicalTool}(**)`;
|
|
}
|
|
|
|
// All other bare tool names stay as-is
|
|
return canonicalTool;
|
|
}
|
|
|
|
/**
|
|
* Get all allowed tool patterns
|
|
*/
|
|
getAllowedTools(): string[] {
|
|
return [...this.allowedTools];
|
|
}
|
|
|
|
/**
|
|
* Get all disallowed tool patterns
|
|
*/
|
|
getDisallowedTools(): string[] {
|
|
return [...this.disallowedTools];
|
|
}
|
|
|
|
/**
|
|
* Check if any CLI overrides are set
|
|
*/
|
|
hasOverrides(): boolean {
|
|
return this.allowedTools.length > 0 || this.disallowedTools.length > 0;
|
|
}
|
|
|
|
/**
|
|
* Clear all CLI permission overrides
|
|
*/
|
|
clear(): void {
|
|
this.allowedTools = [];
|
|
this.disallowedTools = [];
|
|
}
|
|
}
|
|
|
|
// Singleton instance
|
|
export const cliPermissions = new CliPermissions();
|