@warlock.js/ai-workspace 5.45.0 → 5.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/README.md +2 -0
- package/cjs/index.cjs +514 -54
- package/cjs/index.cjs.map +1 -1
- package/esm/backends/local.d.mts.map +1 -1
- package/esm/backends/local.mjs +24 -1
- package/esm/backends/local.mjs.map +1 -1
- package/esm/contracts/index.d.mts +4 -4
- package/esm/contracts/tool-io.type.d.mts +6 -0
- package/esm/contracts/tool-io.type.d.mts.map +1 -1
- package/esm/contracts/workspace-backend.contract.d.mts +20 -0
- package/esm/contracts/workspace-backend.contract.d.mts.map +1 -1
- package/esm/contracts/workspace-edit.type.d.mts +30 -0
- package/esm/contracts/workspace-edit.type.d.mts.map +1 -1
- package/esm/contracts/workspace-ops.contract.d.mts +7 -2
- package/esm/contracts/workspace-ops.contract.d.mts.map +1 -1
- package/esm/contracts/workspace-policy.type.d.mts +23 -0
- package/esm/contracts/workspace-policy.type.d.mts.map +1 -1
- package/esm/contracts/workspace.contract.d.mts +4 -1
- package/esm/contracts/workspace.contract.d.mts.map +1 -1
- package/esm/hooks/shell-output.mjs +113 -0
- package/esm/hooks/shell-output.mjs.map +1 -0
- package/esm/index.d.mts +4 -4
- package/esm/ops.d.mts.map +1 -1
- package/esm/ops.mjs +108 -40
- package/esm/ops.mjs.map +1 -1
- package/esm/policy/shell-check.d.mts +2 -0
- package/esm/policy/shell-check.d.mts.map +1 -1
- package/esm/policy/shell-check.mjs +59 -5
- package/esm/policy/shell-check.mjs.map +1 -1
- package/esm/search/walk.mjs +209 -0
- package/esm/search/walk.mjs.map +1 -0
- package/esm/shell-jobs.d.mts +1 -1
- package/esm/shell-jobs.d.mts.map +1 -1
- package/esm/shell-jobs.mjs +1 -1
- package/esm/shell-jobs.mjs.map +1 -1
- package/esm/tools/glob.mjs +5 -4
- package/esm/tools/glob.mjs.map +1 -1
- package/esm/tools/grep.mjs +3 -2
- package/esm/tools/grep.mjs.map +1 -1
- package/esm/tools/run-shell.d.mts.map +1 -1
- package/esm/tools/run-shell.mjs +2 -1
- package/esm/tools/run-shell.mjs.map +1 -1
- package/esm/workspace.mjs +2 -2
- package/esm/workspace.mjs.map +1 -1
- package/llms-full.txt +36 -17
- package/package.json +3 -3
- package/skills/use-a-workspace/SKILL.md +36 -17
|
@@ -220,6 +220,53 @@ var Refusal = class extends Error {
|
|
|
220
220
|
this.refusedPath = refusedPath;
|
|
221
221
|
}
|
|
222
222
|
};
|
|
223
|
+
/**
|
|
224
|
+
* The first word of `command` that has an unquoted backslash before a
|
|
225
|
+
* letter, digit or dot inside it (a Windows-style path bash reads as
|
|
226
|
+
* escapes), with the same word spelled with forward slashes. Quoted text
|
|
227
|
+
* and `\ ` style escapes (a backslash before a space, quote, `$` and the
|
|
228
|
+
* like) are left alone.
|
|
229
|
+
*/
|
|
230
|
+
function findWindowsStylePath(command) {
|
|
231
|
+
let quote = null;
|
|
232
|
+
let start = 0;
|
|
233
|
+
let found = false;
|
|
234
|
+
let suggestion = "";
|
|
235
|
+
const finish = (end) => found ? {
|
|
236
|
+
word: command.slice(start, end),
|
|
237
|
+
suggestion
|
|
238
|
+
} : null;
|
|
239
|
+
for (let index = 0; index < command.length; index++) {
|
|
240
|
+
const char = command[index];
|
|
241
|
+
if (quote === null && /[\s;&|<>()]/.test(char)) {
|
|
242
|
+
const hit = finish(index);
|
|
243
|
+
if (hit !== null) return hit;
|
|
244
|
+
start = index + 1;
|
|
245
|
+
found = false;
|
|
246
|
+
suggestion = "";
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (quote === "'") {
|
|
250
|
+
suggestion += char;
|
|
251
|
+
quote = char === "'" ? null : quote;
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (char === "\\") {
|
|
255
|
+
const next = command[index + 1] ?? "";
|
|
256
|
+
if (quote === null && index > start && /[A-Za-z0-9.]/.test(next)) {
|
|
257
|
+
found = true;
|
|
258
|
+
suggestion += "/";
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
suggestion += char + next;
|
|
262
|
+
index++;
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
if (char === "\"" || char === "'") quote = quote === null ? char : quote === char ? null : quote;
|
|
266
|
+
suggestion += char;
|
|
267
|
+
}
|
|
268
|
+
return finish(command.length);
|
|
269
|
+
}
|
|
223
270
|
/** Per-line checker state. */
|
|
224
271
|
var LineChecker = class {
|
|
225
272
|
constructor(policy, shell, line, dialect, interpreter, remembered = []) {
|
|
@@ -709,9 +756,14 @@ function checkSimpleCommand(checker, simple, cwds, dialect) {
|
|
|
709
756
|
*/
|
|
710
757
|
function checkShellCommand(policy, command, options) {
|
|
711
758
|
const resolved = options?.resolved ?? resolveShell(policy.shell, options?.host ?? currentShellHost());
|
|
712
|
-
return runCheck(policy, command, resolved.interpreter, resolved.dialect, options?.remembered);
|
|
759
|
+
return runCheck(policy, command, resolved.interpreter, resolved.dialect, options?.remembered, options?.platform ?? options?.host?.platform ?? process.platform);
|
|
760
|
+
}
|
|
761
|
+
/** Bash on Windows reads `ab\\x` as an escape, not a path: refuse and show the forward-slash form. */
|
|
762
|
+
function refuseWindowsStylePath(command) {
|
|
763
|
+
const hit = findWindowsStylePath(command);
|
|
764
|
+
if (hit !== null) throw new Refusal("unsupported-syntax", `\`${hit.word}\` uses Windows-style backslashes, which bash reads as escapes and so runs a different path. Write the path with forward slashes: \`${hit.suggestion}\` (or quote it).`);
|
|
713
765
|
}
|
|
714
|
-
function runCheck(policy, command, interpreter, dialect, remembered) {
|
|
766
|
+
function runCheck(policy, command, interpreter, dialect, remembered, platform = process.platform) {
|
|
715
767
|
const shell = policy.shell;
|
|
716
768
|
if (!shell) return {
|
|
717
769
|
allowed: false,
|
|
@@ -725,8 +777,10 @@ function runCheck(policy, command, interpreter, dialect, remembered) {
|
|
|
725
777
|
const checker = new LineChecker(policy, shell, command, shellMode ? dialect : "posix", interpreter, remembered);
|
|
726
778
|
try {
|
|
727
779
|
if (command.trim() === "") throw new Refusal("not-allowed", "The command is empty.");
|
|
728
|
-
if (shellMode)
|
|
729
|
-
|
|
780
|
+
if (shellMode) {
|
|
781
|
+
if (interpreter === "bash" && platform === "win32") refuseWindowsStylePath(command);
|
|
782
|
+
checkShellLine(checker, policy, command, dialect);
|
|
783
|
+
} else {
|
|
730
784
|
const argv = tokenizeCommand(command);
|
|
731
785
|
if (argv === null) throw argvModeRefusal(command);
|
|
732
786
|
checker.checkArgv(argv, argv.map(() => false), 0);
|
|
@@ -786,5 +840,5 @@ function classifyShellCommand(command, policy, host) {
|
|
|
786
840
|
}
|
|
787
841
|
|
|
788
842
|
//#endregion
|
|
789
|
-
export { checkShellCommand, classifyShellCommand, executableBasename, shellDialect };
|
|
843
|
+
export { checkShellCommand, classifyShellCommand, executableBasename, findWindowsStylePath, shellDialect };
|
|
790
844
|
//# sourceMappingURL=shell-check.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell-check.mjs","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/policy/shell-check.ts"],"sourcesContent":["import path from \"node:path\";\nimport type { WorkspaceShellRefusalReason } from \"../errors\";\nimport type {\n WorkspaceCommandPattern,\n WorkspaceCommandRule,\n WorkspacePolicy,\n WorkspaceShellAsk,\n WorkspaceShellClassification,\n WorkspaceShellPolicy,\n} from \"../contracts\";\nimport { isInsideJailLexically } from \"./jail\";\nimport {\n canonicalPowerShellArgv,\n normalizePowerShellCd,\n POWERSHELL_CD_NAMES,\n POWERSHELL_DIRECTORY_STACK,\n} from \"./powershell-aliases\";\nimport {\n currentShellHost,\n resolveShell,\n type ResolvedShell,\n type ResolvedShellInterpreter,\n type ShellHost,\n} from \"./shell-interpreter\";\nimport {\n parseShellLine,\n type ShellDialect,\n type ShellSegment,\n type ShellSimpleCommand,\n type ShellWord,\n} from \"./shell-parser\";\nimport { analyzeArgvCommand, tokenizeCommand } from \"./tokenize-command\";\n\n/** How a shell-mode line touches a path the jail has to vet. */\nexport type WorkspaceShellPathAccess = \"read\" | \"write\" | \"cwd\";\n\n/** A path a shell-mode line redirects to/from or `cd`s into. */\nexport interface WorkspaceShellPath {\n /** Absolute path, resolved lexically against the workspace root (or an earlier `cd`). */\n path: string;\n /** What the line does with it. */\n access: WorkspaceShellPathAccess;\n /** The target as written in the line. */\n target: string;\n}\n\n/**\n * The shell policy's verdict on one command line — see\n * {@link checkShellCommand}.\n */\nexport interface WorkspaceShellCheck {\n /** Whether the line may run (path targets still get a symlink-aware recheck in `exec`). */\n allowed: boolean;\n /** Why it was refused. */\n reason?: WorkspaceShellRefusalReason;\n /** Model-facing refusal text: the cause, and how to fix the command. */\n message?: string;\n /** The executable the refusal is about, when there is one. */\n executable?: string;\n /** The redirection target or `cd` directory a `\"path-escape\"` refusal is about. */\n path?: string;\n /**\n * Commands that need a decision from `shell.onAsk` before the line may run.\n * When non-empty, `allowed` is `false` with reason `\"approval-required\"`;\n * the ops layer asks, and refuses with `\"approval-denied\"` if the answer is no.\n */\n asks: WorkspaceShellAsk[];\n /** The argv of every simple command checked, in order (wrapped commands included). */\n commands: string[][];\n /** Shell mode: every redirection target and `cd` directory, for the jail recheck. */\n paths: WorkspaceShellPath[];\n}\n\n/**\n * Programs and shell builtins that run a command or code the policy cannot\n * see (a script string, a file, a command read from input, another user).\n * With deny rules in force they would hide a denied command, so they are\n * refused unless the policy names them in `allow`.\n */\nconst OPAQUE_RUNNERS = new Set([\n // Shells and interpreters of command strings.\n \"sh\",\n \"bash\",\n \"dash\",\n \"zsh\",\n \"ksh\",\n \"mksh\",\n \"ash\",\n \"yash\",\n \"posh\",\n \"fish\",\n \"csh\",\n \"tcsh\",\n \"busybox\",\n \"cmd\",\n \"powershell\",\n \"pwsh\",\n \"wsl\",\n // Run a command as someone else, elsewhere, or out of the process group.\n \"sudo\",\n \"doas\",\n \"su\",\n \"runuser\",\n \"pkexec\",\n \"runas\",\n \"chroot\",\n \"nsenter\",\n \"unshare\",\n \"setsid\",\n \"systemd-run\",\n \"start-stop-daemon\",\n \"daemonize\",\n // Run commands built from input or on a schedule.\n \"xargs\",\n \"parallel\",\n \"watch\",\n \"script\",\n \"expect\",\n \"at\",\n \"batch\",\n \"screen\",\n \"tmux\",\n \"stdbuf\",\n \"flock\",\n]);\n\n/**\n * PowerShell cmdlets and aliases that run code or commands the policy cannot\n * see, or change what later names mean (lowercase).\n */\nconst POWERSHELL_OPAQUE = new Set([\n \"iex\",\n \"invoke-expression\",\n \"invoke-command\",\n \"icm\",\n \"start-process\",\n \"saps\",\n \"start\",\n \"add-type\",\n \"set-alias\",\n \"sal\",\n \"new-alias\",\n \"nal\",\n \"invoke-item\",\n \"ii\",\n]);\n\n/** POSIX shell builtins that evaluate code or change how later names resolve. */\nconst SHELL_OPAQUE_BUILTINS = new Set([\n \"eval\",\n \".\",\n \"source\",\n \"alias\",\n \"unalias\",\n \"hash\",\n \"enable\",\n \"trap\",\n \"let\",\n \"declare\",\n \"typeset\",\n \"local\",\n \"set\",\n \"shopt\",\n \"fc\",\n \"bind\",\n \"complete\",\n \"compgen\",\n \"compopt\",\n \"mapfile\",\n \"readarray\",\n]);\n\n/** cmd.exe builtins that run commands or change the environment. */\nconst CMD_OPAQUE_BUILTINS = new Set([\"call\", \"start\", \"set\", \"pushd\", \"popd\"]);\n\n/**\n * Shell builtins whose arguments bash may evaluate as arithmetic (array\n * subscripts such as `a[$(cmd)]`): their arguments must be literal and free\n * of `[`.\n */\nconst ARITHMETIC_SENSITIVE_BUILTINS = new Set([\n \"test\",\n \"[\",\n \"printf\",\n \"read\",\n \"unset\",\n \"export\",\n \"readonly\",\n \"getopts\",\n \"wait\",\n \"kill\",\n \"shift\",\n \"return\",\n \"exit\",\n \"break\",\n \"continue\",\n \"ulimit\",\n]);\n\n/**\n * Variables a shell-mode line may not assign: they change how the shell\n * parses, traces or resolves what follows (`CDPATH` would move `cd` out of\n * the jail's sight, `PS4` is expanded — command substitutions included —\n * on every traced line, `IFS` re-splits expansions).\n */\nconst SENSITIVE_VARIABLES = new Set([\n \"CDPATH\",\n \"ENV\",\n \"BASH_ENV\",\n \"SHELLOPTS\",\n \"BASHOPTS\",\n \"IFS\",\n \"PS1\",\n \"PS2\",\n \"PS3\",\n \"PS4\",\n \"PROMPT_COMMAND\",\n \"BASH_XTRACEFD\",\n \"GLOBIGNORE\",\n \"EXECIGNORE\",\n \"BASH_LOADABLES_PATH\",\n]);\n\n/** Redirection targets that are always fine even though they sit outside the jail. */\nconst DEVICE_TARGETS = new Set([\"/dev/null\", \"/dev/stdout\", \"/dev/stderr\"]);\n\n/** cmd.exe's null device. */\nconst CMD_DEVICE_TARGETS = new Set([\"nul\"]);\n\n/** Deepest chain of wrappers (`env time nice …`) the policy unwraps. */\nconst MAX_WRAPPER_DEPTH = 8;\n\n/** Most distinct directories a line may `cd` between. */\nconst MAX_CD_CANDIDATES = 32;\n\n/** Longest command shown verbatim in a refusal before it is abbreviated. */\nconst MAX_SHOWN_COMMAND = 48;\n\n/** The dialect `interpreter: \"sh\"` parses on this platform. */\nexport function shellDialect(): ShellDialect {\n return process.platform === \"win32\" ? \"cmd\" : \"posix\";\n}\n\n/**\n * Reduce an argv's first element to the basename the allow/deny policy is\n * keyed on. `\"npm\"` → `\"npm\"`; `\"/usr/bin/node\"` → `\"node\"`; `\"node.exe\"`\n * → `\"node\"` (the `.exe`/`.cmd`/`.bat`/`.com` Windows extension is\n * stripped). The cmd dialect splits Windows paths too.\n */\nexport function executableBasename(firstToken: string, dialect: ShellDialect = \"posix\"): string {\n const base =\n dialect === \"cmd\" || dialect === \"powershell\" || dialect === \"powershell5\"\n ? path.win32.basename(firstToken)\n : path.basename(firstToken);\n\n return base.replace(\n dialect === \"powershell\" || dialect === \"powershell5\"\n ? /\\.(exe|cmd|bat|com|ps1)$/i\n : /\\.(exe|cmd|bat|com)$/i,\n \"\",\n );\n}\n\n/** Show a command in a refusal, abbreviated at a word boundary when long. */\nfunction show(source: string): string {\n const flat = source.replace(/\\s+/g, \" \").trim();\n\n if (flat.length <= MAX_SHOWN_COMMAND) {\n return `\\`${flat}\\``;\n }\n\n const cut = flat.lastIndexOf(\" \", MAX_SHOWN_COMMAND);\n\n return `\\`${flat.slice(0, cut > 0 ? cut : MAX_SHOWN_COMMAND)} …\\``;\n}\n\n/** Compile `denyPatterns` once per check. */\ninterface CompiledPattern {\n source: WorkspaceCommandPattern;\n test(argv: string[], dialect: ShellDialect): boolean;\n}\n\nfunction compilePattern(pattern: WorkspaceCommandPattern): CompiledPattern {\n if (pattern instanceof RegExp) {\n const regex = new RegExp(pattern.source, pattern.flags.replace(/[gy]/g, \"\"));\n\n return {\n source: pattern,\n test: (argv, dialect) =>\n regex.test([executableBasename(argv[0] ?? \"\", dialect), ...argv.slice(1)].join(\" \")),\n };\n }\n\n const tokens = tokenizeCommand(pattern);\n\n if (tokens === null) {\n // Not representable as an argv: match it as text inside the joined argv.\n return {\n source: pattern,\n test: (argv, dialect) =>\n [executableBasename(argv[0] ?? \"\", dialect), ...argv.slice(1)].join(\" \").includes(pattern),\n };\n }\n\n return {\n source: pattern,\n test: (argv, dialect) => {\n if (argv.length < tokens.length) {\n return false;\n }\n\n return tokens.every((token, index) => {\n const actual = argv[index] ?? \"\";\n\n return index === 0\n ? executableBasename(actual, dialect).toLowerCase() ===\n executableBasename(token, dialect).toLowerCase()\n : actual === token;\n });\n },\n };\n}\n\n/** Thrown inside the checker to stop at the first refusal. */\nclass Refusal extends Error {\n public constructor(\n public readonly reason: WorkspaceShellRefusalReason,\n message: string,\n public readonly executable?: string,\n public readonly refusedPath?: string,\n ) {\n super(message);\n }\n}\n\n/** Options a check takes beyond the policy and the line. */\nexport interface ShellCheckOptions {\n /** Patterns a host approved earlier with `remember`; they allow what would ask, never what is denied. */\n remembered?: readonly WorkspaceCommandPattern[];\n /** The host the `\"auto\"` interpreter is resolved against (tests inject one). */\n host?: ShellHost;\n /** The already-resolved shell, to skip resolving it again. */\n resolved?: ResolvedShell;\n}\n\n/** A compiled `shell.rules` entry. */\ninterface CompiledRule {\n rule: WorkspaceCommandRule;\n pattern: CompiledPattern;\n}\n\n/** Per-line checker state. */\nclass LineChecker {\n public readonly commands: string[][] = [];\n\n public readonly paths: WorkspaceShellPath[] = [];\n\n public readonly asks: WorkspaceShellAsk[] = [];\n\n private readonly deny: Set<string>;\n\n private readonly allow: string[];\n\n private readonly patterns: CompiledPattern[];\n\n private readonly rules: CompiledRule[];\n\n private readonly remembered: CompiledPattern[];\n\n private readonly hasCommandRules: boolean;\n\n /** Whether `shell.onAsk` is set: unmatched, unreadable and opaque commands ask. */\n public readonly askMode: boolean;\n\n public constructor(\n private readonly policy: WorkspacePolicy,\n private readonly shell: WorkspaceShellPolicy,\n private readonly line: string,\n private readonly dialect: ShellDialect,\n private readonly interpreter: ResolvedShellInterpreter,\n remembered: readonly WorkspaceCommandPattern[] = [],\n ) {\n this.deny = new Set((shell.deny ?? []).map((name) => name.toLowerCase()));\n this.allow = shell.allow ?? [];\n this.patterns = (shell.denyPatterns ?? []).map(compilePattern);\n this.rules = (shell.rules ?? []).map((rule) => ({ rule, pattern: compilePattern(rule.match) }));\n this.remembered = remembered.map(compilePattern);\n this.askMode = shell.onAsk !== undefined;\n this.hasCommandRules =\n this.deny.size > 0 ||\n this.patterns.length > 0 ||\n this.rules.length > 0 ||\n shell.allowCommand !== undefined;\n }\n\n /** Whether argument hooks (`denyPatterns` / `allowCommand`) need literal argv. */\n public get hasArgumentHooks(): boolean {\n return this.patterns.length > 0 || this.shell.allowCommand !== undefined;\n }\n\n private get shellMode(): boolean {\n return this.interpreter !== \"none\";\n }\n\n /** The last rule that matches `argv` (later rules win). */\n private matchRule(argv: string[]): WorkspaceCommandRule | undefined {\n for (let index = this.rules.length - 1; index >= 0; index--) {\n const compiled = this.rules[index] as CompiledRule;\n\n if (compiled.pattern.test(argv, this.dialect)) {\n return compiled.rule;\n }\n }\n\n return undefined;\n }\n\n private isRemembered(argv: string[]): boolean {\n return this.remembered.some((pattern) => pattern.test(argv, this.dialect));\n }\n\n /** Record a command that needs a decision (once per argv and reason). */\n public addAsk(\n argv: string[],\n reason: WorkspaceShellAsk[\"reason\"],\n rule?: WorkspaceCommandRule,\n ): void {\n const key = `${reason}\\u0000${argv.join(\"\\u0000\")}`;\n\n if (this.asks.some((ask) => `${ask.reason}\\u0000${ask.argv.join(\"\\u0000\")}` === key)) {\n return;\n }\n\n this.asks.push({ argv: [...argv], reason, ...(rule !== undefined ? { rule } : {}) });\n }\n\n /** Refuse when the matching rule denies. */\n private assertNotDenied(argv: string[], rule: WorkspaceCommandRule | undefined): void {\n if (rule?.effect !== \"deny\") {\n return;\n }\n\n const explanation =\n rule.reason !== undefined && rule.reason.trim() !== \"\" ? ` ${rule.reason.trim()}` : \"\";\n\n throw new Refusal(\n \"denied-pattern\",\n `${show(argv.join(\" \"))} is denied by a workspace shell rule.${explanation}`,\n executableBasename(argv[0] ?? \"\", this.dialect),\n );\n }\n\n /**\n * Check the canonical form of a PowerShell alias (`Remove-Item -Recurse\n * -Force x` as `rm -rf x`): deny, `denyPatterns`, `allowCommand` and the\n * rules, but not the `allow` list (which names the spelling that was written).\n */\n public checkCanonical(argv: string[]): void {\n const key = executableBasename(argv[0] ?? \"\", this.dialect).toLowerCase();\n\n if (this.deny.has(key)) {\n throw new Refusal(\n \"denied-executable\",\n `\\`${key}\\` is denied by the workspace shell policy (in ${show(argv.join(\" \"))}).`,\n key,\n );\n }\n\n this.checkHooks(argv, true);\n\n const rule = this.matchRule(argv);\n\n this.assertNotDenied(argv, rule);\n\n if (rule?.effect === \"ask\" && !this.isRemembered(argv)) {\n this.addAsk(argv, \"rule\", rule);\n }\n }\n\n /** Refuse a denied executable (used for `cd` spellings). */\n public assertExecutableNotDenied(key: string, argv: string[]): void {\n if (this.deny.has(key)) {\n throw new Refusal(\n \"denied-executable\",\n `\\`${key}\\` is denied by the workspace shell policy (in ${show(argv.join(\" \"))}).`,\n key,\n );\n }\n }\n\n /** Run `denyPatterns` and `allowCommand` on one argv. */\n private checkHooks(argv: string[], nested: boolean): void {\n const shown = show(argv.join(\" \"));\n\n for (const pattern of this.patterns) {\n if (pattern.test(argv, this.dialect)) {\n throw new Refusal(\n \"denied-pattern\",\n `${shown} matches a command pattern the workspace shell policy denies.`,\n executableBasename(argv[0] ?? \"\", this.dialect),\n );\n }\n }\n\n const hook = this.shell.allowCommand;\n\n if (hook === undefined) {\n return;\n }\n\n let verdict: boolean | string;\n\n try {\n verdict = hook([...argv], { command: this.line, interpreter: this.interpreter, nested });\n } catch {\n verdict = false;\n }\n\n if (verdict === true) {\n return;\n }\n\n const explanation =\n typeof verdict === \"string\" && verdict.trim() !== \"\" ? ` ${verdict.trim()}` : \"\";\n\n throw new Refusal(\n \"not-allowed\",\n `${shown} was refused by the workspace shell policy.${explanation}`,\n executableBasename(argv[0] ?? \"\", this.dialect),\n );\n }\n\n /** Whether the policy names `executable` in a non-empty `allow` list. */\n private explicitlyAllowed(executable: string): boolean {\n return this.allow.includes(executable);\n }\n\n /** Refuse a command runner the policy cannot see into, unless explicitly allowed. */\n private checkOpaque(executable: string, argv: string[]): void {\n if (this.explicitlyAllowed(executable) || !this.hasCommandRules) {\n return;\n }\n\n throw new Refusal(\n \"nested-command\",\n `\\`${executable}\\` runs a command the workspace shell policy cannot inspect (in ${show(argv.join(\" \"))}). Run the inner command directly instead.`,\n executable,\n );\n }\n\n /**\n * Check one argv: `deny`, `allow`, `denyPatterns`, `allowCommand`, then\n * follow wrappers and `find -exec` to the command they run. `dynamic[i]`\n * says whether `argv[i]` is decided at run time (shell mode).\n */\n public checkArgv(argv: string[], dynamic: boolean[], depth: number): void {\n const first = argv[0] ?? \"\";\n const executable = executableBasename(first, this.dialect);\n const key = executable.toLowerCase();\n\n if (executable === \"\") {\n throw new Refusal(\"not-allowed\", \"The command has no executable name.\");\n }\n\n if (dynamic[0] === true) {\n throw new Refusal(\n \"unsupported-syntax\",\n `The command name \\`${first}\\` is decided when the line runs, so the workspace shell policy cannot check it. Write the command name literally.`,\n );\n }\n\n if (this.deny.has(key)) {\n throw new Refusal(\n \"denied-executable\",\n `\\`${executable}\\` is denied by the workspace shell policy (in ${show(argv.join(\" \"))}).`,\n executable,\n );\n }\n\n if (this.allow.length > 0 && !this.allow.includes(executable)) {\n const listed = this.allow.slice(0, 20).join(\", \");\n\n throw new Refusal(\n \"not-allowed\",\n `\\`${executable}\\` is not in the workspace shell allowlist. Allowed commands: ${listed}${this.allow.length > 20 ? \", …\" : \"\"}.`,\n executable,\n );\n }\n\n this.checkHooks(argv, depth > 0);\n\n const rule = this.matchRule(argv);\n\n this.assertNotDenied(argv, rule);\n this.commands.push([...argv]);\n\n const remembered = this.isRemembered(argv);\n const approved = rule?.effect === \"allow\" || remembered || this.explicitlyAllowed(executable);\n\n if (\n OPAQUE_RUNNERS.has(key) ||\n (this.shellMode && this.dialect === \"posix\" && SHELL_OPAQUE_BUILTINS.has(key)) ||\n (this.shellMode && this.dialect === \"cmd\" && CMD_OPAQUE_BUILTINS.has(key)) ||\n (this.shellMode &&\n (this.dialect === \"powershell\" || this.dialect === \"powershell5\") &&\n POWERSHELL_OPAQUE.has(key))\n ) {\n if (approved) {\n return;\n }\n\n if (this.askMode || rule?.effect === \"ask\") {\n this.addAsk(argv, \"nested\", rule);\n\n return;\n }\n\n this.checkOpaque(executable, argv);\n\n return;\n }\n\n if (this.shellMode && this.dialect === \"posix\" && ARITHMETIC_SENSITIVE_BUILTINS.has(key)) {\n this.checkArithmeticSensitive(executable, argv, dynamic);\n }\n\n const ranges = this.unwrap(key, argv);\n\n // A command no rule or allow list covers asks — except a wrapper, which\n // asks through the command it runs.\n if (!approved && ranges === undefined) {\n if (rule?.effect === \"ask\") {\n this.addAsk(argv, \"rule\", rule);\n } else if (rule === undefined && this.askMode && this.allow.length === 0) {\n this.addAsk(argv, \"rule\");\n }\n } else if (!approved && rule?.effect === \"ask\") {\n this.addAsk(argv, \"rule\", rule);\n }\n\n if (ranges === undefined) {\n return;\n }\n\n if (ranges === null || depth >= MAX_WRAPPER_DEPTH) {\n if (this.askMode) {\n this.addAsk(argv, \"nested\", rule);\n\n return;\n }\n\n throw new Refusal(\n \"nested-command\",\n `\\`${executable}\\` is used with options the workspace shell policy cannot read (in ${show(argv.join(\" \"))}). Run the inner command directly instead.`,\n executable,\n );\n }\n\n for (const [start, end] of ranges) {\n this.checkArgv(argv.slice(start, end), dynamic.slice(start, end), depth + 1);\n }\n }\n\n /**\n * Builtins bash may evaluate arguments of as arithmetic (array subscripts\n * like `a[$(cmd)]` run the substitution): arguments must be literal, free\n * of `[`, and must not name a variable that changes how the shell runs.\n */\n private checkArithmeticSensitive(executable: string, argv: string[], dynamic: boolean[]): void {\n for (let index = 1; index < argv.length; index++) {\n const argument = argv[index] ?? \"\";\n\n if (dynamic[index] === true) {\n throw new Refusal(\n \"unsupported-syntax\",\n `\\`${executable}\\` arguments must be literal here (\\`${argument}\\` is not): bash may evaluate them as arithmetic.`,\n executable,\n );\n }\n\n if (argument.includes(\"[\") && argument !== \"[\" && argument !== \"]\") {\n throw new Refusal(\n \"unsupported-syntax\",\n `\\`${executable}\\` arguments with \\`[\\` (array subscripts, evaluated as arithmetic) are not supported.`,\n executable,\n );\n }\n\n const name = argument.split(\"=\")[0] ?? \"\";\n\n if (SENSITIVE_VARIABLES.has(name) || name.startsWith(\"BASH_FUNC_\")) {\n throw new Refusal(\n \"unsupported-syntax\",\n `Changing \\`${name}\\` is not supported: it changes how the shell runs the commands that follow.`,\n executable,\n );\n }\n }\n }\n\n /**\n * The argv ranges a wrapper runs. `undefined` — not a wrapper (or one that\n * runs nothing, like a bare `env`); `null` — a wrapper whose options the\n * policy cannot read (refused).\n */\n private unwrap(key: string, argv: string[]): Array<[number, number]> | null | undefined {\n switch (key) {\n case \"env\":\n return suffixFrom(argv, unwrapEnv(argv));\n case \"nice\":\n return suffixFrom(argv, unwrapNice(argv));\n case \"nohup\":\n return suffixFrom(argv, unwrapSimple(argv, []));\n case \"time\":\n return suffixFrom(argv, unwrapSimple(argv, [\"-p\"]));\n case \"timeout\":\n return suffixFrom(argv, unwrapTimeout(argv));\n case \"command\":\n return argv.slice(1).some((arg) => arg === \"-v\" || arg === \"-V\")\n ? undefined\n : suffixFrom(argv, unwrapSimple(argv, [\"-p\"]));\n case \"builtin\":\n return suffixFrom(argv, unwrapSimple(argv, []));\n case \"exec\":\n return this.dialect === \"posix\" ? suffixFrom(argv, unwrapExec(argv)) : undefined;\n case \"find\":\n return unwrapFind(argv);\n default:\n return undefined;\n }\n }\n\n /** Check a `cd` (shell mode): literal, one operand, inside the jail from every possible cwd. */\n public checkCd(words: ShellWord[], cwds: string[]): string[] {\n const argv = words.map((word) => word.value);\n\n if (this.deny.has(\"cd\")) {\n throw new Refusal(\"denied-executable\", \"`cd` is denied by the workspace shell policy.\", \"cd\");\n }\n\n this.checkHooks(argv, false);\n this.commands.push(argv);\n\n const operands = words.slice(1);\n\n while (\n this.dialect === \"posix\" &&\n operands.length > 0 &&\n [\"-P\", \"-L\", \"--\"].includes(operands[0]?.value ?? \"\")\n ) {\n operands.shift();\n }\n\n const target = operands[0];\n\n if (operands.length !== 1 || target === undefined || target.value === \"-\" || target.dynamic) {\n throw new Refusal(\n \"unsupported-syntax\",\n \"`cd` needs exactly one literal directory inside the workspace (no `-`, `~` or variables).\",\n \"cd\",\n );\n }\n\n const next = new Set(cwds);\n\n for (const cwd of cwds) {\n const resolved = this.resolveTarget(cwd, target.value);\n\n this.assertInJail(resolved, target.value, \"cd\");\n this.paths.push({ path: resolved, access: \"cwd\", target: target.value });\n next.add(resolved);\n }\n\n if (next.size > MAX_CD_CANDIDATES) {\n throw new Refusal(\"unsupported-syntax\", \"The line changes directory too many times.\", \"cd\");\n }\n\n return [...next];\n }\n\n /** Check a redirection target from every possible cwd. */\n public checkRedirectTarget(word: ShellWord, access: \"read\" | \"write\", cwds: string[]): void {\n if (\n (this.dialect === \"powershell\" || this.dialect === \"powershell5\") &&\n !word.quoted &&\n word.value.toLowerCase() === \"$null\"\n ) {\n return;\n }\n\n if (word.dynamic) {\n throw new Refusal(\n \"unsupported-syntax\",\n `The redirection target \\`${word.raw}\\` must be a literal path (no variables, globs or \\`~\\`).`,\n );\n }\n\n const value = word.value;\n\n if (\n (this.dialect === \"posix\" && DEVICE_TARGETS.has(value)) ||\n (this.dialect === \"cmd\" && CMD_DEVICE_TARGETS.has(value.toLowerCase()))\n ) {\n return;\n }\n\n for (const cwd of cwds) {\n const resolved = this.resolveTarget(cwd, value);\n\n this.assertInJail(resolved, value, \"redirect\");\n this.paths.push({ path: resolved, access, target: value });\n }\n }\n\n private resolveTarget(cwd: string, target: string): string {\n return path.resolve(cwd, target);\n }\n\n private assertInJail(resolved: string, target: string, kind: \"cd\" | \"redirect\"): void {\n if (isInsideJailLexically(this.policy, resolved)) {\n return;\n }\n\n throw new Refusal(\n \"path-escape\",\n kind === \"cd\"\n ? `\\`cd ${target}\\` leaves the workspace jail (or enters a denied path); commands must stay inside the workspace.`\n : `The redirection target \\`${target}\\` is outside the workspace jail or on a denied path.`,\n undefined,\n target,\n );\n }\n}\n\n/**\n * Turn a wrapper's \"inner command starts at `start`\" into a range; a\n * wrapper that runs nothing (`start === argv.length`) becomes `undefined`.\n */\nfunction suffixFrom(\n argv: string[],\n start: number | null,\n): Array<[number, number]> | null | undefined {\n if (start === null) {\n return null;\n }\n\n return start < argv.length ? [[start, argv.length]] : undefined;\n}\n\n/** `nohup` / `time` / `command` / `builtin`: skip the listed flags and `--`. */\nfunction unwrapSimple(argv: string[], flags: string[]): number | null {\n let index = 1;\n\n while (index < argv.length) {\n const arg = argv[index] ?? \"\";\n\n if (arg === \"--\") {\n index++;\n\n break;\n }\n\n if (flags.includes(arg)) {\n index++;\n\n continue;\n }\n\n if (arg.startsWith(\"-\") && arg !== \"-\") {\n return null;\n }\n\n break;\n }\n\n return index;\n}\n\n/** `env [-i] [-u NAME] [NAME=value]… command…` */\nfunction unwrapEnv(argv: string[]): number | null {\n let index = 1;\n\n while (index < argv.length) {\n const arg = argv[index] ?? \"\";\n\n if (arg === \"-i\" || arg === \"-\" || arg === \"--ignore-environment\") {\n index++;\n\n continue;\n }\n\n if (arg === \"-u\" || arg === \"--unset\") {\n index += 2;\n\n continue;\n }\n\n if (arg.startsWith(\"--unset=\") || (arg.startsWith(\"-u\") && arg.length > 2)) {\n index++;\n\n continue;\n }\n\n if (arg === \"--\") {\n index++;\n\n break;\n }\n\n if (arg.startsWith(\"-\")) {\n return null;\n }\n\n break;\n }\n\n while (index < argv.length && /^[^=]+=/.test(argv[index] ?? \"\")) {\n index++;\n }\n\n return index;\n}\n\n/** `nice [-n N | -N | --adjustment=N] command…` */\nfunction unwrapNice(argv: string[]): number | null {\n let index = 1;\n\n while (index < argv.length) {\n const arg = argv[index] ?? \"\";\n\n if (arg === \"-n\" || arg === \"--adjustment\") {\n index += 2;\n\n continue;\n }\n\n if (/^-n-?[0-9]+$/.test(arg) || /^--adjustment=-?[0-9]+$/.test(arg) || /^-[0-9]+$/.test(arg)) {\n index++;\n\n continue;\n }\n\n if (arg === \"--\") {\n index++;\n\n break;\n }\n\n if (arg.startsWith(\"-\")) {\n return null;\n }\n\n break;\n }\n\n return index;\n}\n\n/** `timeout [options] DURATION command…` */\nfunction unwrapTimeout(argv: string[]): number | null {\n let index = 1;\n\n while (index < argv.length) {\n const arg = argv[index] ?? \"\";\n\n if (arg === \"-s\" || arg === \"--signal\" || arg === \"-k\" || arg === \"--kill-after\") {\n index += 2;\n\n continue;\n }\n\n if (\n /^-[sk].+$/.test(arg) ||\n arg.startsWith(\"--signal=\") ||\n arg.startsWith(\"--kill-after=\") ||\n arg === \"--preserve-status\" ||\n arg === \"--foreground\" ||\n arg === \"-v\" ||\n arg === \"--verbose\"\n ) {\n index++;\n\n continue;\n }\n\n if (arg === \"--\") {\n index++;\n\n break;\n }\n\n if (arg.startsWith(\"-\")) {\n return null;\n }\n\n break;\n }\n\n // The duration, then the command.\n if (index >= argv.length - 1) {\n return null;\n }\n\n return index + 1;\n}\n\n/** `exec [-cl] [-a name] command…` (bash). */\nfunction unwrapExec(argv: string[]): number | null {\n let index = 1;\n\n while (index < argv.length) {\n const arg = argv[index] ?? \"\";\n\n if (arg === \"-c\" || arg === \"-l\" || arg === \"-cl\" || arg === \"-lc\") {\n index++;\n\n continue;\n }\n\n if (arg === \"-a\") {\n index += 2;\n\n continue;\n }\n\n if (arg === \"--\") {\n index++;\n\n break;\n }\n\n if (arg.startsWith(\"-\")) {\n return null;\n }\n\n break;\n }\n\n return index;\n}\n\n/** The commands `find -exec / -execdir / -ok / -okdir … ;|+` runs. */\nfunction unwrapFind(argv: string[]): Array<[number, number]> | null | undefined {\n const inner: Array<[number, number]> = [];\n\n for (let index = 1; index < argv.length; index++) {\n const arg = argv[index] ?? \"\";\n\n if (arg !== \"-exec\" && arg !== \"-execdir\" && arg !== \"-ok\" && arg !== \"-okdir\") {\n continue;\n }\n\n const start = index + 1;\n let end = start;\n\n while (end < argv.length && argv[end] !== \";\" && argv[end] !== \"+\") {\n end++;\n }\n\n if (end >= argv.length || end === start) {\n return null;\n }\n\n inner.push([start, end]);\n index = end;\n }\n\n return inner.length > 0 ? inner : undefined;\n}\n\n/** Build the argv-mode refusal for a line that is not a single argv. */\nfunction argvModeRefusal(command: string): Refusal {\n const analysis = analyzeArgvCommand(command);\n\n if (analysis.ok) {\n // Not reached: callers only ask for a refusal of a line that failed.\n return new Refusal(\"metacharacter\", \"The command cannot run as a single argv.\");\n }\n\n if (analysis.type === \"empty\") {\n return new Refusal(\"not-allowed\", \"The command is empty.\");\n }\n\n if (analysis.type === \"unbalanced-quotes\") {\n return new Refusal(\n \"unbalanced-quotes\",\n `The command has an unbalanced \\`${analysis.quote}\\` quote. Close it, or quote the argument with the other kind of quote.`,\n );\n }\n\n const token = analysis.token;\n const suffix = \" (run_shell executes a single command without a shell.)\";\n const parsed = parseShellLine(command, \"posix\");\n const segments: ShellSegment[] = parsed.ok ? parsed.segments : [];\n const listOperator = token === \"&&\" || token === \"||\" || token === \";\" || token === \"\\n\";\n\n if (listOperator && segments.length >= 2) {\n const shownOperator = token === \"\\n\" ? \"A line break\" : `\\`${token}\\``;\n const firstWords = (segments[0]?.source ?? \"\").trim().split(/\\s+/);\n\n if (firstWords[0] === \"cd\") {\n const rest = segments.slice(1).map((segment) => show(segment.source));\n\n return new Refusal(\n \"metacharacter\",\n `${shownOperator} is not supported in argv mode, and \\`cd\\` does not carry over: every command starts in the workspace root. Run ${rest.join(\", then \")} on its own, with paths relative to the root.${suffix}`,\n );\n }\n\n const steps = segments.map((segment) => show(segment.source));\n const joiner = token === \"||\" ? \", and if it fails, \" : \", then \";\n\n return new Refusal(\n \"metacharacter\",\n `${shownOperator} is not supported in argv mode: run ${steps.join(joiner)}, as separate commands.${suffix}`,\n );\n }\n\n if (token === \"&\" && segments.length >= 1) {\n const steps = segments.map((segment) => show(segment.source));\n const run =\n steps.length === 1\n ? `run ${steps[0] ?? \"\"} without it`\n : `run ${steps.join(\", then \")} as separate commands`;\n\n return new Refusal(\n \"metacharacter\",\n `\\`&\\` is not supported in argv mode: ${run}; start a long-running command with background: true.${suffix}`,\n );\n }\n\n if (token === \"|\") {\n const pipeline = command\n .split(\"|\")\n .map((part) => part.trim())\n .filter(Boolean);\n\n return new Refusal(\n \"metacharacter\",\n `\\`|\\` (a pipe) is not supported in argv mode: run ${show(pipeline[0] ?? command)} on its own and read its output yourself.${suffix}`,\n );\n }\n\n if (token === \">\" || token === \">>\" || token === \">&\") {\n return new Refusal(\n \"metacharacter\",\n `\\`${token}\\` (redirection) is not supported in argv mode: run the command without it and save the output with write_file if you need it.${suffix}`,\n );\n }\n\n if (token === \"<\" || token === \"<<\" || token === \"<&\") {\n return new Refusal(\n \"metacharacter\",\n `\\`${token}\\` (input redirection) is not supported in argv mode: pass the file path to the command as an argument.${suffix}`,\n );\n }\n\n if (token === \"$(\" || token === \"`\") {\n return new Refusal(\n \"metacharacter\",\n `Command substitution (${token === \"$(\" ? \"`$(…)`\" : \"backticks\"}) is not supported in argv mode: run the inner command first, then pass its output literally.${suffix}`,\n );\n }\n\n if (token === \"$\" || token === \"${\") {\n return new Refusal(\n \"metacharacter\",\n `\\`$\\` (variable expansion) is not supported in argv mode: write the value literally, or put the \\`$\\` inside quotes to pass it as text.${suffix}`,\n );\n }\n\n if (token === \"(\" || token === \")\") {\n return new Refusal(\n \"metacharacter\",\n `\\`${token}\\` is not supported in argv mode: put it inside quotes to pass it as text.${suffix}`,\n );\n }\n\n return new Refusal(\n \"metacharacter\",\n `\\`${token === \"\\n\" ? \"\\\\n\" : token}\\` is not supported in argv mode: run one command per call, and quote any argument that contains it.${suffix}`,\n );\n}\n\n/** Check a shell-mode line. */\nfunction checkShellLine(\n checker: LineChecker,\n policy: WorkspacePolicy,\n command: string,\n dialect: ShellDialect,\n): void {\n if (/^[-+]/.test(command)) {\n throw new Refusal(\"unsupported-syntax\", \"A command line may not start with `-` or `+`.\");\n }\n\n const parsed = parseShellLine(command, dialect);\n\n if (!parsed.ok) {\n if (checker.askMode) {\n checker.addAsk([command], \"unparsed\");\n\n return;\n }\n\n throw new Refusal(\n parsed.reason,\n `${parsed.message} The workspace shell policy only runs lines it can check.`,\n );\n }\n\n if (parsed.background) {\n throw new Refusal(\n \"unsupported-syntax\",\n \"`&` (run in the background) is not supported in a command line: start a long-running command with background: true instead.\",\n );\n }\n\n let cwds = [path.resolve(policy.cwd)];\n\n for (const simple of parsed.commands) {\n cwds = checkSimpleCommand(checker, simple, cwds, dialect);\n }\n}\n\n/** Check one parsed simple command; returns the possible working directories after it. */\nfunction checkSimpleCommand(\n checker: LineChecker,\n simple: ShellSimpleCommand,\n cwds: string[],\n dialect: ShellDialect,\n): string[] {\n for (const assignment of simple.assignments) {\n const name = assignment.assignmentName ?? \"\";\n\n if (SENSITIVE_VARIABLES.has(name) || name.startsWith(\"BASH_FUNC_\")) {\n throw new Refusal(\n \"unsupported-syntax\",\n `Assigning \\`${name}\\` is not supported: it changes how the shell runs the commands that follow.`,\n );\n }\n }\n\n for (const redirect of simple.redirects) {\n if (redirect.type === \"read\" || redirect.type === \"write\") {\n checker.checkRedirectTarget(redirect.target, redirect.type, cwds);\n }\n }\n\n const first = simple.words[0];\n\n if (first === undefined) {\n return cwds;\n }\n\n const executable = first.dynamic ? \"\" : executableBasename(first.value, dialect).toLowerCase();\n const powershell = dialect === \"powershell\" || dialect === \"powershell5\";\n\n if (executable === \"cd\" || (powershell && POWERSHELL_CD_NAMES.has(executable))) {\n // A `cd` the policy cannot follow would let later targets escape the jail:\n // it is refused outright, never turned into an ask.\n checker.assertExecutableNotDenied(\n executable,\n simple.words.map((word) => word.value),\n );\n\n if (powershell) {\n const words = normalizePowerShellCd(simple.words);\n\n if (words === null) {\n throw new Refusal(\n \"unsupported-syntax\",\n \"`Set-Location` is supported with one directory (`-Path` / `-LiteralPath` or positional) only.\",\n \"cd\",\n );\n }\n\n return checker.checkCd(words, cwds);\n }\n\n return checker.checkCd(simple.words, cwds);\n }\n\n try {\n if (!first.dynamic && first.value.includes(\"=\")) {\n throw new Refusal(\n \"unsupported-syntax\",\n `\\`${first.raw}\\` is not a plain command name or \\`NAME=value\\` assignment.`,\n );\n }\n\n if (/^[-+]/.test(first.value)) {\n throw new Refusal(\"unsupported-syntax\", `\\`${first.raw}\\` is not a command name.`);\n }\n\n if (checker.hasArgumentHooks) {\n const dynamic = simple.words.find((word) => word.dynamic);\n\n if (dynamic !== undefined) {\n throw new Refusal(\n \"unsupported-syntax\",\n `\\`${dynamic.raw}\\` is decided when the line runs, so it cannot be checked against the workspace shell policy's command rules. Write the value literally (quote globs).`,\n );\n }\n }\n\n if (\n (dialect === \"posix\" && (executable === \"pushd\" || executable === \"popd\")) ||\n (powershell && POWERSHELL_DIRECTORY_STACK.has(executable))\n ) {\n throw new Refusal(\n \"unsupported-syntax\",\n `\\`${executable}\\` is not supported; use \\`${powershell ? \"Set-Location\" : \"cd\"}\\`.`,\n );\n }\n\n const argv = simple.words.map((word) => (word.dynamic ? word.raw : word.value));\n\n checker.checkArgv(\n argv,\n simple.words.map((word) => word.dynamic),\n 0,\n );\n\n if (powershell) {\n const canonical = canonicalPowerShellArgv(argv);\n\n if (canonical !== undefined && canonical.join(\"\\u0000\") !== argv.join(\"\\u0000\")) {\n checker.checkCanonical(canonical);\n }\n }\n } catch (error) {\n // A command the policy cannot read is put to the host instead of refused.\n if (error instanceof Refusal && error.reason === \"unsupported-syntax\" && checker.askMode) {\n checker.addAsk(\n simple.words.map((word) => (word.dynamic ? word.raw : word.value)),\n \"unparsed\",\n );\n\n return cwds;\n }\n\n throw error;\n }\n\n return cwds;\n}\n\n/**\n * The shell policy's verdict on a command line, without touching the\n * filesystem.\n *\n * - **argv mode** (`interpreter: \"none\"`, the default): the line must\n * tokenize into one argv; otherwise it is refused with a\n * `\"metacharacter\"` / `\"unbalanced-quotes\"` reason and a message that\n * names the operator and suggests the split.\n * - **shell mode** (`interpreter: \"sh\"`): the line is parsed and every\n * simple command — inside `;`, `&&`, `||`, pipes, subshells, `$( )`,\n * backticks and here-documents — is checked; redirection targets and `cd`\n * directories must stay in the jail (checked lexically here; `exec`\n * re-checks them with symlinks resolved).\n *\n * Every argv goes through `deny` (case-insensitive, wins), `allow`\n * (exhaustive when non-empty), `denyPatterns` and `allowCommand`.\n * Wrappers (`env`, `time`, `timeout`, `nice`, `nohup`, `command`,\n * `builtin`, `exec`) and `find -exec` are followed to the command they run.\n * A runner the policy cannot see into (`bash -c`, `eval`, `xargs`, `sudo`,\n * …) is refused while the policy has deny rules, unless `allow` names it.\n *\n * @example\n * checkShellCommand({ cwd, shell: { allow: [\"git\"] } }, \"git add -A && git commit -m wip\");\n * // { allowed: false, reason: \"metacharacter\", message: \"`&&` is not supported in argv mode: run `git add -A`, then `git commit -m wip`, …\" }\n */\nexport function checkShellCommand(\n policy: WorkspacePolicy,\n command: string,\n options?: ShellCheckOptions,\n): WorkspaceShellCheck {\n const resolved =\n options?.resolved ?? resolveShell(policy.shell, options?.host ?? currentShellHost());\n\n return runCheck(policy, command, resolved.interpreter, resolved.dialect, options?.remembered);\n}\n\n/**\n * {@link checkShellCommand} with the shell-mode dialect chosen explicitly\n * instead of from the interpreter (used to test the cmd.exe and PowerShell\n * rules anywhere). Argv mode ignores `dialect`.\n */\nexport function checkShellCommandInDialect(\n policy: WorkspacePolicy,\n command: string,\n dialect: ShellDialect,\n options?: ShellCheckOptions,\n): WorkspaceShellCheck {\n const resolved =\n options?.resolved ?? resolveShell(policy.shell, options?.host ?? currentShellHost());\n\n return runCheck(policy, command, resolved.interpreter, dialect, options?.remembered);\n}\n\nfunction runCheck(\n policy: WorkspacePolicy,\n command: string,\n interpreter: ResolvedShellInterpreter,\n dialect: ShellDialect,\n remembered?: readonly WorkspaceCommandPattern[],\n): WorkspaceShellCheck {\n const shell = policy.shell;\n\n if (!shell) {\n return {\n allowed: false,\n reason: \"not-allowed\",\n message: \"The workspace has no shell policy, so no command may run.\",\n asks: [],\n commands: [],\n paths: [],\n };\n }\n\n const shellMode = interpreter !== \"none\";\n const checker = new LineChecker(\n policy,\n shell,\n command,\n shellMode ? dialect : \"posix\",\n interpreter,\n remembered,\n );\n\n try {\n if (command.trim() === \"\") {\n throw new Refusal(\"not-allowed\", \"The command is empty.\");\n }\n\n if (shellMode) {\n checkShellLine(checker, policy, command, dialect);\n } else {\n const argv = tokenizeCommand(command);\n\n if (argv === null) {\n throw argvModeRefusal(command);\n }\n\n checker.checkArgv(\n argv,\n argv.map(() => false),\n 0,\n );\n }\n\n if (checker.asks.length > 0) {\n const first = checker.asks[0] as WorkspaceShellAsk;\n const shown = show(first.argv.join(\" \"));\n const why = first.rule?.reason !== undefined ? ` ${first.rule.reason}` : \"\";\n\n return {\n allowed: false,\n reason: \"approval-required\",\n message: `${shown} needs approval, and the workspace shell policy has no approval handler (shell.onAsk).${why}`,\n asks: checker.asks,\n commands: checker.commands,\n paths: checker.paths,\n };\n }\n\n return { allowed: true, asks: [], commands: checker.commands, paths: checker.paths };\n } catch (error) {\n if (!(error instanceof Refusal)) {\n throw error;\n }\n\n return {\n allowed: false,\n reason: error.reason,\n message: error.message,\n ...(error.executable !== undefined ? { executable: error.executable } : {}),\n ...(error.refusedPath !== undefined ? { path: error.refusedPath } : {}),\n asks: [],\n commands: checker.commands,\n paths: checker.paths,\n };\n }\n}\n\n/**\n * The simple commands a line would run (pipeline members, `&&` / `||` / `;`\n * parts, subshells and substitutions included), without checking anything.\n * A line the parser cannot read with confidence comes back with `opaque`\n * set to the reason and no commands.\n *\n * The line is read with the policy's shell dialect when its `interpreter`\n * is a shell, and with the platform's default shell otherwise.\n *\n * @example\n * classifyShellCommand(\"a | b\"); // { commands: [[\"a\"], [\"b\"]] }\n */\nexport function classifyShellCommand(\n command: string,\n policy?: Pick<WorkspacePolicy, \"shell\">,\n host?: ShellHost,\n): WorkspaceShellClassification {\n const resolved = resolveShell(policy?.shell, host ?? currentShellHost());\n const dialect = resolved.interpreter === \"none\" ? shellDialect() : resolved.dialect;\n const parsed = parseShellLine(command, dialect);\n\n if (!parsed.ok) {\n return { commands: [], opaque: parsed.message };\n }\n\n return {\n commands: parsed.commands\n .filter((simple) => simple.words.length > 0)\n .map((simple) => simple.words.map((word) => (word.dynamic ? word.raw : word.value))),\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"shell-check.mjs","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/policy/shell-check.ts"],"sourcesContent":["import path from \"node:path\";\nimport type { WorkspaceShellRefusalReason } from \"../errors\";\nimport type {\n WorkspaceCommandPattern,\n WorkspaceCommandRule,\n WorkspacePolicy,\n WorkspaceShellAsk,\n WorkspaceShellClassification,\n WorkspaceShellPolicy,\n} from \"../contracts\";\nimport { isInsideJailLexically } from \"./jail\";\nimport {\n canonicalPowerShellArgv,\n normalizePowerShellCd,\n POWERSHELL_CD_NAMES,\n POWERSHELL_DIRECTORY_STACK,\n} from \"./powershell-aliases\";\nimport {\n currentShellHost,\n resolveShell,\n type ResolvedShell,\n type ResolvedShellInterpreter,\n type ShellHost,\n} from \"./shell-interpreter\";\nimport {\n parseShellLine,\n type ShellDialect,\n type ShellSegment,\n type ShellSimpleCommand,\n type ShellWord,\n} from \"./shell-parser\";\nimport { analyzeArgvCommand, tokenizeCommand } from \"./tokenize-command\";\n\n/** How a shell-mode line touches a path the jail has to vet. */\nexport type WorkspaceShellPathAccess = \"read\" | \"write\" | \"cwd\";\n\n/** A path a shell-mode line redirects to/from or `cd`s into. */\nexport interface WorkspaceShellPath {\n /** Absolute path, resolved lexically against the workspace root (or an earlier `cd`). */\n path: string;\n /** What the line does with it. */\n access: WorkspaceShellPathAccess;\n /** The target as written in the line. */\n target: string;\n}\n\n/**\n * The shell policy's verdict on one command line — see\n * {@link checkShellCommand}.\n */\nexport interface WorkspaceShellCheck {\n /** Whether the line may run (path targets still get a symlink-aware recheck in `exec`). */\n allowed: boolean;\n /** Why it was refused. */\n reason?: WorkspaceShellRefusalReason;\n /** Model-facing refusal text: the cause, and how to fix the command. */\n message?: string;\n /** The executable the refusal is about, when there is one. */\n executable?: string;\n /** The redirection target or `cd` directory a `\"path-escape\"` refusal is about. */\n path?: string;\n /**\n * Commands that need a decision from `shell.onAsk` before the line may run.\n * When non-empty, `allowed` is `false` with reason `\"approval-required\"`;\n * the ops layer asks, and refuses with `\"approval-denied\"` if the answer is no.\n */\n asks: WorkspaceShellAsk[];\n /** The argv of every simple command checked, in order (wrapped commands included). */\n commands: string[][];\n /** Shell mode: every redirection target and `cd` directory, for the jail recheck. */\n paths: WorkspaceShellPath[];\n}\n\n/**\n * Programs and shell builtins that run a command or code the policy cannot\n * see (a script string, a file, a command read from input, another user).\n * With deny rules in force they would hide a denied command, so they are\n * refused unless the policy names them in `allow`.\n */\nconst OPAQUE_RUNNERS = new Set([\n // Shells and interpreters of command strings.\n \"sh\",\n \"bash\",\n \"dash\",\n \"zsh\",\n \"ksh\",\n \"mksh\",\n \"ash\",\n \"yash\",\n \"posh\",\n \"fish\",\n \"csh\",\n \"tcsh\",\n \"busybox\",\n \"cmd\",\n \"powershell\",\n \"pwsh\",\n \"wsl\",\n // Run a command as someone else, elsewhere, or out of the process group.\n \"sudo\",\n \"doas\",\n \"su\",\n \"runuser\",\n \"pkexec\",\n \"runas\",\n \"chroot\",\n \"nsenter\",\n \"unshare\",\n \"setsid\",\n \"systemd-run\",\n \"start-stop-daemon\",\n \"daemonize\",\n // Run commands built from input or on a schedule.\n \"xargs\",\n \"parallel\",\n \"watch\",\n \"script\",\n \"expect\",\n \"at\",\n \"batch\",\n \"screen\",\n \"tmux\",\n \"stdbuf\",\n \"flock\",\n]);\n\n/**\n * PowerShell cmdlets and aliases that run code or commands the policy cannot\n * see, or change what later names mean (lowercase).\n */\nconst POWERSHELL_OPAQUE = new Set([\n \"iex\",\n \"invoke-expression\",\n \"invoke-command\",\n \"icm\",\n \"start-process\",\n \"saps\",\n \"start\",\n \"add-type\",\n \"set-alias\",\n \"sal\",\n \"new-alias\",\n \"nal\",\n \"invoke-item\",\n \"ii\",\n]);\n\n/** POSIX shell builtins that evaluate code or change how later names resolve. */\nconst SHELL_OPAQUE_BUILTINS = new Set([\n \"eval\",\n \".\",\n \"source\",\n \"alias\",\n \"unalias\",\n \"hash\",\n \"enable\",\n \"trap\",\n \"let\",\n \"declare\",\n \"typeset\",\n \"local\",\n \"set\",\n \"shopt\",\n \"fc\",\n \"bind\",\n \"complete\",\n \"compgen\",\n \"compopt\",\n \"mapfile\",\n \"readarray\",\n]);\n\n/** cmd.exe builtins that run commands or change the environment. */\nconst CMD_OPAQUE_BUILTINS = new Set([\"call\", \"start\", \"set\", \"pushd\", \"popd\"]);\n\n/**\n * Shell builtins whose arguments bash may evaluate as arithmetic (array\n * subscripts such as `a[$(cmd)]`): their arguments must be literal and free\n * of `[`.\n */\nconst ARITHMETIC_SENSITIVE_BUILTINS = new Set([\n \"test\",\n \"[\",\n \"printf\",\n \"read\",\n \"unset\",\n \"export\",\n \"readonly\",\n \"getopts\",\n \"wait\",\n \"kill\",\n \"shift\",\n \"return\",\n \"exit\",\n \"break\",\n \"continue\",\n \"ulimit\",\n]);\n\n/**\n * Variables a shell-mode line may not assign: they change how the shell\n * parses, traces or resolves what follows (`CDPATH` would move `cd` out of\n * the jail's sight, `PS4` is expanded — command substitutions included —\n * on every traced line, `IFS` re-splits expansions).\n */\nconst SENSITIVE_VARIABLES = new Set([\n \"CDPATH\",\n \"ENV\",\n \"BASH_ENV\",\n \"SHELLOPTS\",\n \"BASHOPTS\",\n \"IFS\",\n \"PS1\",\n \"PS2\",\n \"PS3\",\n \"PS4\",\n \"PROMPT_COMMAND\",\n \"BASH_XTRACEFD\",\n \"GLOBIGNORE\",\n \"EXECIGNORE\",\n \"BASH_LOADABLES_PATH\",\n]);\n\n/** Redirection targets that are always fine even though they sit outside the jail. */\nconst DEVICE_TARGETS = new Set([\"/dev/null\", \"/dev/stdout\", \"/dev/stderr\"]);\n\n/** cmd.exe's null device. */\nconst CMD_DEVICE_TARGETS = new Set([\"nul\"]);\n\n/** Deepest chain of wrappers (`env time nice …`) the policy unwraps. */\nconst MAX_WRAPPER_DEPTH = 8;\n\n/** Most distinct directories a line may `cd` between. */\nconst MAX_CD_CANDIDATES = 32;\n\n/** Longest command shown verbatim in a refusal before it is abbreviated. */\nconst MAX_SHOWN_COMMAND = 48;\n\n/** The dialect `interpreter: \"sh\"` parses on this platform. */\nexport function shellDialect(): ShellDialect {\n return process.platform === \"win32\" ? \"cmd\" : \"posix\";\n}\n\n/**\n * Reduce an argv's first element to the basename the allow/deny policy is\n * keyed on. `\"npm\"` → `\"npm\"`; `\"/usr/bin/node\"` → `\"node\"`; `\"node.exe\"`\n * → `\"node\"` (the `.exe`/`.cmd`/`.bat`/`.com` Windows extension is\n * stripped). The cmd dialect splits Windows paths too.\n */\nexport function executableBasename(firstToken: string, dialect: ShellDialect = \"posix\"): string {\n const base =\n dialect === \"cmd\" || dialect === \"powershell\" || dialect === \"powershell5\"\n ? path.win32.basename(firstToken)\n : path.basename(firstToken);\n\n return base.replace(\n dialect === \"powershell\" || dialect === \"powershell5\"\n ? /\\.(exe|cmd|bat|com|ps1)$/i\n : /\\.(exe|cmd|bat|com)$/i,\n \"\",\n );\n}\n\n/** Show a command in a refusal, abbreviated at a word boundary when long. */\nfunction show(source: string): string {\n const flat = source.replace(/\\s+/g, \" \").trim();\n\n if (flat.length <= MAX_SHOWN_COMMAND) {\n return `\\`${flat}\\``;\n }\n\n const cut = flat.lastIndexOf(\" \", MAX_SHOWN_COMMAND);\n\n return `\\`${flat.slice(0, cut > 0 ? cut : MAX_SHOWN_COMMAND)} …\\``;\n}\n\n/** Compile `denyPatterns` once per check. */\ninterface CompiledPattern {\n source: WorkspaceCommandPattern;\n test(argv: string[], dialect: ShellDialect): boolean;\n}\n\nfunction compilePattern(pattern: WorkspaceCommandPattern): CompiledPattern {\n if (pattern instanceof RegExp) {\n const regex = new RegExp(pattern.source, pattern.flags.replace(/[gy]/g, \"\"));\n\n return {\n source: pattern,\n test: (argv, dialect) =>\n regex.test([executableBasename(argv[0] ?? \"\", dialect), ...argv.slice(1)].join(\" \")),\n };\n }\n\n const tokens = tokenizeCommand(pattern);\n\n if (tokens === null) {\n // Not representable as an argv: match it as text inside the joined argv.\n return {\n source: pattern,\n test: (argv, dialect) =>\n [executableBasename(argv[0] ?? \"\", dialect), ...argv.slice(1)].join(\" \").includes(pattern),\n };\n }\n\n return {\n source: pattern,\n test: (argv, dialect) => {\n if (argv.length < tokens.length) {\n return false;\n }\n\n return tokens.every((token, index) => {\n const actual = argv[index] ?? \"\";\n\n return index === 0\n ? executableBasename(actual, dialect).toLowerCase() ===\n executableBasename(token, dialect).toLowerCase()\n : actual === token;\n });\n },\n };\n}\n\n/** Thrown inside the checker to stop at the first refusal. */\nclass Refusal extends Error {\n public constructor(\n public readonly reason: WorkspaceShellRefusalReason,\n message: string,\n public readonly executable?: string,\n public readonly refusedPath?: string,\n ) {\n super(message);\n }\n}\n\n/** Options a check takes beyond the policy and the line. */\nexport interface ShellCheckOptions {\n /** Patterns a host approved earlier with `remember`; they allow what would ask, never what is denied. */\n remembered?: readonly WorkspaceCommandPattern[];\n /** The host the `\"auto\"` interpreter is resolved against (tests inject one). */\n host?: ShellHost;\n /** The already-resolved shell, to skip resolving it again. */\n resolved?: ResolvedShell;\n /** The platform the lines run on. Default: `host.platform`, else the running one. */\n platform?: NodeJS.Platform;\n}\n\n/**\n * The first word of `command` that has an unquoted backslash before a\n * letter, digit or dot inside it (a Windows-style path bash reads as\n * escapes), with the same word spelled with forward slashes. Quoted text\n * and `\\ ` style escapes (a backslash before a space, quote, `$` and the\n * like) are left alone.\n */\nexport function findWindowsStylePath(command: string): { word: string; suggestion: string } | null {\n let quote: \"'\" | '\"' | null = null;\n let start = 0;\n let found = false;\n let suggestion = \"\";\n\n const finish = (end: number): { word: string; suggestion: string } | null =>\n found ? { word: command.slice(start, end), suggestion } : null;\n\n for (let index = 0; index < command.length; index++) {\n const char = command[index] as string;\n\n if (quote === null && /[\\s;&|<>()]/.test(char)) {\n const hit = finish(index);\n\n if (hit !== null) {\n return hit;\n }\n\n start = index + 1;\n found = false;\n suggestion = \"\";\n\n continue;\n }\n\n if (quote === \"'\") {\n suggestion += char;\n quote = char === \"'\" ? null : quote;\n\n continue;\n }\n\n if (char === \"\\\\\") {\n const next = command[index + 1] ?? \"\";\n\n if (quote === null && index > start && /[A-Za-z0-9.]/.test(next)) {\n found = true;\n suggestion += \"/\";\n\n continue;\n }\n\n // Any other escape keeps its backslash and swallows the next character.\n suggestion += char + next;\n index++;\n\n continue;\n }\n\n if (char === '\"' || char === \"'\") {\n quote = quote === null ? char : quote === char ? null : quote;\n }\n\n suggestion += char;\n }\n\n return finish(command.length);\n}\n\n/** A compiled `shell.rules` entry. */\ninterface CompiledRule {\n rule: WorkspaceCommandRule;\n pattern: CompiledPattern;\n}\n\n/** Per-line checker state. */\nclass LineChecker {\n public readonly commands: string[][] = [];\n\n public readonly paths: WorkspaceShellPath[] = [];\n\n public readonly asks: WorkspaceShellAsk[] = [];\n\n private readonly deny: Set<string>;\n\n private readonly allow: string[];\n\n private readonly patterns: CompiledPattern[];\n\n private readonly rules: CompiledRule[];\n\n private readonly remembered: CompiledPattern[];\n\n private readonly hasCommandRules: boolean;\n\n /** Whether `shell.onAsk` is set: unmatched, unreadable and opaque commands ask. */\n public readonly askMode: boolean;\n\n public constructor(\n private readonly policy: WorkspacePolicy,\n private readonly shell: WorkspaceShellPolicy,\n private readonly line: string,\n private readonly dialect: ShellDialect,\n private readonly interpreter: ResolvedShellInterpreter,\n remembered: readonly WorkspaceCommandPattern[] = [],\n ) {\n this.deny = new Set((shell.deny ?? []).map((name) => name.toLowerCase()));\n this.allow = shell.allow ?? [];\n this.patterns = (shell.denyPatterns ?? []).map(compilePattern);\n this.rules = (shell.rules ?? []).map((rule) => ({ rule, pattern: compilePattern(rule.match) }));\n this.remembered = remembered.map(compilePattern);\n this.askMode = shell.onAsk !== undefined;\n this.hasCommandRules =\n this.deny.size > 0 ||\n this.patterns.length > 0 ||\n this.rules.length > 0 ||\n shell.allowCommand !== undefined;\n }\n\n /** Whether argument hooks (`denyPatterns` / `allowCommand`) need literal argv. */\n public get hasArgumentHooks(): boolean {\n return this.patterns.length > 0 || this.shell.allowCommand !== undefined;\n }\n\n private get shellMode(): boolean {\n return this.interpreter !== \"none\";\n }\n\n /** The last rule that matches `argv` (later rules win). */\n private matchRule(argv: string[]): WorkspaceCommandRule | undefined {\n for (let index = this.rules.length - 1; index >= 0; index--) {\n const compiled = this.rules[index] as CompiledRule;\n\n if (compiled.pattern.test(argv, this.dialect)) {\n return compiled.rule;\n }\n }\n\n return undefined;\n }\n\n private isRemembered(argv: string[]): boolean {\n return this.remembered.some((pattern) => pattern.test(argv, this.dialect));\n }\n\n /** Record a command that needs a decision (once per argv and reason). */\n public addAsk(\n argv: string[],\n reason: WorkspaceShellAsk[\"reason\"],\n rule?: WorkspaceCommandRule,\n ): void {\n const key = `${reason}\\u0000${argv.join(\"\\u0000\")}`;\n\n if (this.asks.some((ask) => `${ask.reason}\\u0000${ask.argv.join(\"\\u0000\")}` === key)) {\n return;\n }\n\n this.asks.push({ argv: [...argv], reason, ...(rule !== undefined ? { rule } : {}) });\n }\n\n /** Refuse when the matching rule denies. */\n private assertNotDenied(argv: string[], rule: WorkspaceCommandRule | undefined): void {\n if (rule?.effect !== \"deny\") {\n return;\n }\n\n const explanation =\n rule.reason !== undefined && rule.reason.trim() !== \"\" ? ` ${rule.reason.trim()}` : \"\";\n\n throw new Refusal(\n \"denied-pattern\",\n `${show(argv.join(\" \"))} is denied by a workspace shell rule.${explanation}`,\n executableBasename(argv[0] ?? \"\", this.dialect),\n );\n }\n\n /**\n * Check the canonical form of a PowerShell alias (`Remove-Item -Recurse\n * -Force x` as `rm -rf x`): deny, `denyPatterns`, `allowCommand` and the\n * rules, but not the `allow` list (which names the spelling that was written).\n */\n public checkCanonical(argv: string[]): void {\n const key = executableBasename(argv[0] ?? \"\", this.dialect).toLowerCase();\n\n if (this.deny.has(key)) {\n throw new Refusal(\n \"denied-executable\",\n `\\`${key}\\` is denied by the workspace shell policy (in ${show(argv.join(\" \"))}).`,\n key,\n );\n }\n\n this.checkHooks(argv, true);\n\n const rule = this.matchRule(argv);\n\n this.assertNotDenied(argv, rule);\n\n if (rule?.effect === \"ask\" && !this.isRemembered(argv)) {\n this.addAsk(argv, \"rule\", rule);\n }\n }\n\n /** Refuse a denied executable (used for `cd` spellings). */\n public assertExecutableNotDenied(key: string, argv: string[]): void {\n if (this.deny.has(key)) {\n throw new Refusal(\n \"denied-executable\",\n `\\`${key}\\` is denied by the workspace shell policy (in ${show(argv.join(\" \"))}).`,\n key,\n );\n }\n }\n\n /** Run `denyPatterns` and `allowCommand` on one argv. */\n private checkHooks(argv: string[], nested: boolean): void {\n const shown = show(argv.join(\" \"));\n\n for (const pattern of this.patterns) {\n if (pattern.test(argv, this.dialect)) {\n throw new Refusal(\n \"denied-pattern\",\n `${shown} matches a command pattern the workspace shell policy denies.`,\n executableBasename(argv[0] ?? \"\", this.dialect),\n );\n }\n }\n\n const hook = this.shell.allowCommand;\n\n if (hook === undefined) {\n return;\n }\n\n let verdict: boolean | string;\n\n try {\n verdict = hook([...argv], { command: this.line, interpreter: this.interpreter, nested });\n } catch {\n verdict = false;\n }\n\n if (verdict === true) {\n return;\n }\n\n const explanation =\n typeof verdict === \"string\" && verdict.trim() !== \"\" ? ` ${verdict.trim()}` : \"\";\n\n throw new Refusal(\n \"not-allowed\",\n `${shown} was refused by the workspace shell policy.${explanation}`,\n executableBasename(argv[0] ?? \"\", this.dialect),\n );\n }\n\n /** Whether the policy names `executable` in a non-empty `allow` list. */\n private explicitlyAllowed(executable: string): boolean {\n return this.allow.includes(executable);\n }\n\n /** Refuse a command runner the policy cannot see into, unless explicitly allowed. */\n private checkOpaque(executable: string, argv: string[]): void {\n if (this.explicitlyAllowed(executable) || !this.hasCommandRules) {\n return;\n }\n\n throw new Refusal(\n \"nested-command\",\n `\\`${executable}\\` runs a command the workspace shell policy cannot inspect (in ${show(argv.join(\" \"))}). Run the inner command directly instead.`,\n executable,\n );\n }\n\n /**\n * Check one argv: `deny`, `allow`, `denyPatterns`, `allowCommand`, then\n * follow wrappers and `find -exec` to the command they run. `dynamic[i]`\n * says whether `argv[i]` is decided at run time (shell mode).\n */\n public checkArgv(argv: string[], dynamic: boolean[], depth: number): void {\n const first = argv[0] ?? \"\";\n const executable = executableBasename(first, this.dialect);\n const key = executable.toLowerCase();\n\n if (executable === \"\") {\n throw new Refusal(\"not-allowed\", \"The command has no executable name.\");\n }\n\n if (dynamic[0] === true) {\n throw new Refusal(\n \"unsupported-syntax\",\n `The command name \\`${first}\\` is decided when the line runs, so the workspace shell policy cannot check it. Write the command name literally.`,\n );\n }\n\n if (this.deny.has(key)) {\n throw new Refusal(\n \"denied-executable\",\n `\\`${executable}\\` is denied by the workspace shell policy (in ${show(argv.join(\" \"))}).`,\n executable,\n );\n }\n\n if (this.allow.length > 0 && !this.allow.includes(executable)) {\n const listed = this.allow.slice(0, 20).join(\", \");\n\n throw new Refusal(\n \"not-allowed\",\n `\\`${executable}\\` is not in the workspace shell allowlist. Allowed commands: ${listed}${this.allow.length > 20 ? \", …\" : \"\"}.`,\n executable,\n );\n }\n\n this.checkHooks(argv, depth > 0);\n\n const rule = this.matchRule(argv);\n\n this.assertNotDenied(argv, rule);\n this.commands.push([...argv]);\n\n const remembered = this.isRemembered(argv);\n const approved = rule?.effect === \"allow\" || remembered || this.explicitlyAllowed(executable);\n\n if (\n OPAQUE_RUNNERS.has(key) ||\n (this.shellMode && this.dialect === \"posix\" && SHELL_OPAQUE_BUILTINS.has(key)) ||\n (this.shellMode && this.dialect === \"cmd\" && CMD_OPAQUE_BUILTINS.has(key)) ||\n (this.shellMode &&\n (this.dialect === \"powershell\" || this.dialect === \"powershell5\") &&\n POWERSHELL_OPAQUE.has(key))\n ) {\n if (approved) {\n return;\n }\n\n if (this.askMode || rule?.effect === \"ask\") {\n this.addAsk(argv, \"nested\", rule);\n\n return;\n }\n\n this.checkOpaque(executable, argv);\n\n return;\n }\n\n if (this.shellMode && this.dialect === \"posix\" && ARITHMETIC_SENSITIVE_BUILTINS.has(key)) {\n this.checkArithmeticSensitive(executable, argv, dynamic);\n }\n\n const ranges = this.unwrap(key, argv);\n\n // A command no rule or allow list covers asks — except a wrapper, which\n // asks through the command it runs.\n if (!approved && ranges === undefined) {\n if (rule?.effect === \"ask\") {\n this.addAsk(argv, \"rule\", rule);\n } else if (rule === undefined && this.askMode && this.allow.length === 0) {\n this.addAsk(argv, \"rule\");\n }\n } else if (!approved && rule?.effect === \"ask\") {\n this.addAsk(argv, \"rule\", rule);\n }\n\n if (ranges === undefined) {\n return;\n }\n\n if (ranges === null || depth >= MAX_WRAPPER_DEPTH) {\n if (this.askMode) {\n this.addAsk(argv, \"nested\", rule);\n\n return;\n }\n\n throw new Refusal(\n \"nested-command\",\n `\\`${executable}\\` is used with options the workspace shell policy cannot read (in ${show(argv.join(\" \"))}). Run the inner command directly instead.`,\n executable,\n );\n }\n\n for (const [start, end] of ranges) {\n this.checkArgv(argv.slice(start, end), dynamic.slice(start, end), depth + 1);\n }\n }\n\n /**\n * Builtins bash may evaluate arguments of as arithmetic (array subscripts\n * like `a[$(cmd)]` run the substitution): arguments must be literal, free\n * of `[`, and must not name a variable that changes how the shell runs.\n */\n private checkArithmeticSensitive(executable: string, argv: string[], dynamic: boolean[]): void {\n for (let index = 1; index < argv.length; index++) {\n const argument = argv[index] ?? \"\";\n\n if (dynamic[index] === true) {\n throw new Refusal(\n \"unsupported-syntax\",\n `\\`${executable}\\` arguments must be literal here (\\`${argument}\\` is not): bash may evaluate them as arithmetic.`,\n executable,\n );\n }\n\n if (argument.includes(\"[\") && argument !== \"[\" && argument !== \"]\") {\n throw new Refusal(\n \"unsupported-syntax\",\n `\\`${executable}\\` arguments with \\`[\\` (array subscripts, evaluated as arithmetic) are not supported.`,\n executable,\n );\n }\n\n const name = argument.split(\"=\")[0] ?? \"\";\n\n if (SENSITIVE_VARIABLES.has(name) || name.startsWith(\"BASH_FUNC_\")) {\n throw new Refusal(\n \"unsupported-syntax\",\n `Changing \\`${name}\\` is not supported: it changes how the shell runs the commands that follow.`,\n executable,\n );\n }\n }\n }\n\n /**\n * The argv ranges a wrapper runs. `undefined` — not a wrapper (or one that\n * runs nothing, like a bare `env`); `null` — a wrapper whose options the\n * policy cannot read (refused).\n */\n private unwrap(key: string, argv: string[]): Array<[number, number]> | null | undefined {\n switch (key) {\n case \"env\":\n return suffixFrom(argv, unwrapEnv(argv));\n case \"nice\":\n return suffixFrom(argv, unwrapNice(argv));\n case \"nohup\":\n return suffixFrom(argv, unwrapSimple(argv, []));\n case \"time\":\n return suffixFrom(argv, unwrapSimple(argv, [\"-p\"]));\n case \"timeout\":\n return suffixFrom(argv, unwrapTimeout(argv));\n case \"command\":\n return argv.slice(1).some((arg) => arg === \"-v\" || arg === \"-V\")\n ? undefined\n : suffixFrom(argv, unwrapSimple(argv, [\"-p\"]));\n case \"builtin\":\n return suffixFrom(argv, unwrapSimple(argv, []));\n case \"exec\":\n return this.dialect === \"posix\" ? suffixFrom(argv, unwrapExec(argv)) : undefined;\n case \"find\":\n return unwrapFind(argv);\n default:\n return undefined;\n }\n }\n\n /** Check a `cd` (shell mode): literal, one operand, inside the jail from every possible cwd. */\n public checkCd(words: ShellWord[], cwds: string[]): string[] {\n const argv = words.map((word) => word.value);\n\n if (this.deny.has(\"cd\")) {\n throw new Refusal(\"denied-executable\", \"`cd` is denied by the workspace shell policy.\", \"cd\");\n }\n\n this.checkHooks(argv, false);\n this.commands.push(argv);\n\n const operands = words.slice(1);\n\n while (\n this.dialect === \"posix\" &&\n operands.length > 0 &&\n [\"-P\", \"-L\", \"--\"].includes(operands[0]?.value ?? \"\")\n ) {\n operands.shift();\n }\n\n const target = operands[0];\n\n if (operands.length !== 1 || target === undefined || target.value === \"-\" || target.dynamic) {\n throw new Refusal(\n \"unsupported-syntax\",\n \"`cd` needs exactly one literal directory inside the workspace (no `-`, `~` or variables).\",\n \"cd\",\n );\n }\n\n const next = new Set(cwds);\n\n for (const cwd of cwds) {\n const resolved = this.resolveTarget(cwd, target.value);\n\n this.assertInJail(resolved, target.value, \"cd\");\n this.paths.push({ path: resolved, access: \"cwd\", target: target.value });\n next.add(resolved);\n }\n\n if (next.size > MAX_CD_CANDIDATES) {\n throw new Refusal(\"unsupported-syntax\", \"The line changes directory too many times.\", \"cd\");\n }\n\n return [...next];\n }\n\n /** Check a redirection target from every possible cwd. */\n public checkRedirectTarget(word: ShellWord, access: \"read\" | \"write\", cwds: string[]): void {\n if (\n (this.dialect === \"powershell\" || this.dialect === \"powershell5\") &&\n !word.quoted &&\n word.value.toLowerCase() === \"$null\"\n ) {\n return;\n }\n\n if (word.dynamic) {\n throw new Refusal(\n \"unsupported-syntax\",\n `The redirection target \\`${word.raw}\\` must be a literal path (no variables, globs or \\`~\\`).`,\n );\n }\n\n const value = word.value;\n\n if (\n (this.dialect === \"posix\" && DEVICE_TARGETS.has(value)) ||\n (this.dialect === \"cmd\" && CMD_DEVICE_TARGETS.has(value.toLowerCase()))\n ) {\n return;\n }\n\n for (const cwd of cwds) {\n const resolved = this.resolveTarget(cwd, value);\n\n this.assertInJail(resolved, value, \"redirect\");\n this.paths.push({ path: resolved, access, target: value });\n }\n }\n\n private resolveTarget(cwd: string, target: string): string {\n return path.resolve(cwd, target);\n }\n\n private assertInJail(resolved: string, target: string, kind: \"cd\" | \"redirect\"): void {\n if (isInsideJailLexically(this.policy, resolved)) {\n return;\n }\n\n throw new Refusal(\n \"path-escape\",\n kind === \"cd\"\n ? `\\`cd ${target}\\` leaves the workspace jail (or enters a denied path); commands must stay inside the workspace.`\n : `The redirection target \\`${target}\\` is outside the workspace jail or on a denied path.`,\n undefined,\n target,\n );\n }\n}\n\n/**\n * Turn a wrapper's \"inner command starts at `start`\" into a range; a\n * wrapper that runs nothing (`start === argv.length`) becomes `undefined`.\n */\nfunction suffixFrom(\n argv: string[],\n start: number | null,\n): Array<[number, number]> | null | undefined {\n if (start === null) {\n return null;\n }\n\n return start < argv.length ? [[start, argv.length]] : undefined;\n}\n\n/** `nohup` / `time` / `command` / `builtin`: skip the listed flags and `--`. */\nfunction unwrapSimple(argv: string[], flags: string[]): number | null {\n let index = 1;\n\n while (index < argv.length) {\n const arg = argv[index] ?? \"\";\n\n if (arg === \"--\") {\n index++;\n\n break;\n }\n\n if (flags.includes(arg)) {\n index++;\n\n continue;\n }\n\n if (arg.startsWith(\"-\") && arg !== \"-\") {\n return null;\n }\n\n break;\n }\n\n return index;\n}\n\n/** `env [-i] [-u NAME] [NAME=value]… command…` */\nfunction unwrapEnv(argv: string[]): number | null {\n let index = 1;\n\n while (index < argv.length) {\n const arg = argv[index] ?? \"\";\n\n if (arg === \"-i\" || arg === \"-\" || arg === \"--ignore-environment\") {\n index++;\n\n continue;\n }\n\n if (arg === \"-u\" || arg === \"--unset\") {\n index += 2;\n\n continue;\n }\n\n if (arg.startsWith(\"--unset=\") || (arg.startsWith(\"-u\") && arg.length > 2)) {\n index++;\n\n continue;\n }\n\n if (arg === \"--\") {\n index++;\n\n break;\n }\n\n if (arg.startsWith(\"-\")) {\n return null;\n }\n\n break;\n }\n\n while (index < argv.length && /^[^=]+=/.test(argv[index] ?? \"\")) {\n index++;\n }\n\n return index;\n}\n\n/** `nice [-n N | -N | --adjustment=N] command…` */\nfunction unwrapNice(argv: string[]): number | null {\n let index = 1;\n\n while (index < argv.length) {\n const arg = argv[index] ?? \"\";\n\n if (arg === \"-n\" || arg === \"--adjustment\") {\n index += 2;\n\n continue;\n }\n\n if (/^-n-?[0-9]+$/.test(arg) || /^--adjustment=-?[0-9]+$/.test(arg) || /^-[0-9]+$/.test(arg)) {\n index++;\n\n continue;\n }\n\n if (arg === \"--\") {\n index++;\n\n break;\n }\n\n if (arg.startsWith(\"-\")) {\n return null;\n }\n\n break;\n }\n\n return index;\n}\n\n/** `timeout [options] DURATION command…` */\nfunction unwrapTimeout(argv: string[]): number | null {\n let index = 1;\n\n while (index < argv.length) {\n const arg = argv[index] ?? \"\";\n\n if (arg === \"-s\" || arg === \"--signal\" || arg === \"-k\" || arg === \"--kill-after\") {\n index += 2;\n\n continue;\n }\n\n if (\n /^-[sk].+$/.test(arg) ||\n arg.startsWith(\"--signal=\") ||\n arg.startsWith(\"--kill-after=\") ||\n arg === \"--preserve-status\" ||\n arg === \"--foreground\" ||\n arg === \"-v\" ||\n arg === \"--verbose\"\n ) {\n index++;\n\n continue;\n }\n\n if (arg === \"--\") {\n index++;\n\n break;\n }\n\n if (arg.startsWith(\"-\")) {\n return null;\n }\n\n break;\n }\n\n // The duration, then the command.\n if (index >= argv.length - 1) {\n return null;\n }\n\n return index + 1;\n}\n\n/** `exec [-cl] [-a name] command…` (bash). */\nfunction unwrapExec(argv: string[]): number | null {\n let index = 1;\n\n while (index < argv.length) {\n const arg = argv[index] ?? \"\";\n\n if (arg === \"-c\" || arg === \"-l\" || arg === \"-cl\" || arg === \"-lc\") {\n index++;\n\n continue;\n }\n\n if (arg === \"-a\") {\n index += 2;\n\n continue;\n }\n\n if (arg === \"--\") {\n index++;\n\n break;\n }\n\n if (arg.startsWith(\"-\")) {\n return null;\n }\n\n break;\n }\n\n return index;\n}\n\n/** The commands `find -exec / -execdir / -ok / -okdir … ;|+` runs. */\nfunction unwrapFind(argv: string[]): Array<[number, number]> | null | undefined {\n const inner: Array<[number, number]> = [];\n\n for (let index = 1; index < argv.length; index++) {\n const arg = argv[index] ?? \"\";\n\n if (arg !== \"-exec\" && arg !== \"-execdir\" && arg !== \"-ok\" && arg !== \"-okdir\") {\n continue;\n }\n\n const start = index + 1;\n let end = start;\n\n while (end < argv.length && argv[end] !== \";\" && argv[end] !== \"+\") {\n end++;\n }\n\n if (end >= argv.length || end === start) {\n return null;\n }\n\n inner.push([start, end]);\n index = end;\n }\n\n return inner.length > 0 ? inner : undefined;\n}\n\n/** Build the argv-mode refusal for a line that is not a single argv. */\nfunction argvModeRefusal(command: string): Refusal {\n const analysis = analyzeArgvCommand(command);\n\n if (analysis.ok) {\n // Not reached: callers only ask for a refusal of a line that failed.\n return new Refusal(\"metacharacter\", \"The command cannot run as a single argv.\");\n }\n\n if (analysis.type === \"empty\") {\n return new Refusal(\"not-allowed\", \"The command is empty.\");\n }\n\n if (analysis.type === \"unbalanced-quotes\") {\n return new Refusal(\n \"unbalanced-quotes\",\n `The command has an unbalanced \\`${analysis.quote}\\` quote. Close it, or quote the argument with the other kind of quote.`,\n );\n }\n\n const token = analysis.token;\n const suffix = \" (run_shell executes a single command without a shell.)\";\n const parsed = parseShellLine(command, \"posix\");\n const segments: ShellSegment[] = parsed.ok ? parsed.segments : [];\n const listOperator = token === \"&&\" || token === \"||\" || token === \";\" || token === \"\\n\";\n\n if (listOperator && segments.length >= 2) {\n const shownOperator = token === \"\\n\" ? \"A line break\" : `\\`${token}\\``;\n const firstWords = (segments[0]?.source ?? \"\").trim().split(/\\s+/);\n\n if (firstWords[0] === \"cd\") {\n const rest = segments.slice(1).map((segment) => show(segment.source));\n\n return new Refusal(\n \"metacharacter\",\n `${shownOperator} is not supported in argv mode, and \\`cd\\` does not carry over: every command starts in the workspace root. Run ${rest.join(\", then \")} on its own, with paths relative to the root.${suffix}`,\n );\n }\n\n const steps = segments.map((segment) => show(segment.source));\n const joiner = token === \"||\" ? \", and if it fails, \" : \", then \";\n\n return new Refusal(\n \"metacharacter\",\n `${shownOperator} is not supported in argv mode: run ${steps.join(joiner)}, as separate commands.${suffix}`,\n );\n }\n\n if (token === \"&\" && segments.length >= 1) {\n const steps = segments.map((segment) => show(segment.source));\n const run =\n steps.length === 1\n ? `run ${steps[0] ?? \"\"} without it`\n : `run ${steps.join(\", then \")} as separate commands`;\n\n return new Refusal(\n \"metacharacter\",\n `\\`&\\` is not supported in argv mode: ${run}; start a long-running command with background: true.${suffix}`,\n );\n }\n\n if (token === \"|\") {\n const pipeline = command\n .split(\"|\")\n .map((part) => part.trim())\n .filter(Boolean);\n\n return new Refusal(\n \"metacharacter\",\n `\\`|\\` (a pipe) is not supported in argv mode: run ${show(pipeline[0] ?? command)} on its own and read its output yourself.${suffix}`,\n );\n }\n\n if (token === \">\" || token === \">>\" || token === \">&\") {\n return new Refusal(\n \"metacharacter\",\n `\\`${token}\\` (redirection) is not supported in argv mode: run the command without it and save the output with write_file if you need it.${suffix}`,\n );\n }\n\n if (token === \"<\" || token === \"<<\" || token === \"<&\") {\n return new Refusal(\n \"metacharacter\",\n `\\`${token}\\` (input redirection) is not supported in argv mode: pass the file path to the command as an argument.${suffix}`,\n );\n }\n\n if (token === \"$(\" || token === \"`\") {\n return new Refusal(\n \"metacharacter\",\n `Command substitution (${token === \"$(\" ? \"`$(…)`\" : \"backticks\"}) is not supported in argv mode: run the inner command first, then pass its output literally.${suffix}`,\n );\n }\n\n if (token === \"$\" || token === \"${\") {\n return new Refusal(\n \"metacharacter\",\n `\\`$\\` (variable expansion) is not supported in argv mode: write the value literally, or put the \\`$\\` inside quotes to pass it as text.${suffix}`,\n );\n }\n\n if (token === \"(\" || token === \")\") {\n return new Refusal(\n \"metacharacter\",\n `\\`${token}\\` is not supported in argv mode: put it inside quotes to pass it as text.${suffix}`,\n );\n }\n\n return new Refusal(\n \"metacharacter\",\n `\\`${token === \"\\n\" ? \"\\\\n\" : token}\\` is not supported in argv mode: run one command per call, and quote any argument that contains it.${suffix}`,\n );\n}\n\n/** Check a shell-mode line. */\nfunction checkShellLine(\n checker: LineChecker,\n policy: WorkspacePolicy,\n command: string,\n dialect: ShellDialect,\n): void {\n if (/^[-+]/.test(command)) {\n throw new Refusal(\"unsupported-syntax\", \"A command line may not start with `-` or `+`.\");\n }\n\n const parsed = parseShellLine(command, dialect);\n\n if (!parsed.ok) {\n if (checker.askMode) {\n checker.addAsk([command], \"unparsed\");\n\n return;\n }\n\n throw new Refusal(\n parsed.reason,\n `${parsed.message} The workspace shell policy only runs lines it can check.`,\n );\n }\n\n if (parsed.background) {\n throw new Refusal(\n \"unsupported-syntax\",\n \"`&` (run in the background) is not supported in a command line: start a long-running command with background: true instead.\",\n );\n }\n\n let cwds = [path.resolve(policy.cwd)];\n\n for (const simple of parsed.commands) {\n cwds = checkSimpleCommand(checker, simple, cwds, dialect);\n }\n}\n\n/** Check one parsed simple command; returns the possible working directories after it. */\nfunction checkSimpleCommand(\n checker: LineChecker,\n simple: ShellSimpleCommand,\n cwds: string[],\n dialect: ShellDialect,\n): string[] {\n for (const assignment of simple.assignments) {\n const name = assignment.assignmentName ?? \"\";\n\n if (SENSITIVE_VARIABLES.has(name) || name.startsWith(\"BASH_FUNC_\")) {\n throw new Refusal(\n \"unsupported-syntax\",\n `Assigning \\`${name}\\` is not supported: it changes how the shell runs the commands that follow.`,\n );\n }\n }\n\n for (const redirect of simple.redirects) {\n if (redirect.type === \"read\" || redirect.type === \"write\") {\n checker.checkRedirectTarget(redirect.target, redirect.type, cwds);\n }\n }\n\n const first = simple.words[0];\n\n if (first === undefined) {\n return cwds;\n }\n\n const executable = first.dynamic ? \"\" : executableBasename(first.value, dialect).toLowerCase();\n const powershell = dialect === \"powershell\" || dialect === \"powershell5\";\n\n if (executable === \"cd\" || (powershell && POWERSHELL_CD_NAMES.has(executable))) {\n // A `cd` the policy cannot follow would let later targets escape the jail:\n // it is refused outright, never turned into an ask.\n checker.assertExecutableNotDenied(\n executable,\n simple.words.map((word) => word.value),\n );\n\n if (powershell) {\n const words = normalizePowerShellCd(simple.words);\n\n if (words === null) {\n throw new Refusal(\n \"unsupported-syntax\",\n \"`Set-Location` is supported with one directory (`-Path` / `-LiteralPath` or positional) only.\",\n \"cd\",\n );\n }\n\n return checker.checkCd(words, cwds);\n }\n\n return checker.checkCd(simple.words, cwds);\n }\n\n try {\n if (!first.dynamic && first.value.includes(\"=\")) {\n throw new Refusal(\n \"unsupported-syntax\",\n `\\`${first.raw}\\` is not a plain command name or \\`NAME=value\\` assignment.`,\n );\n }\n\n if (/^[-+]/.test(first.value)) {\n throw new Refusal(\"unsupported-syntax\", `\\`${first.raw}\\` is not a command name.`);\n }\n\n if (checker.hasArgumentHooks) {\n const dynamic = simple.words.find((word) => word.dynamic);\n\n if (dynamic !== undefined) {\n throw new Refusal(\n \"unsupported-syntax\",\n `\\`${dynamic.raw}\\` is decided when the line runs, so it cannot be checked against the workspace shell policy's command rules. Write the value literally (quote globs).`,\n );\n }\n }\n\n if (\n (dialect === \"posix\" && (executable === \"pushd\" || executable === \"popd\")) ||\n (powershell && POWERSHELL_DIRECTORY_STACK.has(executable))\n ) {\n throw new Refusal(\n \"unsupported-syntax\",\n `\\`${executable}\\` is not supported; use \\`${powershell ? \"Set-Location\" : \"cd\"}\\`.`,\n );\n }\n\n const argv = simple.words.map((word) => (word.dynamic ? word.raw : word.value));\n\n checker.checkArgv(\n argv,\n simple.words.map((word) => word.dynamic),\n 0,\n );\n\n if (powershell) {\n const canonical = canonicalPowerShellArgv(argv);\n\n if (canonical !== undefined && canonical.join(\"\\u0000\") !== argv.join(\"\\u0000\")) {\n checker.checkCanonical(canonical);\n }\n }\n } catch (error) {\n // A command the policy cannot read is put to the host instead of refused.\n if (error instanceof Refusal && error.reason === \"unsupported-syntax\" && checker.askMode) {\n checker.addAsk(\n simple.words.map((word) => (word.dynamic ? word.raw : word.value)),\n \"unparsed\",\n );\n\n return cwds;\n }\n\n throw error;\n }\n\n return cwds;\n}\n\n/**\n * The shell policy's verdict on a command line, without touching the\n * filesystem.\n *\n * - **argv mode** (`interpreter: \"none\"`, the default): the line must\n * tokenize into one argv; otherwise it is refused with a\n * `\"metacharacter\"` / `\"unbalanced-quotes\"` reason and a message that\n * names the operator and suggests the split.\n * - **shell mode** (`interpreter: \"sh\"`): the line is parsed and every\n * simple command — inside `;`, `&&`, `||`, pipes, subshells, `$( )`,\n * backticks and here-documents — is checked; redirection targets and `cd`\n * directories must stay in the jail (checked lexically here; `exec`\n * re-checks them with symlinks resolved).\n *\n * Every argv goes through `deny` (case-insensitive, wins), `allow`\n * (exhaustive when non-empty), `denyPatterns` and `allowCommand`.\n * Wrappers (`env`, `time`, `timeout`, `nice`, `nohup`, `command`,\n * `builtin`, `exec`) and `find -exec` are followed to the command they run.\n * A runner the policy cannot see into (`bash -c`, `eval`, `xargs`, `sudo`,\n * …) is refused while the policy has deny rules, unless `allow` names it.\n *\n * @example\n * checkShellCommand({ cwd, shell: { allow: [\"git\"] } }, \"git add -A && git commit -m wip\");\n * // { allowed: false, reason: \"metacharacter\", message: \"`&&` is not supported in argv mode: run `git add -A`, then `git commit -m wip`, …\" }\n */\nexport function checkShellCommand(\n policy: WorkspacePolicy,\n command: string,\n options?: ShellCheckOptions,\n): WorkspaceShellCheck {\n const resolved =\n options?.resolved ?? resolveShell(policy.shell, options?.host ?? currentShellHost());\n\n return runCheck(\n policy,\n command,\n resolved.interpreter,\n resolved.dialect,\n options?.remembered,\n options?.platform ?? options?.host?.platform ?? process.platform,\n );\n}\n\n/**\n * {@link checkShellCommand} with the shell-mode dialect chosen explicitly\n * instead of from the interpreter (used to test the cmd.exe and PowerShell\n * rules anywhere). Argv mode ignores `dialect`.\n */\nexport function checkShellCommandInDialect(\n policy: WorkspacePolicy,\n command: string,\n dialect: ShellDialect,\n options?: ShellCheckOptions,\n): WorkspaceShellCheck {\n const resolved =\n options?.resolved ?? resolveShell(policy.shell, options?.host ?? currentShellHost());\n\n return runCheck(\n policy,\n command,\n resolved.interpreter,\n dialect,\n options?.remembered,\n options?.platform ?? options?.host?.platform ?? process.platform,\n );\n}\n\n/** Bash on Windows reads `ab\\\\x` as an escape, not a path: refuse and show the forward-slash form. */\nfunction refuseWindowsStylePath(command: string): void {\n const hit = findWindowsStylePath(command);\n\n if (hit !== null) {\n throw new Refusal(\n \"unsupported-syntax\",\n `\\`${hit.word}\\` uses Windows-style backslashes, which bash reads as escapes and so runs a different path. ` +\n `Write the path with forward slashes: \\`${hit.suggestion}\\` (or quote it).`,\n );\n }\n}\n\nfunction runCheck(\n policy: WorkspacePolicy,\n command: string,\n interpreter: ResolvedShellInterpreter,\n dialect: ShellDialect,\n remembered?: readonly WorkspaceCommandPattern[],\n platform: NodeJS.Platform = process.platform,\n): WorkspaceShellCheck {\n const shell = policy.shell;\n\n if (!shell) {\n return {\n allowed: false,\n reason: \"not-allowed\",\n message: \"The workspace has no shell policy, so no command may run.\",\n asks: [],\n commands: [],\n paths: [],\n };\n }\n\n const shellMode = interpreter !== \"none\";\n const checker = new LineChecker(\n policy,\n shell,\n command,\n shellMode ? dialect : \"posix\",\n interpreter,\n remembered,\n );\n\n try {\n if (command.trim() === \"\") {\n throw new Refusal(\"not-allowed\", \"The command is empty.\");\n }\n\n if (shellMode) {\n if (interpreter === \"bash\" && platform === \"win32\") {\n refuseWindowsStylePath(command);\n }\n\n checkShellLine(checker, policy, command, dialect);\n } else {\n const argv = tokenizeCommand(command);\n\n if (argv === null) {\n throw argvModeRefusal(command);\n }\n\n checker.checkArgv(\n argv,\n argv.map(() => false),\n 0,\n );\n }\n\n if (checker.asks.length > 0) {\n const first = checker.asks[0] as WorkspaceShellAsk;\n const shown = show(first.argv.join(\" \"));\n const why = first.rule?.reason !== undefined ? ` ${first.rule.reason}` : \"\";\n\n return {\n allowed: false,\n reason: \"approval-required\",\n message: `${shown} needs approval, and the workspace shell policy has no approval handler (shell.onAsk).${why}`,\n asks: checker.asks,\n commands: checker.commands,\n paths: checker.paths,\n };\n }\n\n return { allowed: true, asks: [], commands: checker.commands, paths: checker.paths };\n } catch (error) {\n if (!(error instanceof Refusal)) {\n throw error;\n }\n\n return {\n allowed: false,\n reason: error.reason,\n message: error.message,\n ...(error.executable !== undefined ? { executable: error.executable } : {}),\n ...(error.refusedPath !== undefined ? { path: error.refusedPath } : {}),\n asks: [],\n commands: checker.commands,\n paths: checker.paths,\n };\n }\n}\n\n/**\n * The simple commands a line would run (pipeline members, `&&` / `||` / `;`\n * parts, subshells and substitutions included), without checking anything.\n * A line the parser cannot read with confidence comes back with `opaque`\n * set to the reason and no commands.\n *\n * The line is read with the policy's shell dialect when its `interpreter`\n * is a shell, and with the platform's default shell otherwise.\n *\n * @example\n * classifyShellCommand(\"a | b\"); // { commands: [[\"a\"], [\"b\"]] }\n */\nexport function classifyShellCommand(\n command: string,\n policy?: Pick<WorkspacePolicy, \"shell\">,\n host?: ShellHost,\n): WorkspaceShellClassification {\n const resolved = resolveShell(policy?.shell, host ?? currentShellHost());\n const dialect = resolved.interpreter === \"none\" ? shellDialect() : resolved.dialect;\n const parsed = parseShellLine(command, dialect);\n\n if (!parsed.ok) {\n return { commands: [], opaque: parsed.message };\n }\n\n return {\n commands: parsed.commands\n .filter((simple) => simple.words.length > 0)\n .map((simple) => simple.words.map((word) => (word.dynamic ? word.raw : word.value))),\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { globToRegExp, isInside, matchesAnyGlob } from "../policy/jail.mjs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
//#region ../../packages/ai-workspace/src/search/walk.ts
|
|
5
|
+
/** Directories `glob` and `grep` never descend into unless `search.skipDirs` says otherwise. */
|
|
6
|
+
const DEFAULT_SEARCH_SKIP_DIRS = [".git", "node_modules"];
|
|
7
|
+
/** Default most files one `glob` or `grep` walk looks at. */
|
|
8
|
+
const DEFAULT_SEARCH_MAX_FILES = 5e4;
|
|
9
|
+
/**
|
|
10
|
+
* The directory a glob can be anchored at: its leading segments that hold no
|
|
11
|
+
* wildcard, never counting the last segment (it may be a file). `""` is the root.
|
|
12
|
+
*/
|
|
13
|
+
function staticGlobPrefix(pattern) {
|
|
14
|
+
const segments = pattern.split("/");
|
|
15
|
+
const kept = [];
|
|
16
|
+
for (let index = 0; index < segments.length - 1; index++) {
|
|
17
|
+
const segment = segments[index] ?? "";
|
|
18
|
+
if (segment === "" || segment === "." || segment === ".." || /[*?[\]{}\\]/.test(segment)) break;
|
|
19
|
+
kept.push(segment);
|
|
20
|
+
}
|
|
21
|
+
return kept.join("/");
|
|
22
|
+
}
|
|
23
|
+
/** Parse the lines of a `.gitignore` located at workspace-relative `base`. */
|
|
24
|
+
function parseGitignore(content, base) {
|
|
25
|
+
const rules = [];
|
|
26
|
+
for (const raw of content.split(/\r?\n/)) {
|
|
27
|
+
let line = raw.trimEnd();
|
|
28
|
+
if (line === "" || line.startsWith("#")) continue;
|
|
29
|
+
const negate = line.startsWith("!");
|
|
30
|
+
if (negate) line = line.slice(1);
|
|
31
|
+
const dirOnly = line.endsWith("/");
|
|
32
|
+
if (dirOnly) line = line.slice(0, -1);
|
|
33
|
+
const anywhere = line.startsWith("**/");
|
|
34
|
+
if (anywhere) line = line.slice(3);
|
|
35
|
+
if (line.startsWith("/")) line = line.slice(1);
|
|
36
|
+
if (line === "") continue;
|
|
37
|
+
rules.push({
|
|
38
|
+
base,
|
|
39
|
+
negate,
|
|
40
|
+
dirOnly,
|
|
41
|
+
anywhere,
|
|
42
|
+
nameOnly: !anywhere && !line.includes("/") && !raw.trimStart().startsWith("/"),
|
|
43
|
+
regex: globToRegExp(line)
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return rules;
|
|
47
|
+
}
|
|
48
|
+
/** Whether the ordered rules (last match wins) ignore the entry at `rel`. */
|
|
49
|
+
function isIgnored(rel, name, isDir, rules) {
|
|
50
|
+
let ignored = false;
|
|
51
|
+
for (const rule of rules) {
|
|
52
|
+
if (rule.dirOnly && !isDir) continue;
|
|
53
|
+
const local = rule.base === "" ? rel : rel.slice(rule.base.length + 1);
|
|
54
|
+
let hit;
|
|
55
|
+
if (rule.nameOnly) hit = rule.regex.test(name);
|
|
56
|
+
else if (rule.anywhere) {
|
|
57
|
+
const parts = local.split("/");
|
|
58
|
+
hit = parts.some((_, index) => rule.regex.test(parts.slice(index).join("/")));
|
|
59
|
+
} else hit = rule.regex.test(local);
|
|
60
|
+
if (hit) ignored = !rule.negate;
|
|
61
|
+
}
|
|
62
|
+
return ignored;
|
|
63
|
+
}
|
|
64
|
+
/** Whether a directory name or relative path is one of the `skipDirs` globs. */
|
|
65
|
+
function isSkipped(name, rel, skipDirs) {
|
|
66
|
+
return skipDirs.some((glob) => glob.includes("/") ? matchesAnyGlob(rel, [glob]) : globToRegExp(glob).test(name));
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* List a directory with entry types: the backend's own typed listing when it has
|
|
70
|
+
* one, else its plain listing plus `stat`, else (no `stat`) a second listing per
|
|
71
|
+
* entry to tell directories from files.
|
|
72
|
+
*/
|
|
73
|
+
async function listTyped(backend, absDir) {
|
|
74
|
+
if (backend.listTyped !== void 0) return backend.listTyped(absDir);
|
|
75
|
+
const entries = await backend.list(absDir);
|
|
76
|
+
return Promise.all(entries.map(async (entry) => {
|
|
77
|
+
if (backend.stat !== void 0) try {
|
|
78
|
+
return {
|
|
79
|
+
path: entry,
|
|
80
|
+
type: (await backend.stat(entry)).type
|
|
81
|
+
};
|
|
82
|
+
} catch {
|
|
83
|
+
return {
|
|
84
|
+
path: entry,
|
|
85
|
+
type: "other"
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
if ((await backend.list(entry)).length === 0) try {
|
|
90
|
+
await backend.readFile(entry);
|
|
91
|
+
return {
|
|
92
|
+
path: entry,
|
|
93
|
+
type: "file"
|
|
94
|
+
};
|
|
95
|
+
} catch {
|
|
96
|
+
return {
|
|
97
|
+
path: entry,
|
|
98
|
+
type: "directory"
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
path: entry,
|
|
103
|
+
type: "directory"
|
|
104
|
+
};
|
|
105
|
+
} catch {
|
|
106
|
+
return {
|
|
107
|
+
path: entry,
|
|
108
|
+
type: "file"
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
/** Read `.gitignore` at `absDir`, or nothing when there is none. */
|
|
114
|
+
async function readGitignore(backend, absDir, base) {
|
|
115
|
+
try {
|
|
116
|
+
return parseGitignore(await backend.readFile(path.join(absDir, ".gitignore")), base);
|
|
117
|
+
} catch {
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Walk the files under `startDir` breadth first. Denied, skipped and
|
|
123
|
+
* gitignored directories are dropped before they are listed, so a tree such as
|
|
124
|
+
* `node_modules` is never read. Directories are read `concurrency` at a time,
|
|
125
|
+
* the signal is checked between batches, and the walk stops at `maxFiles`.
|
|
126
|
+
* Symlinks are never followed; a symlink to a file is reported only when its
|
|
127
|
+
* target is inside the jail root.
|
|
128
|
+
*/
|
|
129
|
+
async function walkFiles(options) {
|
|
130
|
+
const { backend, jailRoot, startDir, denyPaths, skipDirs, signal } = options;
|
|
131
|
+
const files = [];
|
|
132
|
+
let truncated = false;
|
|
133
|
+
const isDenied = (rel) => denyPaths.length > 0 && matchesAnyGlob(rel, denyPaths);
|
|
134
|
+
const isDeniedDir = (rel) => isDenied(rel) || denyPaths.some((glob) => glob.endsWith("/**") && globToRegExp(glob.slice(0, -3)).test(rel));
|
|
135
|
+
const startRel = path.relative(jailRoot, startDir).split(path.sep).join("/");
|
|
136
|
+
let inherited = [];
|
|
137
|
+
if (options.gitignore) {
|
|
138
|
+
const segments = startRel === "" ? [] : startRel.split("/");
|
|
139
|
+
for (let depth = 0; depth < segments.length; depth++) {
|
|
140
|
+
const base = segments.slice(0, depth).join("/");
|
|
141
|
+
const abs = base === "" ? jailRoot : path.join(jailRoot, ...segments.slice(0, depth));
|
|
142
|
+
inherited = inherited.concat(await readGitignore(backend, abs, base));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
let level = [{
|
|
146
|
+
abs: startDir,
|
|
147
|
+
rel: startRel,
|
|
148
|
+
rules: inherited
|
|
149
|
+
}];
|
|
150
|
+
const readDir = async (dir, next) => {
|
|
151
|
+
let entries;
|
|
152
|
+
try {
|
|
153
|
+
entries = await listTyped(backend, dir.abs);
|
|
154
|
+
} catch {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
let rules = dir.rules;
|
|
158
|
+
if (options.gitignore && entries.some((entry) => path.basename(entry.path) === ".gitignore" && entry.type !== "directory")) rules = rules.concat(await readGitignore(backend, dir.abs, dir.rel));
|
|
159
|
+
for (const entry of entries) {
|
|
160
|
+
if (files.length >= options.maxFiles) {
|
|
161
|
+
truncated = true;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const name = path.basename(entry.path);
|
|
165
|
+
const rel = dir.rel === "" ? name : `${dir.rel}/${name}`;
|
|
166
|
+
let type = entry.type;
|
|
167
|
+
let abs = entry.path;
|
|
168
|
+
if (type === "symlink") try {
|
|
169
|
+
const target = await backend.realpath(entry.path);
|
|
170
|
+
if (!isInside(target, jailRoot) || backend.stat === void 0) continue;
|
|
171
|
+
if ((await backend.stat(target)).type !== "file") continue;
|
|
172
|
+
type = "file";
|
|
173
|
+
abs = entry.path;
|
|
174
|
+
} catch {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (type === "directory") {
|
|
178
|
+
if (isDeniedDir(rel) || isSkipped(name, rel, skipDirs)) continue;
|
|
179
|
+
if (options.gitignore && isIgnored(rel, name, true, rules)) continue;
|
|
180
|
+
next.push({
|
|
181
|
+
abs,
|
|
182
|
+
rel,
|
|
183
|
+
rules
|
|
184
|
+
});
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (type !== "file" || isDenied(rel)) continue;
|
|
188
|
+
if (options.gitignore && isIgnored(rel, name, false, rules)) continue;
|
|
189
|
+
files.push(abs);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
while (level.length > 0 && !truncated) {
|
|
193
|
+
const next = [];
|
|
194
|
+
for (let index = 0; index < level.length && !truncated; index += options.concurrency) {
|
|
195
|
+
signal?.throwIfAborted();
|
|
196
|
+
await Promise.all(level.slice(index, index + options.concurrency).map((dir) => readDir(dir, next)));
|
|
197
|
+
}
|
|
198
|
+
signal?.throwIfAborted();
|
|
199
|
+
level = next;
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
files,
|
|
203
|
+
truncated
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
//#endregion
|
|
208
|
+
export { DEFAULT_SEARCH_MAX_FILES, DEFAULT_SEARCH_SKIP_DIRS, listTyped, staticGlobPrefix, walkFiles };
|
|
209
|
+
//# sourceMappingURL=walk.mjs.map
|