fullcourtdefense-cli 1.35.7 → 1.35.9

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.
Files changed (41) hide show
  1. package/dist/actionIdentity.js +2 -1
  2. package/dist/actionPolicyEngine.d.ts +38 -0
  3. package/dist/actionPolicyEngine.js +383 -24
  4. package/dist/approvalPrompt.js +2 -2
  5. package/dist/askDialog.js +5 -4
  6. package/dist/blockExplanation.js +2 -0
  7. package/dist/callerEnv.d.ts +33 -0
  8. package/dist/callerEnv.js +90 -0
  9. package/dist/commandNarrative.js +111 -4
  10. package/dist/commands/daemon.js +1 -1
  11. package/dist/commands/deterministicGuard.d.ts +32 -0
  12. package/dist/commands/deterministicGuard.js +214 -19
  13. package/dist/commands/hook.d.ts +34 -1
  14. package/dist/commands/hook.js +188 -31
  15. package/dist/commands/mcpGateway.js +2 -0
  16. package/dist/commands/taintLedger.js +3 -1
  17. package/dist/commands/workloadProtect.js +6 -2
  18. package/dist/devConfirm.d.ts +28 -12
  19. package/dist/devConfirm.js +134 -48
  20. package/dist/hookSlim.d.ts +1 -1
  21. package/dist/hookSlim.js +22 -2
  22. package/dist/nativeHook.d.ts +2 -1
  23. package/dist/nativeHook.js +109 -3
  24. package/dist/notify.js +4 -0
  25. package/dist/shellAst.d.ts +117 -0
  26. package/dist/shellAst.js +590 -0
  27. package/dist/shellAstLoader.d.ts +32 -0
  28. package/dist/shellAstLoader.js +271 -0
  29. package/dist/telemetry.d.ts +20 -0
  30. package/dist/telemetry.js +7 -0
  31. package/dist/vendor/tree-sitter/LICENSE.tree-sitter-bash +21 -0
  32. package/dist/vendor/tree-sitter/LICENSE.web-tree-sitter +21 -0
  33. package/dist/vendor/tree-sitter/VERSIONS.json +4 -0
  34. package/dist/vendor/tree-sitter/tree-sitter-bash.wasm +0 -0
  35. package/dist/vendor/tree-sitter/tree-sitter.cjs +4031 -0
  36. package/dist/vendor/tree-sitter/tree-sitter.wasm +0 -0
  37. package/dist/verdictIpc.js +5 -2
  38. package/dist/verdictIpcClient.d.ts +11 -1
  39. package/dist/verdictIpcClient.js +12 -2
  40. package/dist/version.json +1 -1
  41. package/package.json +9 -6
@@ -27,10 +27,12 @@ __export(actionPolicyEngine_exports, {
27
27
  MACHINE_KINDS: () => MACHINE_KINDS,
28
28
  WORKSTATION_ASK_APPROVAL: () => WORKSTATION_ASK_APPROVAL,
29
29
  approvalExceptionError: () => approvalExceptionError,
30
+ canonicalAgentClient: () => canonicalAgentClient,
30
31
  deriveToolOperations: () => deriveToolOperations,
31
32
  detectSecretKind: () => detectSecretKind,
32
33
  effectiveApprovalScope: () => effectiveApprovalScope,
33
34
  evaluateActionPolicies: () => evaluateActionPolicies,
35
+ getShellAstMode: () => getShellAstMode,
34
36
  inferBundledToolContext: () => inferBundledToolContext,
35
37
  inferToolContext: () => inferToolContext,
36
38
  inventoryToolMatchesActionPolicy: () => inventoryToolMatchesActionPolicy,
@@ -40,6 +42,9 @@ __export(actionPolicyEngine_exports, {
40
42
  resolveApprovalOptions: () => resolveApprovalOptions,
41
43
  roleExceptionError: () => roleExceptionError,
42
44
  ruleNeedsContentFact: () => ruleNeedsContentFact,
45
+ setShellAstMode: () => setShellAstMode,
46
+ setShellAstProvider: () => setShellAstProvider,
47
+ takeShellAstOperationComparison: () => takeShellAstOperationComparison,
43
48
  toolCapabilities: () => toolCapabilities
44
49
  });
45
50
  module.exports = __toCommonJS(actionPolicyEngine_exports);
@@ -6039,16 +6044,93 @@ function provenJavaScriptReadUrls(code) {
6039
6044
  return [];
6040
6045
  }
6041
6046
  }
6047
+ var PY_READ_IMPORTS = /* @__PURE__ */ new Set(["urllib", "urllib.request", "urllib.parse", "urllib.error", "json", "re", "sys", "time", "datetime", "pprint", "textwrap", "collections", "itertools", "functools", "math", "string", "requests"]);
6048
+ var PY_INPUT_OR_EFFECT_RE = /__|"""|#|\b(?:open|input|stdin|argv|orig_argv|environ|getenv|getproxies\w*|proxy_bypass\w*|netrc|get_netrc_auth|get_environ_proxies|modules|exec|eval|compile|execfile|breakpoint\w*|help|codecs|importlib|getattr|setattr|delattr|globals|locals|vars|subprocess|system|popen|socket|pickle|marshal|ctypes|os|io|shutil|pathlib|glob|platform|getpass|builtins|_getframe|gi_frame|cr_frame|ag_frame|tb_frame|f_builtins|f_globals|f_locals|format|format_map|Formatter|Template|chr|post|put|patch|delete|urlretrieve|build_opener|install_opener|add_header|add_unredirected_header|add_data|Session|session|lambda|partial)\b|(?<!urllib\.)\brequest\b|\.(?:data|method|full_url|selector|host)\s*=|\b(?:urlopen|Request|get)\s*=(?!=)|\bsys\s*\.\s*(?!(?:exit|stdout|stderr)\b)\w|\.\s*sys\b|[)\]]\s*\(|"\s*\+|\+\s*"|%\s*[(\w]/;
6049
+ function maskPythonStrings(code) {
6050
+ return code.replace(/(?<!\w)([rRfF]{0,2})"((?:[^"\\\n]|\\.)*)"/g, (_m, prefix, body) => /f/i.test(prefix) ? `"${(body.match(/\{[^{}]*\}/g) || []).join(" ")}"` : '""');
6051
+ }
6052
+ var PY_REQUEST_CALL_RE = /\b(?:urllib\.request\.)?(?:urlopen|Request)\b|\brequests\.(?:get|head)\b/g;
6053
+ function pythonCallArguments(code, open) {
6054
+ const args = [];
6055
+ let depth = 0;
6056
+ let quote = "";
6057
+ let current2 = "";
6058
+ for (let i2 = open + 1; i2 < code.length; i2++) {
6059
+ const ch = code[i2];
6060
+ if (quote) {
6061
+ current2 += ch;
6062
+ if (ch === "\\") {
6063
+ current2 += code[++i2] ?? "";
6064
+ continue;
6065
+ }
6066
+ if (ch === quote) quote = "";
6067
+ continue;
6068
+ }
6069
+ if (ch === '"' || ch === "'") {
6070
+ quote = ch;
6071
+ current2 += ch;
6072
+ continue;
6073
+ }
6074
+ if (ch === "(" || ch === "[" || ch === "{") depth++;
6075
+ if (ch === ")" || ch === "]" || ch === "}") {
6076
+ if (depth === 0) {
6077
+ if (ch !== ")") return void 0;
6078
+ args.push(current2.trim());
6079
+ return args.filter(Boolean);
6080
+ }
6081
+ depth--;
6082
+ }
6083
+ if (ch === "," && depth === 0) {
6084
+ args.push(current2.trim());
6085
+ current2 = "";
6086
+ continue;
6087
+ }
6088
+ current2 += ch;
6089
+ }
6090
+ return void 0;
6091
+ }
6092
+ function provenPythonReadUrls(code) {
6093
+ if (code.length > 16384) return [];
6094
+ const words = maskPythonStrings(code);
6095
+ if (PY_INPUT_OR_EFFECT_RE.test(words)) return [];
6096
+ for (const statement of words.matchAll(/(?:^|[\n;])[ \t]*(?:from[ \t]+([\w.]+)[ \t]+import\b([^\n;]*)|import[ \t]+([^\n;]+))/g)) {
6097
+ if (/\bas\b/.test(statement[2] ?? statement[3] ?? "")) return [];
6098
+ const modules = statement[1] !== void 0 ? [statement[1]] : statement[3].split(",").map((name) => name.trim());
6099
+ if (!modules.every((name) => PY_READ_IMPORTS.has(name))) return [];
6100
+ }
6101
+ const requestVariables = new Set([...words.matchAll(/\b([A-Za-z_]\w*)\s*=\s*(?:urllib\.request\.)?Request\s*\(/g)].map((m) => m[1]));
6102
+ const urls = [];
6103
+ for (const call of code.matchAll(PY_REQUEST_CALL_RE)) {
6104
+ const after = (call.index ?? 0) + call[0].length;
6105
+ const open = code.slice(after).search(/\S/) + after;
6106
+ if (code[open] !== "(") return [];
6107
+ const args = pythonCallArguments(code, open);
6108
+ if (!args || args.length === 0) return [];
6109
+ const [target, ...options] = args;
6110
+ if (!options.every((option) => /^timeout\s*=\s*\d+(?:\.\d+)?$/.test(option) || /^context\s*=\s*[A-Za-z_]\w*$/.test(option) || /^headers\s*=\s*\{\s*(?:"[^"\\{}]*"\s*:\s*"[^"\\{}]*"\s*,?\s*)*\}$/.test(option))) return [];
6111
+ if (/urlopen$/.test(call[0]) && requestVariables.has(target)) continue;
6112
+ const literal2 = /^[fr]?"(https?:\/\/([A-Za-z0-9.-]+)(?::\d+)?)([/?][^"\\]*)?"$/.exec(target);
6113
+ if (!literal2) return [];
6114
+ if (/^[\d.]+$/.test(literal2[2]) || isInternalHost(literal2[2])) return [];
6115
+ urls.push(literal2[1] + (literal2[3] || "/").replace(/\{[\s\S]*$/, ""));
6116
+ }
6117
+ return urls;
6118
+ }
6042
6119
  function provenInlineJavaScriptReadUrls(command) {
6043
6120
  const urls = [];
6044
6121
  for (const segment of splitShellSegments(command)) {
6045
6122
  const here = segment.match(/^\s*@'\r?\n([\s\S]*?)\r?\n'@\s*\|\s*node(?:\.exe)?(?:\s+-)?\s*$/i);
6046
6123
  const inline = segment.match(/^\s*node(?:\.exe)?\s+(?:-e|--eval)\s+'([^']*)'\s*$/i);
6124
+ const python = segment.match(/^\s*python3?\s+-c\s+'([^']*)'\s*$/);
6047
6125
  const code = here?.[1] ?? inline?.[1];
6048
6126
  if (code !== void 0) {
6049
6127
  const proven = provenJavaScriptReadUrls(code);
6050
6128
  if (!proven.length) return [];
6051
6129
  urls.push(...proven);
6130
+ } else if (python) {
6131
+ const proven = provenPythonReadUrls(python[1]);
6132
+ if (!proven.length) return [];
6133
+ urls.push(...proven);
6052
6134
  } else {
6053
6135
  const lead = shellSegmentLead(segment);
6054
6136
  if (!(SHELL_READ_PROGRAMS.has(lead) || ["git", "gh", "cd", "set-location"].includes(lead)) || classifyShellCommandOperation(segment) !== "read" || /https?:\/\//i.test(segment)) return [];
@@ -6481,14 +6563,21 @@ function toolCapabilities(value) {
6481
6563
  addCapabilityIfAny(capabilities, words, "mcp", ["mcp", "tool", "server"]);
6482
6564
  return capabilities;
6483
6565
  }
6484
- function toolNameIdentityMatches(actual, expected) {
6485
- const actualLower = actual.toLowerCase();
6486
- const expectedLower = expected.toLowerCase();
6487
- if (actualLower === expectedLower) return true;
6488
- if (actualLower.includes(expectedLower) || expectedLower.includes(actualLower)) return true;
6489
- const actualWords = normalizedWords(actual);
6490
- const expectedWords = normalizedWords(expected);
6491
- return [...expectedWords].some((word) => word.length >= 4 && actualWords.has(word));
6566
+ function canonicalToolIdentity(value) {
6567
+ const lower = value.trim().toLowerCase();
6568
+ const mcp = /^mcp__(.+?)__(.+)$/.exec(lower);
6569
+ if (mcp) return { server: mcp[1], tool: mcp[2] };
6570
+ const slash = lower.lastIndexOf("/");
6571
+ if (slash > 0 && slash < lower.length - 1) return { server: lower.slice(0, slash).replace(/^@/, ""), tool: lower.slice(slash + 1) };
6572
+ const qualified = /^.+(?:__|\.)(.+)$/.exec(lower);
6573
+ return { tool: qualified ? qualified[1] : lower };
6574
+ }
6575
+ function toolNameIdentityMatches(actual, expected, actualServer) {
6576
+ const call = canonicalToolIdentity(actual);
6577
+ const rule = canonicalToolIdentity(expected);
6578
+ if (call.tool !== rule.tool) return false;
6579
+ const server = call.server || actualServer?.trim().toLowerCase();
6580
+ return !rule.server || !server || server === rule.server;
6492
6581
  }
6493
6582
  var IDENTITY_CONSTRAINT_FIELDS = /* @__PURE__ */ new Set(["developerName", "machineName"]);
6494
6583
  function normalizeHostPart(value) {
@@ -6505,6 +6594,11 @@ var PATH_CONSTRAINT_FIELDS = /* @__PURE__ */ new Set(["path"]);
6505
6594
  function normalizePathValue(value) {
6506
6595
  return value.replace(/\\/g, "/");
6507
6596
  }
6597
+ var AGENT_CLIENT_ALIASES = { gemini: "gemini-cli", "vs-code": "vscode" };
6598
+ function canonicalAgentClient(value) {
6599
+ const id = value.trim().toLowerCase().replace(/[\s_]+/g, "-");
6600
+ return AGENT_CLIENT_ALIASES[id] || id;
6601
+ }
6508
6602
  function evaluateConstraint(constraint, context) {
6509
6603
  let actual = context[constraint.field] || "";
6510
6604
  let expected = constraint.value;
@@ -6512,6 +6606,10 @@ function evaluateConstraint(constraint, context) {
6512
6606
  actual = normalizePathValue(actual);
6513
6607
  expected = normalizePathValue(expected);
6514
6608
  }
6609
+ if (constraint.field === "agentClient") {
6610
+ actual = canonicalAgentClient(actual);
6611
+ expected = canonicalAgentClient(expected);
6612
+ }
6515
6613
  if (COMMAND_LINE_FIELDS.has(constraint.field) && /^(?:not_)?(?:matches|contains)$/.test(constraint.operator)) {
6516
6614
  actual = shellCommandWords(actual);
6517
6615
  }
@@ -6538,9 +6636,9 @@ function evaluateConstraint(constraint, context) {
6538
6636
  if (constraint.field === "toolName") {
6539
6637
  switch (constraint.operator) {
6540
6638
  case "equals":
6541
- return toolNameIdentityMatches(actual, expected);
6639
+ return toolNameIdentityMatches(actual, expected, context.mcpServer);
6542
6640
  case "not_equals":
6543
- return actual.toLowerCase() !== expected.toLowerCase();
6641
+ return !toolNameIdentityMatches(actual, expected, context.mcpServer);
6544
6642
  case "contains":
6545
6643
  return actual.toLowerCase().includes(expected.toLowerCase());
6546
6644
  case "not_contains":
@@ -6944,7 +7042,7 @@ function isHighEntropySecretToken(token, allowWordSlug = false) {
6944
7042
  if (/^sha(?:1|256|384|512)-/i.test(token)) return false;
6945
7043
  if (/^data:/i.test(token) || /^[A-Za-z0-9+/]{200,}={0,2}$/.test(token)) return false;
6946
7044
  if (/^(?:https?|s3|gs|file):\/\//i.test(token) || /[\\/]{1}[\w.-]+[\\/]/.test(token)) return false;
6947
- if (allowWordSlug && /^[A-Za-z_][\w.-]*\/[A-Za-z_][\w.-]*\.[A-Za-z][A-Za-z0-9]{0,9}$/.test(token)) return false;
7045
+ if (allowWordSlug && /^[A-Za-z_][\w.-]*\/\w[\w.-]*\.[A-Za-z][A-Za-z0-9]{0,9}$/.test(token)) return false;
6948
7046
  if (allowWordSlug && /[/.]/.test(token) && token.split(/[/.]/).length >= 2 && token.split(/[/.]/).every((part) => /^[A-Za-z][a-z]*(?:[A-Z][a-z0-9]*)*$/.test(part))) return false;
6949
7047
  if (/^[a-z]+(?:[A-Z][a-z0-9]+)+$/.test(token) || /^[a-z]+(?:_[a-z0-9]+)+$/.test(token)) return false;
6950
7048
  if (allowWordSlug && /^[A-Z][a-z]+-(?:[A-Z][a-z]+){2,}$/.test(token)) return false;
@@ -6960,6 +7058,12 @@ function isHighEntropySecretToken(token, allowWordSlug = false) {
6960
7058
  var SECRET_NAME = String.raw`(?:api[_-]?key|apikey|access[_-]?token|auth[_-]?token|refresh[_-]?token|bearer[_-]?token|client[_-]?secret|secret[_-]?key|secret|password|passwd|pwd|credentials?|private[_-]?key|token)`;
6961
7059
  var SECRET_ASSIGNMENT_RE = new RegExp(String.raw`(?:^|[^\w])[\w.-]*?${SECRET_NAME}[\\"']*\s*(?:=>|[:=])\s*[\\"']*([^\s\\"',;&|)}\]<>]{8,})`, "gi");
6962
7060
  var SECRET_FLAG_RE = /(?:^|[\s"'`;(\[{,])--?(?:password|passwd|pwd|token|api-?key|secret|client-secret|access-token)(?:=|\s+)[\\"']*([^\s\\"',;&|)}\]<>-][^\s\\"',;&|)}\]<>]{7,})/gi;
7061
+ var SECRET_FLAG_QUOTED_RE = /(?:^|[\s"'`;(\[{,])--?(?:password|passwd|pwd|token|api-?key|secret|client-secret|access-token)(?:=|\s+)\\?(["'])([^\s"'\\-][^\s"'\\]{7,})\\?\1/gi;
7062
+ function isWordPhraseWithCounters(value) {
7063
+ const parts = value.split(/[-_.]/);
7064
+ const words = parts.filter((part) => /^(?:[a-z]+|[A-Z]+|[A-Z][a-z]+)$/.test(part));
7065
+ return parts.length >= 3 && words.length >= 2 && words.some((word) => word.length >= 3) && parts.every((part) => words.includes(part) || /^\d{1,6}(?:[a-z]+)?$/.test(part));
7066
+ }
6963
7067
  var SECRET_PLACEHOLDER_RE = /^(?:<.*>|\$\{?[A-Za-z_][\w.]*\}?|%[A-Za-z_]+%|\{\{.*\}\}|\[.*\]|\*{3,}|x{4,}|X{4,}|\.{3,}|-{3,}|_{3,}|(?:your|my|our|the|a|an)(?:[-_.][\w.-]*)?|(?:some|example|sample|dummy|fake|test|mock|placeholder|redacted|masked|hidden|changeme|change_me|replace_?me|todo|tbd|fixme|none|null|nil|undefined|true|false|required|optional|omitted|removed|unset|empty|secret|password|token|xxx+|abc+|123+)[\w.-]*)$/i;
6964
7068
  var SECRET_CODE_EXPR_RE = /^(?:[A-Za-z_$][\w$]*(?:[.\[][^\s=]*)+|[A-Za-z_$][\w$]*\(.*\)?|process\.env\.\w+|os\.environ.*|env(?:iron)?\.\w+|new\s+\w+.*|await\s+.*|require\(.*)$/;
6965
7069
  function looksLikeSecretValue(raw) {
@@ -6980,6 +7084,7 @@ function looksLikeSecretValue(raw) {
6980
7084
  if (REPEATED_CHUNK_RE.test(value)) return false;
6981
7085
  if (PLACEHOLDER_WORD_RE.test(value)) return false;
6982
7086
  if (/^[a-z]+(?:[-_.][a-z]+)+$/i.test(value)) return false;
7087
+ if (isWordPhraseWithCounters(value)) return false;
6983
7088
  if (/^(?:change|replace|your|my|our|example|sample|dummy|fake|test|mock|placeholder|not[-_]?real|do[-_]?not[-_]?use|insert|enter|put|paste)[-_]/i.test(value)) return false;
6984
7089
  let classes = 0;
6985
7090
  if (/[a-z]/.test(value)) classes++;
@@ -7018,6 +7123,10 @@ function detectSecretKind(raw) {
7018
7123
  if (looksLikeSecretValue(m[1])) return "assignment";
7019
7124
  }
7020
7125
  }
7126
+ SECRET_FLAG_QUOTED_RE.lastIndex = 0;
7127
+ for (let m = SECRET_FLAG_QUOTED_RE.exec(value); m !== null; m = SECRET_FLAG_QUOTED_RE.exec(value)) {
7128
+ if (looksLikeSecretValue(m[2])) return "assignment";
7129
+ }
7021
7130
  for (const re of [GENERIC_VALUE_RE, AUTH_HEADER_RE]) {
7022
7131
  re.lastIndex = 0;
7023
7132
  let m;
@@ -7167,6 +7276,8 @@ function withoutUnattributedJavaScriptUrls(segment) {
7167
7276
  function extractUrl(args, _argsText) {
7168
7277
  const direct = args.url || args.uri || args.endpoint || args.webhookUrl || args.callbackUrl || args.href || args.link;
7169
7278
  if (typeof direct === "string" && direct.trim()) return direct.trim();
7279
+ const listed = Array.isArray(args.urls) ? args.urls.find((url) => typeof url === "string" && url.trim()) : void 0;
7280
+ if (listed) return listed.trim();
7170
7281
  for (const field of URL_ACTION_FIELDS) {
7171
7282
  const raw = args[field];
7172
7283
  if (typeof raw !== "string") continue;
@@ -7726,7 +7837,27 @@ function shellCommandWords(command, depth = 0) {
7726
7837
  }
7727
7838
  return parts.join(" ; ");
7728
7839
  }
7729
- function isWorkspaceScopedRemoval(command) {
7840
+ function cdScopeOfSegment(segment) {
7841
+ const m = /^\s*(?:(?:then|do|else)\s+)?cd\s+(?:--\s+)?("[^"]*"|'[^']*'|[^\s;&|)]+)/i.exec(segment);
7842
+ if (!m) return void 0;
7843
+ const target = m[1].replace(/^["']|["']$/g, "").trim().replace(/\\/g, "/");
7844
+ const lower = target.toLowerCase();
7845
+ if (!lower) return "unscoped";
7846
+ if (/^(?:\/+\*?|~\/*\*?|\.{1,2}\/?|-|\$home\/?|\$\{home\}\/?|%userprofile%\/?|\$env:userprofile\/?|[a-z]:\/*\*?)$/.test(lower)) return "unscoped";
7847
+ if (/^(?:\$\{?[a-z_][a-z0-9_]*\}?|%[a-z_][a-z0-9_]*%|\$env:[a-z_][a-z0-9_]*)\/?$/.test(lower)) return "unscoped";
7848
+ return lower.split("/").some((part) => part && part !== "." && part !== "..") ? "scoped" : "unscoped";
7849
+ }
7850
+ function isNestedScratchOrWorkPath(target) {
7851
+ const t = target.replace(/\\/g, "/").replace(/\/+$/, "");
7852
+ const named = (parts2) => parts2.every((part) => part && !part.startsWith(".") && !/[*?[\]{}$%`]/.test(part));
7853
+ const tmp = /^(?:\/private)?\/(?:tmp|var\/tmp)\/(.+)$/.exec(t);
7854
+ if (tmp) return named(tmp[1].split("/"));
7855
+ const home = /^(?:~|\$\{?HOME\}?|\/home\/[^/.$*?]+|\/Users\/[^/.$*?]+|[A-Za-z]:\/Users\/[^/.$*?]+)\/(.+)$/i.exec(t);
7856
+ if (!home) return false;
7857
+ const parts = home[1].split("/");
7858
+ return parts.length >= 2 && named(parts) && !/^(?:Library|AppData)$/i.test(parts[0]);
7859
+ }
7860
+ function isWorkspaceScopedRemoval(command, cwdScoped = false) {
7730
7861
  const segments = splitShellSegments(command, true);
7731
7862
  const removal = segments.find((seg) => /\b(rm|del|remove-item|rmdir)\b/i.test(seg)) || command;
7732
7863
  const tokens = removal.trim().split(/\s+/).slice(1);
@@ -7734,8 +7865,9 @@ function isWorkspaceScopedRemoval(command) {
7734
7865
  if (targets.length === 0) return false;
7735
7866
  return targets.every((raw) => {
7736
7867
  const t = raw.replace(/^["']|["']$/g, "");
7737
- if (!t || t === "*" || t === "." || t === ".." || /^\.\/?\*$/.test(t) || /^\.\\\*$/.test(t)) return false;
7738
- if (/^[\/\\]/.test(t) || /^[A-Za-z]:/.test(t) || /^~/.test(t)) return false;
7868
+ if (t === "*" || /^\.\/?\*$/.test(t) || /^\.\\\*$/.test(t)) return cwdScoped;
7869
+ if (!t || t === "." || t === "..") return false;
7870
+ if (/^[\/\\]/.test(t) || /^[A-Za-z]:/.test(t) || /^~/.test(t) || /^\$\{?HOME\}?[\/\\]/.test(t)) return isNestedScratchOrWorkPath(t);
7739
7871
  if (/\$|%|\$\{|\$\(|`/.test(t)) return false;
7740
7872
  if (/(^|[\/\\])\.\.([\/\\]|$)/.test(t)) return false;
7741
7873
  if (/^\*\*?$|^[\/\\]?\*$/.test(t)) return false;
@@ -7860,7 +7992,7 @@ function isInertValueExpression(raw) {
7860
7992
  return /^(?:""|-?\d)/.test(body);
7861
7993
  }
7862
7994
  var worstShellOp = (ops, floor = "read") => ops.reduce((w, op) => (SHELL_OP_RANK[op] || 0) > (SHELL_OP_RANK[w] || 0) ? op : w, floor);
7863
- function classifyShellSegment(segment, depth = 0) {
7995
+ function classifyShellSegment(segment, depth = 0, cwdScoped = false) {
7864
7996
  const raw = segment.trim();
7865
7997
  if (!raw) return "read";
7866
7998
  if (PS_SESSION_VARIABLE_ASSIGN_RE.test(raw)) return "SHELL";
@@ -7892,12 +8024,30 @@ function classifyShellSegment(segment, depth = 0) {
7892
8024
  }
7893
8025
  return worstShellOp([classifyShellWords(maskQuotedSpans(raw), raw, lead)], "SHELL");
7894
8026
  }
7895
- return classifyShellWords(maskQuotedSpans(raw), raw, lead);
7896
- }
7897
- function classifyShellWords(masked, raw, lead) {
8027
+ return classifyShellWords(maskQuotedSpans(raw), raw, lead, cwdScoped);
8028
+ }
8029
+ var STDIN_DATA_MODULES = /* @__PURE__ */ new Set(["json.tool", "base64"]);
8030
+ var CODE_RUNS_OR_SENDS_INPUT_RE = /\b(?:exec|eval|compile|execfile|runpy|runsource|importlib|import_module|getattr|setattr|delattr|globals|locals|vars|builtins|modules|_getframe|gi_frame|f_builtins|f_globals|f_locals|Function|constructor|binding|vm|pickle|cPickle|marshal|shelve|dill|yaml|jsonpickle|require|import\s*\(|open|write|writeFile\w*|os|shutil|pathlib|fs|socket|urllib|requests|httpx|http|https|fetch|net|system|spawn|popen|Popen|subprocess|Open3|qx|readpipe|do|send|breakpoint\w*|set_trace|pdb|pty|code|interact|timeit|cProfile|profile|doctest)\b|__|(?:instance|class|module)_eval\b|[)\]]\s*\(|["']\s*\+|\+\s*["']|chr\s*\(|%x[\[{(]|`/;
8031
+ var PERL_CODE_REGEX_RE = /(?:^|[^\w$@%&])s\s*[^\w\s]|\(\?\??\{/;
8032
+ function interpreterStageReadsStdinAsData(stage) {
8033
+ const pieces = stage.trim().match(/"(?:[^"\\]|\\.)*"|'[^']*'|[^\s"']+/g) || [];
8034
+ if (pieces.join("").replace(/\s/g, "") !== stage.replace(/\s/g, "")) return false;
8035
+ const lead = pieces.shift() || "";
8036
+ if (!/^(?:python3?|perl)$/.test(lead)) return false;
8037
+ if (lead.startsWith("python") && pieces.length === 2 && pieces[0] === "-m") return STDIN_DATA_MODULES.has(pieces[1]);
8038
+ const flag = lead.startsWith("python") ? /^-c$/ : /^-[lnapF]*e$/;
8039
+ if (pieces.length !== 2 || !flag.test(pieces[0])) return false;
8040
+ const program = pieces[1];
8041
+ if (!/^'[^']*'$/.test(program) && !/^"[^"$`\\]*"$/.test(program)) return false;
8042
+ const code = program.slice(1, -1);
8043
+ if (lead === "perl" && PERL_CODE_REGEX_RE.test(code)) return false;
8044
+ return !CODE_RUNS_OR_SENDS_INPUT_RE.test(code) && !codeSpawnsProcess(code, lead);
8045
+ }
8046
+ function classifyShellWords(masked, raw, lead, cwdScoped = false) {
7898
8047
  const value = masked.trim().toLowerCase();
7899
8048
  if (!value) return "read";
7900
- const pipesIntoInterpreter = /\|\s*(?:sudo\s+)?(?:ba|z|da|k)?sh\b|\|\s*(?:pwsh|powershell|python3?|node|perl|ruby)\b/.test(value);
8049
+ const interpreterStages = splitShellSegments(raw, true).slice(1).filter((stage) => /^(?:sudo\s+)?(?:python3?|node|perl|ruby)\b/i.test(stage));
8050
+ const pipesIntoInterpreter = /\|\s*(?:sudo\s+)?(?:ba|z|da|k)?sh\b|\|\s*(?:sudo\s+)?(?:pwsh|powershell)\b/.test(value) || /\|\s*(?:sudo\s+)?(?:python3?|node|perl|ruby)\b/.test(value) && !(interpreterStages.length > 0 && interpreterStages.every(interpreterStageReadsStdinAsData));
7901
8051
  const segment = raw;
7902
8052
  if (/\b(curl|wget|invoke-restmethod|invoke-webrequest|iwr|irm)\b/.test(value)) {
7903
8053
  const explicitMethod = /(?:\s-x|\s--request|\s-method)[\s=]+["']?(post|put|patch|delete)\b/i.test(segment);
@@ -7942,8 +8092,10 @@ function classifyShellWords(masked, raw, lead) {
7942
8092
  if (GCLOUD_WRITE_VERB_RE.test(gcloudVerb)) return pipesIntoInterpreter ? "SHELL" : "write";
7943
8093
  }
7944
8094
  if (/^(?:kubectl\s+(?:get|describe|logs|top|config\s+(?:current-context|get-contexts|view(?!\s+--raw)))|docker\s+(?:ps|images|logs|inspect|version|info)|terraform\s+(?:plan|validate|fmt|show|state\s+list|output)|aws\s+(?:s3\s+ls|sts\s+get-caller-identity)|az\s+account\s+show|npm\s+(?:ls|list|view|outdated|--version)|node\s+--version|python3?\s+--version)\b/.test(value)) return "read";
7945
- if (/\b(rm\s+-[a-z]*r[a-z]*f|rm\s+-[a-z]*f[a-z]*r|del\s+\/[fqs]|remove-item\b.*\s-recurse\b|rmdir\s+\/s)\b/.test(value)) {
7946
- return isWorkspaceScopedRemoval(segment) ? "write" : "DELETE";
8095
+ const rmFlags = /\brm\s+((?:--?[a-z][a-z-]*\s+)+)/.exec(value) || /\brm\s+((?:--?[a-z][a-z-]*\s+)+)/.exec(maskQuotedSpans(raw.replace(/["'](--?[A-Za-z][A-Za-z-]*)["']/g, "$1")).toLowerCase());
8096
+ const rmRecursiveForce = Boolean(rmFlags && /(?:^|\s)(?:-[a-z]*r[a-z]*|--recursive)\s/.test(rmFlags[1]) && /(?:^|\s)(?:-[a-z]*f[a-z]*|--force)\s/.test(rmFlags[1]));
8097
+ if (rmRecursiveForce || /\b(del\s+\/[fqs]|remove-item\b.*\s-recurse\b|rmdir\s+\/s)\b/.test(value)) {
8098
+ return isWorkspaceScopedRemoval(segment, cwdScoped) ? "write" : "DELETE";
7947
8099
  }
7948
8100
  if (/\b(drop\s+(database|table|schema)|truncate\s+table|kubectl\s+delete|terraform\s+destroy|gcloud\s+(?:sql\s+instances|compute\s+instances|projects|run\s+services)\s+delete|az\s+(?:group|vm|sql\s+server)\s+delete|aws\s+(?:s3\s+rb|rds\s+delete-db-instance|ec2\s+terminate-instances)|docker\s+(?:system\s+prune|volume\s+rm|rm\s+-f)|mkfs|dd\s+if=)\b/.test(value)) {
7949
8101
  return "DELETE";
@@ -8002,12 +8154,159 @@ function storageUriIsDestination(segment, index = 0, all = []) {
8002
8154
  return false;
8003
8155
  }
8004
8156
  var SHELL_BLOCK_STRUCTURE_RE = /^(?:\s|[{}()=\-\d]|\+=|""|\$[\w:.]+|\b(?:try|catch|finally|if|then|else|elseif|elif|fi|do|done|esac|end|exit|return|break|continue)\b)*$/i;
8157
+ var shellAstProvider;
8158
+ var shellAstMode = "shadow";
8159
+ var lastShellAstOperationComparison;
8160
+ function setShellAstProvider(provider) {
8161
+ shellAstProvider = provider;
8162
+ }
8163
+ function setShellAstMode(mode) {
8164
+ shellAstMode = mode;
8165
+ }
8166
+ function getShellAstMode() {
8167
+ return shellAstMode;
8168
+ }
8169
+ function takeShellAstOperationComparison() {
8170
+ const value = lastShellAstOperationComparison;
8171
+ lastShellAstOperationComparison = void 0;
8172
+ return value;
8173
+ }
8174
+ function shellAstFactsFullyComplete(facts) {
8175
+ if (!facts || !facts.complete) return false;
8176
+ for (const command of facts.commands) if (command.wrapper?.inner && !shellAstFactsFullyComplete(command.wrapper.inner)) return false;
8177
+ return true;
8178
+ }
8179
+ var AST_TRANSPARENT_WRAPPERS = /* @__PURE__ */ new Set(["sudo", "doas", "pkexec", "run0", "gosu", "runuser", "env", "nice", "nohup", "ionice", "chrt", "stdbuf", "timeout", "command", "builtin", "time", "exec"]);
8180
+ function astRemovalOperation(command, inContainer) {
8181
+ let argv = command.argv;
8182
+ let literal2 = command.literal;
8183
+ let name = command.name;
8184
+ for (let guard = 0; guard < 4 && AST_TRANSPARENT_WRAPPERS.has(name); guard++) {
8185
+ let i2 = 1;
8186
+ while (i2 < argv.length && (argv[i2].startsWith("-") && argv[i2] !== "-" || /^[A-Za-z_][A-Za-z0-9_]*=/.test(argv[i2]))) {
8187
+ if (/^-(?:u|g|h|C|p|r|t|U|D)$/.test(argv[i2])) i2++;
8188
+ i2++;
8189
+ }
8190
+ if (i2 >= argv.length || !literal2[i2]) return void 0;
8191
+ argv = argv.slice(i2);
8192
+ literal2 = literal2.slice(i2);
8193
+ name = argv[0].replace(/^.*\//, "").replace(/\.exe$/i, "").toLowerCase();
8194
+ }
8195
+ if (name !== "rm") return void 0;
8196
+ let recursive = false;
8197
+ let force = false;
8198
+ const targets = [];
8199
+ let flagsDone = false;
8200
+ for (let i2 = 1; i2 < argv.length; i2++) {
8201
+ const word = argv[i2];
8202
+ if (!flagsDone && word === "--") {
8203
+ flagsDone = true;
8204
+ continue;
8205
+ }
8206
+ if (!flagsDone && /^--/.test(word)) {
8207
+ if (/^--recursive$/i.test(word)) recursive = true;
8208
+ if (/^--force$/i.test(word)) force = true;
8209
+ continue;
8210
+ }
8211
+ if (!flagsDone && /^-[A-Za-z]+$/.test(word)) {
8212
+ if (/[rR]/.test(word)) recursive = true;
8213
+ if (/f/.test(word)) force = true;
8214
+ continue;
8215
+ }
8216
+ targets.push(word);
8217
+ }
8218
+ if (!recursive || !force) return void 0;
8219
+ if (targets.length === 0) return "write";
8220
+ const scoped = targets.every((raw) => {
8221
+ const t = raw.trim().replace(/\\/g, "/");
8222
+ if (/^(?:\*|\.\/+\*)$/.test(t)) return inContainer || command.cwdScope === "scoped";
8223
+ if (!t || t === "." || t === "..") return false;
8224
+ if (/^[\/]/.test(t) || /^[A-Za-z]:/.test(t) || /^~/.test(t) || /^\$\{?HOME\}?\//.test(t)) return isNestedScratchOrWorkPath(t);
8225
+ if (/\$|%|`/.test(t)) return false;
8226
+ if (/(^|\/)\.\.(\/|$)/.test(t)) return false;
8227
+ return true;
8228
+ });
8229
+ return scoped ? "write" : "DELETE";
8230
+ }
8231
+ function classifyShellFromAst(facts, depth, inContainer = false) {
8232
+ const ops = [];
8233
+ let pipeline = 0;
8234
+ let pipelineText = [];
8235
+ let pipelineScoped = false;
8236
+ let pipelineFloor = "read";
8237
+ const flush = () => {
8238
+ if (pipelineText.length) ops.push(worstShellOp([classifyShellSegment(pipelineText.join(" | "), depth, pipelineScoped)], pipelineFloor));
8239
+ pipelineText = [];
8240
+ pipeline = 0;
8241
+ pipelineFloor = "read";
8242
+ };
8243
+ const floorFor = (command) => command.depth && (command.depth.loop > 0 || command.depth.function > 0) ? "SHELL" : "read";
8244
+ for (const command of facts.commands) {
8245
+ if (command.wrapper?.inner) {
8246
+ const inner = classifyShellFromAst(command.wrapper.inner, depth + 1, inContainer || command.wrapper.kind === "container");
8247
+ const rank = SHELL_OP_RANK[inner] || 0;
8248
+ ops.push(command.wrapper.kind === "container" && rank > 1 && rank < 4 ? "SHELL" : inner);
8249
+ }
8250
+ const removal = astRemovalOperation(command, inContainer);
8251
+ if (removal) {
8252
+ flush();
8253
+ ops.push(worstShellOp([removal], floorFor(command)));
8254
+ continue;
8255
+ }
8256
+ if (command.pipeline && command.pipeline === pipeline) {
8257
+ pipelineText.push(command.text);
8258
+ continue;
8259
+ }
8260
+ flush();
8261
+ if (command.pipeline) {
8262
+ pipeline = command.pipeline;
8263
+ pipelineText = [command.text];
8264
+ pipelineScoped = command.cwdScope === "scoped";
8265
+ pipelineFloor = floorFor(command);
8266
+ continue;
8267
+ }
8268
+ ops.push(worstShellOp([classifyShellSegment(command.text, depth, command.cwdScope === "scoped")], floorFor(command)));
8269
+ }
8270
+ flush();
8271
+ return worstShellOp(ops, "read");
8272
+ }
8005
8273
  function classifyShellCommandOperation(command, depth = 0) {
8006
8274
  if (provenPowerShellReadSequence(command)) return "read";
8007
8275
  if (shellUrlActionText(command) !== command) return "write";
8008
8276
  const segments = splitShellSegments(command);
8009
8277
  if (segments.length === 0) return "SHELL";
8010
- return worstShellOp(segments.map((segment) => classifyShellSegment(segment, depth)), "read");
8278
+ let cwdScoped = false;
8279
+ const ops = segments.map((segment) => {
8280
+ const op = classifyShellSegment(segment, depth, cwdScoped);
8281
+ const cd = cdScopeOfSegment(segment);
8282
+ if (cd) cwdScoped = cd === "scoped";
8283
+ return op;
8284
+ });
8285
+ return worstShellOp(ops, "read");
8286
+ }
8287
+ function reconcileShellOperation(command, regexOp) {
8288
+ if (shellAstMode === "off" || !shellAstProvider) return regexOp;
8289
+ let facts;
8290
+ try {
8291
+ facts = shellAstProvider(command);
8292
+ } catch {
8293
+ facts = void 0;
8294
+ }
8295
+ const complete = shellAstFactsFullyComplete(facts);
8296
+ let astOp;
8297
+ if (complete && facts) {
8298
+ astOp = provenPowerShellReadSequence(command) ? "read" : shellUrlActionText(command) !== command ? "write" : classifyShellFromAst(facts, 0);
8299
+ }
8300
+ lastShellAstOperationComparison = { mode: shellAstMode, complete, regex: regexOp, ast: astOp, agree: !complete || astOp === regexOp };
8301
+ return shellAstMode === "primary" && complete && astOp ? astOp : regexOp;
8302
+ }
8303
+ function classifyShellCommandForTool(command) {
8304
+ return reconcileShellOperation(command, classifyShellCommandOperation(command));
8305
+ }
8306
+ function shellCommandTextOf(toolName, args) {
8307
+ if (!CODE_EXEC_TOOL_NAME_RE.test(toolName.toLowerCase().replace(/[_\-.]/g, " "))) return void 0;
8308
+ const text = String(args.command || args.cmd || args.code || args.script || args.input || "");
8309
+ return text || void 0;
8011
8310
  }
8012
8311
  function isPowerShellReadFormatter(segment) {
8013
8312
  if (/^(?:Select-Object\s+[\w., *-]+|Format-Table(?:\s+[\w., *-]+)?|Format-List(?:\s+[\w., *-]+)?|Out-Null|ConvertFrom-Json)$/i.test(segment)) return true;
@@ -8047,10 +8346,35 @@ function isGcloudStorageCatRead(command) {
8047
8346
  }
8048
8347
  return objects > 0;
8049
8348
  }
8349
+ function provenGitCloneOperands(tokens) {
8350
+ if (tokens.shift() !== "clone") return false;
8351
+ let urls = 0;
8352
+ let directories = 0;
8353
+ for (let i2 = 0; i2 < tokens.length; i2++) {
8354
+ const token = tokens[i2];
8355
+ if (/^(?:-q|--quiet|--single-branch|--no-single-branch|--no-tags|--no-checkout|-n|--bare|--mirror)$/.test(token)) continue;
8356
+ if (/^(?:--depth|--branch|-b|--origin|-o)$/.test(token) && /^[\w.\/-]+$/.test(tokens[i2 + 1] || "")) {
8357
+ i2++;
8358
+ continue;
8359
+ }
8360
+ if (/^--(?:depth|branch|origin|filter)=[\w.:\/-]+$/.test(token)) continue;
8361
+ if (urls === 0 && /^https?:\/\/[\w.:\/-]+$/i.test(token)) {
8362
+ urls++;
8363
+ continue;
8364
+ }
8365
+ if (urls === 1 && directories === 0 && /^[\w.~\/-]+$/.test(token) && !token.startsWith("-")) {
8366
+ directories++;
8367
+ continue;
8368
+ }
8369
+ return false;
8370
+ }
8371
+ return urls === 1;
8372
+ }
8050
8373
  function provenCompoundDownload(command, detectedUrl) {
8051
8374
  if (command.includes(detectedUrl) && provenPowerShellReadSequence(command)) return true;
8052
8375
  const segments = splitShellSegments(command).filter((segment) => /\b(?:https?|s3|gs|ftp|sftp|smb):\/\//i.test(segment));
8053
8376
  if (!segments.length || !command.includes(detectedUrl)) return false;
8377
+ if (segments.some((segment) => /^git(?:\.exe)?\s+clone\b/i.test(segment)) && splitShellSegments(command).some((segment) => !segments.includes(segment) && classifyShellSegment(segment) !== "read" && !(["rm", "rmdir", "mkdir", "cd", "pushd", "popd"].includes(shellSegmentLead(segment)) && classifyShellSegment(segment) === "write"))) return false;
8054
8378
  return segments.every((segment) => {
8055
8379
  const statusRead = /^\(((?:Invoke-WebRequest|iwr)\s+[^\r\n]+)\)\.(?:StatusCode|StatusDescription|RawContentLength)$/i.exec(segment);
8056
8380
  if (statusRead) segment = statusRead[1];
@@ -8064,6 +8388,7 @@ function provenCompoundDownload(command, detectedUrl) {
8064
8388
  if (pieces.join("").replace(/\s/g, "") !== raw.replace(/\s/g, "")) return false;
8065
8389
  const tokens = pieces.map((piece) => piece.replace(/^["']|["']$/g, ""));
8066
8390
  const program = tokens.shift() || "";
8391
+ if (/^git(?:\.exe)?$/i.test(program)) return provenGitCloneOperands(tokens);
8067
8392
  if (!/^(?:curl(?:\.exe)?|wget(?:\.exe)?|Invoke-RestMethod|Invoke-WebRequest|irm|iwr)$/i.test(program)) return false;
8068
8393
  const isCurl = /^curl(?:\.exe)?$/i.test(program);
8069
8394
  let urls = 0;
@@ -8215,6 +8540,7 @@ function evaluateActionPolicies(policies, toolName, operation, context = {}, dec
8215
8540
  if (policies.length === 0) {
8216
8541
  return { allowed: true, verdict: "allow" };
8217
8542
  }
8543
+ if (!context.toolName && toolName) context = { ...context, toolName };
8218
8544
  const verdictPriority = {
8219
8545
  block: 3,
8220
8546
  require_approval: 2,
@@ -8378,6 +8704,8 @@ function inferToolContext(toolName, args) {
8378
8704
  }
8379
8705
  const updated = (0, import_detectorRuntime.updatedDetectorFacts)(toolName, detectorArgs);
8380
8706
  if (!updated) return inferBundledToolContext(toolName, args);
8707
+ const shellText = shellCommandTextOf(toolName, args);
8708
+ const operation = shellText && updated.operation !== toolName ? reconcileShellOperation(shellText, updated.operation) : updated.operation;
8381
8709
  const context = { ...updated.context };
8382
8710
  for (const key of RESERVED_INPUT_FACTS) delete context[key];
8383
8711
  const argsText = stringifyArgs(args);
@@ -8391,7 +8719,32 @@ function inferToolContext(toolName, args) {
8391
8719
  context["destination.type"] = context["request.destination.type"];
8392
8720
  context["destination.domain"] = context["request.destination.domain"];
8393
8721
  }
8394
- return { operation: updated.operation, context };
8722
+ applyWebReadFacts(toolName, args, context);
8723
+ return { operation, context };
8724
+ }
8725
+ var WEB_READ_TOOL_NAMES = /* @__PURE__ */ new Set(["webfetch", "fetchwebpage", "webextract"]);
8726
+ function applyWebReadFacts(toolName, args, context) {
8727
+ if (!WEB_READ_TOOL_NAMES.has(toolName.toLowerCase().replace(/[_\-.\s]/g, ""))) return;
8728
+ context["action.kind"] = "http";
8729
+ const url = extractUrl(args, "");
8730
+ if (!url) return;
8731
+ let parsed;
8732
+ try {
8733
+ parsed = new URL(url);
8734
+ } catch {
8735
+ context["url.risk"] = "invalid";
8736
+ return;
8737
+ }
8738
+ const hostType = isInternalHost(parsed.hostname) || isSingleLabelWebHost(parsed) ? "internal" : "external";
8739
+ context.url = url;
8740
+ context["destination.domain"] = parsed.hostname;
8741
+ context["url.risk"] = hostType;
8742
+ context["source.domain"] = parsed.hostname;
8743
+ context["source.type"] = hostType;
8744
+ if (recordHttpRequestFacts(context, args, url)) context["destination.type"] = hostType;
8745
+ else delete context["destination.type"];
8746
+ if (isLoopbackHost(parsed.hostname)) context["destination.loopback"] = "true";
8747
+ else delete context["destination.loopback"];
8395
8748
  }
8396
8749
  function inferBundledToolContext(toolName, args) {
8397
8750
  const context = {};
@@ -8432,7 +8785,7 @@ function inferBundledToolContext(toolName, args) {
8432
8785
  }
8433
8786
  }
8434
8787
  if (operation === toolName && CODE_EXEC_TOOL_NAME_RE.test(nameWords)) {
8435
- operation = classifyShellCommandOperation(commandText);
8788
+ operation = classifyShellCommandForTool(commandText);
8436
8789
  }
8437
8790
  const pathArg = args.path || args.filepath || args.file || args.filename || "";
8438
8791
  if (typeof pathArg === "string" && pathArg.length > 0) {
@@ -8502,6 +8855,7 @@ function inferBundledToolContext(toolName, args) {
8502
8855
  context["destination.type"] = isInternalHost(context["destination.domain"]) ? "internal" : "external";
8503
8856
  }
8504
8857
  }
8858
+ applyWebReadFacts(toolName, args, context);
8505
8859
  return { operation, context };
8506
8860
  }
8507
8861
  var RESERVED_INPUT_FACTS = /* @__PURE__ */ new Set([
@@ -8606,10 +8960,12 @@ function inventoryToolMatchesActionPolicy(toolName, toolActions, policies) {
8606
8960
  MACHINE_KINDS,
8607
8961
  WORKSTATION_ASK_APPROVAL,
8608
8962
  approvalExceptionError,
8963
+ canonicalAgentClient,
8609
8964
  deriveToolOperations,
8610
8965
  detectSecretKind,
8611
8966
  effectiveApprovalScope,
8612
8967
  evaluateActionPolicies,
8968
+ getShellAstMode,
8613
8969
  inferBundledToolContext,
8614
8970
  inferToolContext,
8615
8971
  inventoryToolMatchesActionPolicy,
@@ -8619,5 +8975,8 @@ function inventoryToolMatchesActionPolicy(toolName, toolActions, policies) {
8619
8975
  resolveApprovalOptions,
8620
8976
  roleExceptionError,
8621
8977
  ruleNeedsContentFact,
8978
+ setShellAstMode,
8979
+ setShellAstProvider,
8980
+ takeShellAstOperationComparison,
8622
8981
  toolCapabilities
8623
8982
  });
@@ -94,7 +94,7 @@ function nativeDialogView(reason, options = {}) {
94
94
  'Allow once lets only this action continue.',
95
95
  'Deny or close keeps it blocked.',
96
96
  options.allowAlways && options.alwaysScopeLabel
97
- ? `Always allow covers ${line(options.alwaysScopeLabel, 180)} for ${options.alwaysLimitsLabel || '30 days'} on this machine, under this rule as it is written today.`
97
+ ? `Always allow: ${line(options.alwaysScopeLabel, 180)} for ${options.alwaysLimitsLabel || '30 days'} on this machine, while the rule stays as it is written today.`
98
98
  : '',
99
99
  ].filter(Boolean);
100
100
  const footer = f.policy && !f.fallback
@@ -156,7 +156,7 @@ function nativeApprovalMessage(reason, options = {}) {
156
156
  return [
157
157
  'Allow this action?',
158
158
  '', ...details, '',
159
- ...(options.alwaysScopeLabel ? [`Always allow covers: ${line(options.alwaysScopeLabel, 200)} — for ${options.alwaysLimitsLabel || '30 days'} on this machine, under this rule as it is written today.`, ''] : []),
159
+ ...(options.alwaysScopeLabel ? [`Always allow: ${line(options.alwaysScopeLabel, 200)} for ${options.alwaysLimitsLabel || '30 days'} on this machine, while the rule stays as it is written today.`, ''] : []),
160
160
  'Allow once permits only this action. Deny or closing keeps it stopped.',
161
161
  where,
162
162
  ].join('\n');