fullcourtdefense-cli 1.34.21 → 1.34.25

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 (91) hide show
  1. package/README.md +1 -0
  2. package/dist/actionIdentity.d.ts +1 -0
  3. package/dist/actionIdentity.js +2 -0
  4. package/dist/actionPolicyEngine.d.ts +5 -0
  5. package/dist/actionPolicyEngine.js +273 -30
  6. package/dist/agentTerminal.d.ts +1 -3
  7. package/dist/agentTerminal.js +14 -29
  8. package/dist/approvalPrompt.d.ts +7 -0
  9. package/dist/approvalPrompt.js +46 -0
  10. package/dist/askDialog.d.ts +87 -0
  11. package/dist/askDialog.js +209 -0
  12. package/dist/blockExplanation.js +18 -6
  13. package/dist/boundaryVerification.d.ts +20 -0
  14. package/dist/boundaryVerification.js +147 -0
  15. package/dist/commands/daemon.js +198 -70
  16. package/dist/commands/deterministicGuard.d.ts +2 -0
  17. package/dist/commands/deterministicGuard.js +161 -9
  18. package/dist/commands/hook.js +81 -29
  19. package/dist/commands/installClaudeHook.js +11 -5
  20. package/dist/commands/installCursorHook.js +5 -4
  21. package/dist/commands/mcpGateway.d.ts +2 -0
  22. package/dist/commands/mcpGateway.js +163 -56
  23. package/dist/commands/onboard.js +6 -5
  24. package/dist/commands/onboardingSummary.d.ts +5 -0
  25. package/dist/commands/onboardingSummary.js +11 -0
  26. package/dist/commands/protectedRun.d.ts +14 -0
  27. package/dist/commands/protectedRun.js +207 -0
  28. package/dist/containment-runtime/index.mjs +30118 -0
  29. package/dist/containment-runtime/licenses/_anthropic-ai_sandbox-runtime.txt +201 -0
  30. package/dist/containment-runtime/licenses/_pondwader_socks5-server.txt +20 -0
  31. package/dist/containment-runtime/licenses/commander.txt +22 -0
  32. package/dist/containment-runtime/licenses/node-forge.txt +331 -0
  33. package/dist/containment-runtime/licenses/zod.txt +21 -0
  34. package/dist/containment-runtime/runtime-version.json +1 -0
  35. package/dist/containment-runtime/vendor/java-proxy-agent/build.ts +95 -0
  36. package/dist/containment-runtime/vendor/java-proxy-agent/srt-proxy-agent.jar +0 -0
  37. package/dist/containment-runtime/vendor/seccomp/arm64/apply-seccomp +0 -0
  38. package/dist/containment-runtime/vendor/seccomp/build.ts +69 -0
  39. package/dist/containment-runtime/vendor/seccomp/x64/apply-seccomp +0 -0
  40. package/dist/containment-runtime/vendor/srt-win/arm64/srt-win.exe +0 -0
  41. package/dist/containment-runtime/vendor/srt-win/build.ts +21 -0
  42. package/dist/containment-runtime/vendor/srt-win/x64/srt-win.exe +0 -0
  43. package/dist/containmentLease.d.ts +5 -0
  44. package/dist/containmentLease.js +72 -0
  45. package/dist/detectionUpdates.d.ts +23 -3
  46. package/dist/detectionUpdates.js +45 -14
  47. package/dist/detectorArtifact.d.ts +17 -0
  48. package/dist/detectorArtifact.js +35 -0
  49. package/dist/detectorBaseline.d.ts +2 -0
  50. package/dist/detectorBaseline.js +5 -0
  51. package/dist/detectorModule.d.ts +9 -0
  52. package/dist/detectorModule.js +84 -0
  53. package/dist/detectorRuntime.d.ts +21 -0
  54. package/dist/detectorRuntime.js +49 -0
  55. package/dist/detectorUpdates.d.ts +21 -0
  56. package/dist/detectorUpdates.js +86 -0
  57. package/dist/devConfirm.d.ts +97 -15
  58. package/dist/devConfirm.js +447 -117
  59. package/dist/distress.d.ts +2 -0
  60. package/dist/distress.js +2 -0
  61. package/dist/frictionRollout.js +103 -0
  62. package/dist/gatewayRuntime.d.ts +19 -0
  63. package/dist/gatewayRuntime.js +93 -0
  64. package/dist/hookSlim.js +17 -18
  65. package/dist/index.js +35 -0
  66. package/dist/localDetectionUpdates.d.ts +3 -1
  67. package/dist/localDetectionUpdates.js +8 -1
  68. package/dist/localSafetySnapshot.d.ts +9 -5
  69. package/dist/localSafetySnapshot.js +42 -15
  70. package/dist/mcpToolReview.d.ts +17 -0
  71. package/dist/mcpToolReview.js +127 -0
  72. package/dist/mcpToolTrust.d.ts +22 -0
  73. package/dist/mcpToolTrust.js +66 -0
  74. package/dist/nativeHook.d.ts +4 -0
  75. package/dist/nativeHook.js +31 -17
  76. package/dist/policyRefresh.d.ts +3 -0
  77. package/dist/policyRefresh.js +27 -0
  78. package/dist/protectionMaintenance.d.ts +10 -0
  79. package/dist/protectionMaintenance.js +64 -0
  80. package/dist/protectionProfile.d.ts +26 -0
  81. package/dist/protectionProfile.js +63 -0
  82. package/dist/runtimeConfig.d.ts +2 -0
  83. package/dist/runtimeConfig.js +65 -26
  84. package/dist/selfTest.d.ts +2 -0
  85. package/dist/selfTest.js +9 -1
  86. package/dist/sessionLimits.d.ts +2 -0
  87. package/dist/sessionLimits.js +24 -0
  88. package/dist/telemetry.d.ts +5 -0
  89. package/dist/telemetry.js +8 -0
  90. package/dist/version.json +1 -1
  91. package/package.json +10 -6
package/README.md CHANGED
@@ -73,6 +73,7 @@ fullcourtdefense init
73
73
  - `fullcourtdefense help` — shows the full onboarding flow and command reference.
74
74
  - `fullcourtdefense onboard --token <token>` — resumable machine transaction: preflight + login + protection + optional discovery + verification. `--json true` prints final machine-readable status; exits non-zero only when a critical step fails.
75
75
  - `fullcourtdefense doctor` — confirms outbound HTTPS to FullCourtDefense is open before scanning.
76
+ - `fullcourtdefense hotfix-status` — shows the loaded detector revision and whether fallback is active. Available from 1.34.22. Updates arrive through the background daemon; this command reads local status and does not force a download. Acceptance alone does not prove that a reported false alarm was corrected.
76
77
  - `fullcourtdefense login --token <token>` — enrolls this machine with a fleet token and saves per-machine Shield credentials (no copy/paste).
77
78
  - `fullcourtdefense install-all` — wraps every configured MCP server, installs IDE hooks and terminal guards, uploads discovery, schedules daily rescans.
78
79
  - `fullcourtdefense configure` — legacy/manual setup: saves org API key, Shield ID, Shield key, and API URL to `.fullcourtdefense.yml` (use `login` instead when you have a fleet token).
@@ -3,6 +3,7 @@ export interface ActionIdentity {
3
3
  sessionId?: string;
4
4
  runId?: string;
5
5
  instanceId?: string;
6
+ attemptId?: string;
6
7
  }
7
8
  export declare function identityPart(value: unknown): string | undefined;
8
9
  export declare function captureActionIdentity(payload?: Record<string, unknown>, env?: NodeJS.ProcessEnv): ActionIdentity;
@@ -11,5 +11,7 @@ function captureActionIdentity(payload = {}, env = process.env) {
11
11
  || env.FCD_SESSION_ID || env.CLAUDE_CODE_SESSION_ID || env.CODEX_THREAD_ID),
12
12
  runId: identityPart(env.FCD_RUN_ID || env.GITHUB_RUN_ID || env.CI_PIPELINE_ID),
13
13
  instanceId: identityPart(env.FCD_WORKLOAD_INSTANCE_ID),
14
+ // Never use a generic request id: some clients reuse it for unrelated calls.
15
+ attemptId: identityPart(payload.tool_use_id || payload.tool_call_id),
14
16
  };
15
17
  }
@@ -234,5 +234,10 @@ export declare function inferToolContext(toolName: string, args: Record<string,
234
234
  operation: string;
235
235
  context: Record<string, string>;
236
236
  };
237
+ /** Exported for reproducible detector artifacts; policy decisions are not packaged. */
238
+ export declare function inferBundledToolContext(toolName: string, args: Record<string, any>): {
239
+ operation: string;
240
+ context: Record<string, string>;
241
+ };
237
242
  /** Static inventory match — same operation rules as runtime, without constraint context. */
238
243
  export declare function inventoryToolMatchesActionPolicy(toolName: string, toolActions: string[] | undefined, policies: EngineActionPolicy[]): boolean;
@@ -29,6 +29,7 @@ __export(actionPolicyEngine_exports, {
29
29
  detectSecretKind: () => detectSecretKind,
30
30
  effectiveApprovalScope: () => effectiveApprovalScope,
31
31
  evaluateActionPolicies: () => evaluateActionPolicies,
32
+ inferBundledToolContext: () => inferBundledToolContext,
32
33
  inferToolContext: () => inferToolContext,
33
34
  inventoryToolMatchesActionPolicy: () => inventoryToolMatchesActionPolicy,
34
35
  isLocalFileContentAction: () => isLocalFileContentAction,
@@ -5740,6 +5741,7 @@ function parse3(input, options) {
5740
5741
 
5741
5742
  // src/actionPolicyEngine.ts
5742
5743
  var import_detectionData = require("./detectionData");
5744
+ var import_detectorRuntime = require("./detectorRuntime");
5743
5745
  function isFirestoreQueryUrl(target) {
5744
5746
  try {
5745
5747
  const url = new URL(target);
@@ -5790,6 +5792,27 @@ function provenJavaScriptReadUrls(code) {
5790
5792
  }
5791
5793
  case "EmptyStatement":
5792
5794
  return literal2(void 0);
5795
+ case "IfStatement": {
5796
+ ev(node.test);
5797
+ evaluate(node.consequent, new Map(env), depth + 1);
5798
+ if (node.alternate) evaluate(node.alternate, new Map(env), depth + 1);
5799
+ return literal2(void 0);
5800
+ }
5801
+ case "UnaryExpression": {
5802
+ if (node.operator !== "!") return fail();
5803
+ ev(node.argument);
5804
+ return { kind: "data" };
5805
+ }
5806
+ case "ThrowStatement":
5807
+ return ev(node.argument);
5808
+ case "TemplateLiteral": {
5809
+ for (const expression of node.expressions) {
5810
+ const value = ev(expression);
5811
+ if (!["literal", "data", "string"].includes(value.kind)) return fail();
5812
+ if (value.kind === "literal" && value.value !== null && typeof value.value === "object") return fail();
5813
+ }
5814
+ return { kind: "string" };
5815
+ }
5793
5816
  case "ExpressionStatement":
5794
5817
  case "AwaitExpression":
5795
5818
  case "ChainExpression":
@@ -5833,6 +5856,7 @@ function provenJavaScriptReadUrls(code) {
5833
5856
  case "BinaryExpression": {
5834
5857
  const left = ev(node.left);
5835
5858
  const right = ev(node.right);
5859
+ if (node.operator === "===" || node.operator === "!==") return { kind: "data" };
5836
5860
  if (node.operator !== "+") return fail();
5837
5861
  if (left.kind === "literal" && right.kind === "literal" && typeof left.value === "string" && typeof right.value === "string") {
5838
5862
  if (left.value.length + right.value.length > 32768) return fail();
@@ -5868,6 +5892,12 @@ function provenJavaScriptReadUrls(code) {
5868
5892
  }
5869
5893
  case "NewExpression": {
5870
5894
  const constructor = ev(node.callee);
5895
+ if (constructor.kind === "call" && constructor.name === "Error") {
5896
+ if (node.arguments.length !== 1) return fail();
5897
+ const message = ev(node.arguments[0]);
5898
+ if (message.kind !== "string" && !(message.kind === "literal" && typeof message.value === "string")) return fail();
5899
+ return { kind: "data" };
5900
+ }
5871
5901
  if (constructor.kind !== "call" || constructor.name !== "GoogleAuth" || node.arguments.length !== 1) return fail();
5872
5902
  const options = ev(node.arguments[0]);
5873
5903
  if (options.kind !== "object" || options.fields.size !== 1) return fail();
@@ -5998,6 +6028,7 @@ function provenJavaScriptReadUrls(code) {
5998
6028
  evaluate(tree, /* @__PURE__ */ new Map([
5999
6029
  ["fetch", callable("fetch")],
6000
6030
  ["require", callable("require")],
6031
+ ["Error", callable("Error")],
6001
6032
  ["JSON", object([["stringify", callable("JSON.stringify")]])],
6002
6033
  ["console", object([["log", callable("console.log")], ["error", callable("console.error")]])]
6003
6034
  ]));
@@ -6738,14 +6769,17 @@ var OPERATION_MATCH_SKIP_FIELDS = /* @__PURE__ */ new Set([
6738
6769
  "recipients",
6739
6770
  "from",
6740
6771
  "cc",
6741
- "bcc"
6772
+ "bcc",
6773
+ "domain",
6774
+ "destinationType",
6775
+ "toolStatus"
6742
6776
  ]);
6743
6777
  function buildOperationMatchText(_toolName, context) {
6744
6778
  const parts = [];
6745
6779
  for (const [key, value] of Object.entries(context)) {
6746
6780
  const fieldWords = key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().split(/[_.-]/);
6747
6781
  const identifierField = /^(?:id|ids|uuid|uuids|guid|guids)$/.test(fieldWords[fieldWords.length - 1]);
6748
- if (OPERATION_MATCH_SKIP_FIELDS.has(key) || identifierField) continue;
6782
+ if (OPERATION_MATCH_SKIP_FIELDS.has(key) || identifierField || key.startsWith("args.") || key.startsWith("request.")) continue;
6749
6783
  if (typeof value !== "string" || value.length === 0 || value.length > 2e3) continue;
6750
6784
  const text = COMMAND_LINE_FIELDS.has(key) ? shellVerbWords(value) : value;
6751
6785
  parts.push(`${key} ${text}`);
@@ -6818,6 +6852,7 @@ function isHighEntropySecretToken(token, allowWordSlug = false) {
6818
6852
  if (/^sha(?:1|256|384|512)-/i.test(token)) return false;
6819
6853
  if (/^data:/i.test(token) || /^[A-Za-z0-9+/]{200,}={0,2}$/.test(token)) return false;
6820
6854
  if (/^(?:https?|s3|gs|file):\/\//i.test(token) || /[\\/]{1}[\w.-]+[\\/]/.test(token)) return false;
6855
+ if (allowWordSlug && /^[A-Za-z_][\w.-]*\/[A-Za-z_][\w.-]*\.[A-Za-z][A-Za-z0-9]{0,9}$/.test(token)) return false;
6821
6856
  if (/^[a-z]+(?:[A-Z][a-z0-9]+)+$/.test(token) || /^[a-z]+(?:_[a-z0-9]+)+$/.test(token)) return false;
6822
6857
  if (allowWordSlug && /^[A-Z][a-z]+-(?:[A-Z][a-z]+){2,}$/.test(token)) return false;
6823
6858
  if (allowWordSlug && /^[a-z]+(?:-[a-z]+)+(?:-\d+)?$/.test(token)) return false;
@@ -6988,8 +7023,15 @@ function explicitGitHubWriteRepository(command) {
6988
7023
  function withoutUnattributedJavaScriptUrls(segment) {
6989
7024
  if (segment.length > 32768) return segment;
6990
7025
  const inline = /^\s*node(?:\.exe)?\s+(?:-e|--eval)\s+(?:"([^"$`\\]*)"|'([^']*)')\s*$/i.exec(segment);
6991
- if (!inline) return segment;
6992
- const code = inline[1] ?? inline[2];
7026
+ let code;
7027
+ if (inline) code = inline[1] ?? inline[2];
7028
+ else {
7029
+ const raw = segment.trim();
7030
+ const opening = /^@'\r?\n/.exec(raw);
7031
+ const end = opening ? hereStringEnd(raw, 0) : -1;
7032
+ if (!opening || end < 0 || !/^\s*\|\s*node(?:\.exe)?(?:\s+-)?\s*$/i.test(raw.slice(end))) return segment;
7033
+ code = raw.slice(opening[0].length, end - 3);
7034
+ }
6993
7035
  let budget = 4e3;
6994
7036
  try {
6995
7037
  const tree = parse3(code, { ecmaVersion: "latest", sourceType: "script", allowAwaitOutsideFunction: true });
@@ -7051,8 +7093,12 @@ function extractUrl(args, _argsText) {
7051
7093
  }
7052
7094
  function shellUrlActionText(command) {
7053
7095
  if (command.length > 65536) return command;
7096
+ if (/\$env:(?:TEMP|TMP)\s*=/i.test(maskQuotedSpans(command))) return command;
7097
+ const fileSyntax = (text) => text.replace(/"\$env:(?:TEMP|TMP)([\\/][A-Za-z0-9_ ./\\-]+)"/gi, '"fcd-temporary$1"').replace(/\(Join-Path\s+\$env:(?:TEMP|TMP)\s+'([A-Za-z0-9_ ./\\-]+)'\)/gi, '"fcd-temporary/$1"');
7054
7098
  const originalSegments = splitShellSegments(command);
7055
7099
  const localPaths = /* @__PURE__ */ new Map();
7100
+ const literalValues = /* @__PURE__ */ new Map();
7101
+ const dataAssignments = /* @__PURE__ */ new Set();
7056
7102
  const pathAssignments = /* @__PURE__ */ new Set();
7057
7103
  let expandedSize = command.length;
7058
7104
  let expansionOverflow = false;
@@ -7065,6 +7111,11 @@ function shellUrlActionText(command) {
7065
7111
  return value;
7066
7112
  };
7067
7113
  const segments = originalSegments.map((segment, index) => {
7114
+ const dataAssignment = /^\$([A-Za-z_][\w]*)\s*=\s*(@'\r?\n[\s\S]*?\r?\n'@|'(?:[^']|'')*')$/.exec(segment);
7115
+ if (dataAssignment && !literalValues.has(dataAssignment[1].toLowerCase()) && literalValues.size < 32) {
7116
+ literalValues.set(dataAssignment[1].toLowerCase(), dataAssignment[2]);
7117
+ dataAssignments.add(index);
7118
+ }
7068
7119
  const assignment = /^\$([A-Za-z_][\w]*)\s*=\s*(?:'([^']*)'|"([^"$`]*)")$/.exec(segment);
7069
7120
  if (assignment) {
7070
7121
  const name = assignment[1].toLowerCase();
@@ -7086,7 +7137,14 @@ function shellUrlActionText(command) {
7086
7137
  const value = localPaths.get(name.toLowerCase());
7087
7138
  return value ? expand(match, `"${value}"`) : match;
7088
7139
  });
7089
- return prefix + tail;
7140
+ const expanded = prefix + fileSyntax(tail);
7141
+ return expanded.replace(
7142
+ /^(Set-Content|Add-Content|Out-File)(\s+[\s\S]*?\s+-(?:Value|InputObject)\s+)\$([A-Za-z_][\w]*)(\s+-[\s\S]*|\s*)$/i,
7143
+ (match, writer, options, name, suffix) => {
7144
+ const value = literalValues.get(name.toLowerCase());
7145
+ return value ? expand(match, writer + options + value + suffix) : match;
7146
+ }
7147
+ );
7090
7148
  });
7091
7149
  if (expansionOverflow) return command;
7092
7150
  const filtered = segments.map((segment) => {
@@ -7121,15 +7179,26 @@ function shellUrlActionText(command) {
7121
7179
  return tail;
7122
7180
  });
7123
7181
  const onlyStoredData = segments.every((segment, index) => {
7182
+ if (dataAssignments.has(index)) return true;
7124
7183
  if (pathAssignments.has(index)) return true;
7125
7184
  if (filtered[index] !== segment) return true;
7185
+ if (/^\$[A-Za-z_][\w]*\s*=\s*\$null$/i.test(segment)) return true;
7186
+ if (/^\[System\.Management\.Automation\.Language\.Parser\]::ParseFile\("[^"$`\r\n|;&<>]*",\s*\[ref\]\$[A-Za-z_][\w]*,\s*\[ref\]\$[A-Za-z_][\w]*\)\s*\|\s*Out-Null$/i.test(segment)) return true;
7187
+ if (/^if\s*\(\$([A-Za-z_][\w]*)\.Count\)\s*\{\s*throw\s+\$\1\[0\]\s*\}$/i.test(segment)) return true;
7188
+ if (/^Write-Output\s+'(?:[^']|'')*'$/i.test(segment)) return true;
7189
+ if (/^gh\s+pr\s+checks\s+\d+\s+--json\s+[a-z,]+\s+--jq\s+'(?:[^']|'')*'$/i.test(segment)) return true;
7190
+ const read = /^Get-Content\s+(?:(?:-LiteralPath|-Path)\s+)?(?:'([^'$`\r\n]*)'|"([^"$`\r\n]*)"|([^\s'"$`|;&(){}<>]+))(?:\s+-(?:Tail|TotalCount)\s+\d{1,6})?(?:\s+-Raw)?\s*$/i.exec(segment);
7191
+ if (read) {
7192
+ const target = read[1] ?? read[2] ?? read[3];
7193
+ if (target && !target.startsWith("-") && !/^[\\/]{2}|[\r\n*?\[\]<>]/.test(target) && !/:/.test(target.replace(/^[A-Za-z]:[\\/]/, ""))) return true;
7194
+ }
7126
7195
  const directory = /^New-Item\s+-ItemType\s+Directory\s+(?:-Force\s+)?-(?:LiteralPath|Path)\s+(?:'([^']*)'|"([^"$`]*)")\s*(?:\|\s*Out-Null)?$/i.exec(segment);
7127
7196
  if (directory && /^(?:[A-Za-z]:[\\/]|\/(?!\/))[\w ./\\-]+$/.test(directory[1] ?? directory[2])) return true;
7128
7197
  const words = maskQuotedSpans(segment);
7129
7198
  if (!/[$`|;&(){}<>]/.test(segment) && /^(?:(?:cd|pushd|set-location)\s+[^\r\n]+|(?:pwd|popd|get-location)|git\s+status(?:\s+[^\r\n]+)?)$/i.test(segment)) return true;
7130
7199
  return !/[$`]/.test(segment) && /^\(Get-Content\s+(?:[A-Za-z]:[\\/])?[\w./\\-]+\s+-Raw\)\.Replace\("",\s*""\)\s*\|\s*Set-Content\s+(?:[A-Za-z]:[\\/])?[\w./\\-]+\s+-Encoding\s+utf8\s*$/i.test(words);
7131
7200
  });
7132
- return onlyStoredData && filtered.some((value, index) => value !== segments[index]) ? filtered.join("\n") : command;
7201
+ return onlyStoredData && filtered.some((value, index) => value !== segments[index]) ? filtered.filter((_, index) => !dataAssignments.has(index)).join("\n") : command;
7133
7202
  }
7134
7203
  function splitShellSegments(command, alsoOnPipe = false) {
7135
7204
  const out = [];
@@ -7367,14 +7436,22 @@ function shellSegmentLead(segment) {
7367
7436
  const tokens = segment.trim().split(/\s+/).filter(Boolean);
7368
7437
  let i2 = 0;
7369
7438
  while (i2 < tokens.length && (/^[A-Za-z_][\w]*=/.test(tokens[i2]) || /^(?:sudo|time|nohup|command|builtin|exec|nice|env)$/i.test(tokens[i2]))) i2++;
7370
- const lead = (tokens[i2] || "").toLowerCase().replace(/^["']|["']$/g, "");
7439
+ if (i2 < tokens.length && /^\$[\w:]+$/.test(tokens[i2]) && tokens[i2 + 1] === "=") i2 += 2;
7440
+ else if (i2 < tokens.length && /^\$[\w:]+=$/.test(tokens[i2])) i2 += 1;
7441
+ else if (i2 < tokens.length && /^\$[\w:]+=./.test(tokens[i2])) tokens[i2] = tokens[i2].replace(/^\$[\w:]+=/, "");
7442
+ while (i2 < tokens.length && /^(?:\$|@)?\(+$/.test(tokens[i2])) i2++;
7443
+ const lead = (tokens[i2] || "").toLowerCase().replace(/^(?:\$|@)?\(+/, "").replace(/^["']|["']$/g, "");
7371
7444
  return lead.replace(/^.*[\\/]/, "").replace(/\.(exe|cmd|bat|ps1)$/i, "");
7372
7445
  }
7446
+ var PS_SESSION_VARIABLE_ASSIGN_RE = /^\s*\$(?:psdefaultparametervalues|psmodulepath|profile|executioncontext|env:(?:path|pathext|psmodulepath))\s*(?:\[[^\]]*\]\s*)?(?:\+?=)/i;
7447
+ var PS_VALUE_EXECUTOR_RE = /\b(?:invoke-expression|iex|invoke-command|icm|start-process|saps|invoke-item|ii)\b|&\s*[$(]|\.\s*invoke\s*\(/i;
7373
7448
  var worstShellOp = (ops, floor = "read") => ops.reduce((w, op) => (SHELL_OP_RANK[op] || 0) > (SHELL_OP_RANK[w] || 0) ? op : w, floor);
7374
7449
  function classifyShellSegment(segment, depth = 0) {
7375
7450
  const raw = segment.trim();
7376
7451
  if (!raw) return "read";
7452
+ if (PS_SESSION_VARIABLE_ASSIGN_RE.test(raw)) return "SHELL";
7377
7453
  const lead = shellSegmentLead(segment);
7454
+ if ((/^\$env:[\w]+$/.test(lead) || /^\$\w+$/.test(lead)) && PS_VALUE_EXECUTOR_RE.test(maskQuotedSpans(raw))) return "SHELL";
7378
7455
  if (SHELL_NEUTRAL_BUILTINS.has(lead) || /^\$env:[\w]+$/.test(lead) || /^\$\w+$/.test(lead)) return "read";
7379
7456
  const rawLower = raw.toLowerCase();
7380
7457
  const sqlClient = SHELL_SQL_CLIENTS.has(lead) || /\|\s*(?:sudo\s+)?(?:psql|mysql|mariadb|sqlcmd|sqlite3|mongosh?|clickhouse-client|bq|cockroach|usql)\b/.test(rawLower);
@@ -7452,8 +7529,10 @@ function classifyShellWords(masked, raw, lead) {
7452
7529
  if (/\b(git\s+(?:add|commit|push|merge|rebase|cherry-pick|stash|checkout|switch|restore|tag\s+\S)|npm\s+(?:install|ci|i|uninstall|update|publish)|pnpm\s+(?:install|add|i)|yarn(?:\s+add|\s+install)?|pip3?\s+install|poetry\s+(?:install|add)|cargo\s+(?:build|install)|go\s+(?:build|install|get|mod)|gcloud\s+run\s+deploy|firebase\s+deploy|terraform\s+apply|kubectl\s+apply|docker\s+(?:push|build|compose\s+up)|helm\s+(?:install|upgrade))\b/.test(value)) {
7453
7530
  return "write";
7454
7531
  }
7532
+ if (SHELL_BLOCK_STRUCTURE_RE.test(value)) return "read";
7455
7533
  return "SHELL";
7456
7534
  }
7535
+ 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;
7457
7536
  function classifyShellCommandOperation(command, depth = 0) {
7458
7537
  if (provenPowerShellReadSequence(command)) return "read";
7459
7538
  if (shellUrlActionText(command) !== command) return "write";
@@ -7461,6 +7540,44 @@ function classifyShellCommandOperation(command, depth = 0) {
7461
7540
  if (segments.length === 0) return "SHELL";
7462
7541
  return worstShellOp(segments.map((segment) => classifyShellSegment(segment, depth)), "read");
7463
7542
  }
7543
+ function isPowerShellReadFormatter(segment) {
7544
+ if (/^(?:Select-Object\s+[\w., *-]+|Format-Table(?:\s+[\w., *-]+)?|Format-List(?:\s+[\w., *-]+)?|Out-Null|ConvertFrom-Json)$/i.test(segment)) return true;
7545
+ const tokens = segment.trim().split(/\s+/);
7546
+ if (tokens.shift()?.toLowerCase() !== "convertto-json") return false;
7547
+ const seen = /* @__PURE__ */ new Set();
7548
+ for (let i2 = 0; i2 < tokens.length; i2++) {
7549
+ const flag = tokens[i2].toLowerCase();
7550
+ if (seen.has(flag)) return false;
7551
+ seen.add(flag);
7552
+ if (flag === "-compress") continue;
7553
+ if (flag === "-depth" && /^(?:\d{1,2}|100)$/.test(tokens[++i2] || "")) continue;
7554
+ return false;
7555
+ }
7556
+ return true;
7557
+ }
7558
+ function isGcloudStorageCatRead(command) {
7559
+ if (command.length > 8192 || /[$`{}()<>;&|]/.test(command)) return false;
7560
+ const pieces = command.match(/"[^"\r\n]*"|'[^'\r\n]*'|[^\s"']+/g) || [];
7561
+ if (pieces.join("").replace(/\s/g, "") !== command.replace(/\s/g, "")) return false;
7562
+ const tokens = pieces.map((piece) => piece.replace(/^["']|["']$/g, ""));
7563
+ if (!/^gcloud(?:\.cmd|\.exe)?$/i.test(tokens.shift() || "") || tokens.shift() !== "storage" || tokens.shift() !== "cat" || tokens.length > 32) return false;
7564
+ let objects = 0;
7565
+ for (let i2 = 0; i2 < tokens.length; i2++) {
7566
+ const token = tokens[i2];
7567
+ if (/^gs:\/\/[a-z0-9][a-z0-9._-]*\/[A-Za-z0-9._~/*?-]+$/.test(token)) {
7568
+ objects++;
7569
+ continue;
7570
+ }
7571
+ if (token === "--display-url" || token === "-d") continue;
7572
+ if ((token === "--range" || token === "-r") && /^(?:\d+-\d*|-\d+)$/.test(tokens[i2 + 1] || "")) {
7573
+ i2++;
7574
+ continue;
7575
+ }
7576
+ if (/^--range=(?:\d+-\d*|-\d+)$/.test(token)) continue;
7577
+ return false;
7578
+ }
7579
+ return objects > 0;
7580
+ }
7464
7581
  function provenCompoundDownload(command, detectedUrl) {
7465
7582
  if (command.includes(detectedUrl) && provenPowerShellReadSequence(command)) return true;
7466
7583
  const segments = splitShellSegments(command).filter((segment) => /\b(?:https?|s3|gs|ftp|sftp|smb):\/\//i.test(segment));
@@ -7470,8 +7587,9 @@ function provenCompoundDownload(command, detectedUrl) {
7470
7587
  if (statusRead) segment = statusRead[1];
7471
7588
  if (/[$`{}()<>;&]/.test(segment)) return false;
7472
7589
  const pipeline = splitShellSegments(segment, true);
7473
- if (pipeline.slice(1).some((part) => !/^(?:Select-Object|Format-Table|Format-List)\s+[\w., *-]+$|^Out-Null$/i.test(part))) return false;
7590
+ if (pipeline.slice(1).some((part) => !isPowerShellReadFormatter(part))) return false;
7474
7591
  const raw = pipeline[0];
7592
+ if (isGcloudStorageCatRead(raw)) return true;
7475
7593
  const pieces = raw.match(/"[^"\r\n]*"|'[^'\r\n]*'|[^\s"']+/g) || [];
7476
7594
  if (pieces.join("").replace(/\s/g, "") !== raw.replace(/\s/g, "")) return false;
7477
7595
  const tokens = pieces.map((piece) => piece.replace(/^["']|["']$/g, ""));
@@ -7515,13 +7633,41 @@ function provenCompoundDownload(command, detectedUrl) {
7515
7633
  return urls === 1;
7516
7634
  });
7517
7635
  }
7636
+ function isPowerShellClockDisplay(value) {
7637
+ return /^\(\s*Get-Date\s*\)\s*\.\s*(?:ToUniversalTime\s*\(\s*\)\s*\.\s*)?ToString\s*\(\s*(?:'[^'\r\n]{0,80}'|"[^"$`\r\n]{0,80}")\s*\)$/i.test(value);
7638
+ }
7639
+ function isPowerShellResponseProjection(segment, responses) {
7640
+ const object = /^\[pscustomobject\]\s*@\{([^{}]+)\}\s*(?:\|\s*(.+))?$/i.exec(segment);
7641
+ if (!object) return false;
7642
+ if (object[2] && object[2].split("|").some((part) => !isPowerShellReadFormatter(part.trim()))) return false;
7643
+ const fields = object[1].trim().replace(/;\s*$/, "").split(";");
7644
+ if (fields.length > 32) return false;
7645
+ const labels = /* @__PURE__ */ new Set();
7646
+ return fields.every((field) => {
7647
+ const entry = /^\s*([A-Za-z_][\w]*)\s*=\s*(.+?)\s*$/s.exec(field);
7648
+ if (!entry) return false;
7649
+ const label = entry[1].toLowerCase();
7650
+ if (labels.has(label)) return false;
7651
+ labels.add(label);
7652
+ if (isPowerShellClockDisplay(entry[2])) return true;
7653
+ const member = /^(\[int\]\s*)?\$([A-Za-z_][\w]*)\.(StatusCode|StatusDescription|RawContentLength|Content)$/i.exec(entry[2]);
7654
+ return Boolean(member && responses.has(member[2].toLowerCase()) && (!member[1] || /^(?:StatusCode|RawContentLength)$/i.test(member[3])));
7655
+ });
7656
+ }
7518
7657
  function provenPowerShellReadSequence(command) {
7519
- if (command.length > 65536 || !/(?:^|[;&\n])\s*\$[A-Za-z_][\w]*\s*=\s*(?:Invoke-RestMethod|Invoke-WebRequest|irm|iwr)\s/i.test(command)) return false;
7658
+ if (command.length > 65536 || !/(?:^|[;&\n])\s*(?:\$[A-Za-z_][\w]*\s*=\s*(?:Invoke-RestMethod|Invoke-WebRequest|irm|iwr)\s|(?:\$[A-Za-z_][\w]*\s*=\s*)?gcloud(?:\.cmd|\.exe)?\s+storage\s+cat\s)/i.test(command)) return false;
7520
7659
  const segments = splitShellSegments(command);
7521
- if (segments.length > 32 || /[`{}]/.test(command)) return false;
7660
+ if (segments.length > 32 || /`/.test(command)) return false;
7522
7661
  const responses = /* @__PURE__ */ new Set();
7523
7662
  let requests = 0;
7524
- for (let segment of segments) {
7663
+ for (let index = 0; index < segments.length; index++) {
7664
+ let segment = segments[index];
7665
+ if (/^\[pscustomobject\]\s*@\{/i.test(segment)) {
7666
+ while (!segment.includes("}") && index + 1 < segments.length) segment += ";" + segments[++index];
7667
+ if (!isPowerShellResponseProjection(segment, responses)) return false;
7668
+ continue;
7669
+ }
7670
+ if (/[{}]/.test(maskQuotedSpans(segment))) return false;
7525
7671
  let binding;
7526
7672
  const assignment = /^\$([A-Za-z_][\w]*)\s*=\s*(.+)$/s.exec(segment);
7527
7673
  if (assignment) {
@@ -7533,15 +7679,18 @@ function provenPowerShellReadSequence(command) {
7533
7679
  if (property) segment = property[1];
7534
7680
  const pipeline = splitShellSegments(segment, true);
7535
7681
  const lead = pipeline.shift() || "";
7536
- if (pipeline.some((part) => !/^(?:ConvertTo-Json(?:\s+-Compress)?(?:\s+-Depth\s+\d{1,2})?|ConvertFrom-Json|Select-Object\s+[\w., -]+|Format-List|Format-Table|Out-Null)$/i.test(part))) return false;
7537
- if (/^(?:Invoke-RestMethod|Invoke-WebRequest|irm|iwr)\s/i.test(lead)) {
7682
+ if (pipeline.some((part) => !isPowerShellReadFormatter(part))) return false;
7683
+ if (isGcloudStorageCatRead(lead)) {
7684
+ requests++;
7685
+ } else if (/^(?:Invoke-RestMethod|Invoke-WebRequest|irm|iwr)\s/i.test(lead)) {
7538
7686
  const url = lead.match(/https?:\/\/[^\s"'<>]+/i)?.[0];
7539
7687
  if (!url || !provenCompoundDownload(lead, url)) return false;
7540
7688
  requests++;
7541
- } else if (/^\$[A-Za-z_][\w]*$/.test(lead)) {
7542
- if (binding || !responses.has(lead.slice(1).toLowerCase())) return false;
7689
+ } else if (/^\$[A-Za-z_][\w]*(?:\.[A-Za-z_][\w]*)*$/.test(lead)) {
7690
+ if (!responses.has(lead.slice(1).split(".")[0].toLowerCase())) return false;
7543
7691
  } else {
7544
7692
  if (binding) return false;
7693
+ if (/^gh\s+(?:run|pr)\s+view\s+\d+\s+--json\s+(?:[A-Za-z][A-Za-z,]*|'[A-Za-z][A-Za-z, ]*'|"[A-Za-z][A-Za-z, ]*")(?:\s+--jq\s+'[^'$`\r\n]{1,2000}')?$/i.test(lead) || /^npm\s+view\s+(?:@[\w.-]+\/)?[\w.-]+\s+version$/i.test(lead) || /^Get-ScheduledTask\s+-TaskName\s+(?:[\w.-]+|'[^'$`\r\n]+'|"[^"$`\r\n]+")$/i.test(lead)) continue;
7545
7694
  if (pipeline.length === 0 && /^(?:git\s+status(?:\s+--short)?|cd\s+(?:[A-Za-z]:[\\/][\w./\\-]+|'[A-Za-z]:[\\/][^'$`\r\n]+'))$/i.test(lead)) continue;
7546
7695
  const file = /^Get-Content\s+(?:'([^'\r\n]+)'|"([^"$`\r\n]+)")((?:\s+-(?:Raw|Tail\s+\d+|TotalCount\s+\d+))*)$/i.exec(lead);
7547
7696
  if (!file || !/^[A-Za-z]:[\\/]/.test(file[1] || file[2]) || /[?*]/.test(file[1] || file[2])) return false;
@@ -7723,6 +7872,35 @@ function evaluateActionPolicies(policies, toolName, operation, context = {}, dec
7723
7872
  return worstResult;
7724
7873
  }
7725
7874
  function inferToolContext(toolName, args) {
7875
+ const detectorArgs = {};
7876
+ const untrustedInput = /* @__PURE__ */ Object.create(null);
7877
+ for (const [key, value] of Object.entries(args)) {
7878
+ if (reservedInputFact(key) || LEGACY_TRUST_LABEL_ARGS.has(key)) untrustedInput[key] = value;
7879
+ else detectorArgs[key] = value;
7880
+ }
7881
+ if (Object.keys(untrustedInput).length) {
7882
+ let carrier = "__fcd_untrustedInput";
7883
+ while (Object.hasOwn(detectorArgs, carrier)) carrier += "_";
7884
+ detectorArgs[carrier] = untrustedInput;
7885
+ }
7886
+ const updated = (0, import_detectorRuntime.updatedDetectorFacts)(toolName, detectorArgs);
7887
+ if (!updated) return inferBundledToolContext(toolName, args);
7888
+ const context = { ...updated.context };
7889
+ for (const key of RESERVED_INPUT_FACTS) delete context[key];
7890
+ const argsText = stringifyArgs(args);
7891
+ context["toolArgs.bytes"] = String(Buffer.byteLength(argsText, "utf8"));
7892
+ for (const [key, value] of Object.entries(args)) {
7893
+ if (!["string", "number", "boolean"].includes(typeof value)) continue;
7894
+ context[`args.${key}`] = String(value);
7895
+ if (!reservedInputFact(key) && LEGACY_TRUST_LABEL_ARGS.has(key) && !Object.hasOwn(context, key)) context[key] = String(value);
7896
+ }
7897
+ if (!Object.hasOwn(context, "request.containsSecret") && recordHttpRequestFacts(context, args, extractUrl(args, argsText))) {
7898
+ context["destination.type"] = context["request.destination.type"];
7899
+ context["destination.domain"] = context["request.destination.domain"];
7900
+ }
7901
+ return { operation: updated.operation, context };
7902
+ }
7903
+ function inferBundledToolContext(toolName, args) {
7726
7904
  const context = {};
7727
7905
  let operation = toolName;
7728
7906
  const toolNameLower = toolName.toLowerCase();
@@ -7734,19 +7912,13 @@ function inferToolContext(toolName, args) {
7734
7912
  Number.isFinite(retryCount) ? retryCount : 0,
7735
7913
  Number.isFinite(fanoutCount) ? fanoutCount : 0
7736
7914
  );
7737
- context["toolArgs.bytes"] = String(Buffer.byteLength(argsText, "utf8"));
7738
- const secretKind = detectSecretKind(argsText);
7739
- context["toolArgs.containsSecret"] = secretKind !== "none" ? "true" : "false";
7740
- context["toolArgs.secretKind"] = secretKind;
7741
- context["toolArgs.containsPii"] = hasPiiLikeValue(argsText) ? "true" : "false";
7915
+ Object.assign(context, inputContentFacts(argsText));
7742
7916
  context["retry.count"] = String(burstCount);
7743
7917
  context["fanout.count"] = String(burstCount);
7744
7918
  for (const [key, val] of Object.entries(args)) {
7745
- if (typeof val === "string") {
7746
- context[key] = val;
7747
- } else if (typeof val === "number") {
7748
- context[key] = String(val);
7749
- }
7919
+ if (typeof val !== "string" && typeof val !== "number" && typeof val !== "boolean") continue;
7920
+ context[`args.${key}`] = String(val);
7921
+ if (!reservedInputFact(key)) context[key] = String(val);
7750
7922
  }
7751
7923
  const nameWords = toolNameLower.replace(/[_\-.]/g, " ");
7752
7924
  const isSearchTool = /\b(search|grep|find|lookup|locate)\b/.test(nameWords) && !/\b(delete|remove|drop|write|update|insert|replace|create)\b/.test(nameWords);
@@ -7779,7 +7951,7 @@ function inferToolContext(toolName, args) {
7779
7951
  else if (writeOps.some((op) => toolNameLower.includes(op))) operation = "write";
7780
7952
  else if (deleteOps.some((op) => toolNameLower.includes(op))) operation = "delete";
7781
7953
  }
7782
- const methodArg = (args.method || "").toUpperCase();
7954
+ const methodArg = typeof args.method === "string" ? args.method.toUpperCase() : "";
7783
7955
  if (["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"].includes(methodArg)) {
7784
7956
  operation = methodArg;
7785
7957
  if (args.url) context.url = args.url;
@@ -7794,20 +7966,19 @@ function inferToolContext(toolName, args) {
7794
7966
  const inbound = operation === "read" || ["GET", "HEAD", "OPTIONS", "SELECT", "SHOW", "DESCRIBE", "EXPLAIN"].includes(operation) || isCodeExecTool && !hasSecretLikeValue(argsText) && provenCompoundDownload(commandText, detectedUrl) || provenReadUrls.includes(detectedUrl) || operation === toolName && /\b(fetch|get|read|search|list|lookup|browse|navigate|download|clone|pull|view|open|crawl|scrape)\b/.test(nameWords) && !/\b(post|put|send|upload|push|write|submit|publish|export|create|update|delete|patch)\b/.test(nameWords);
7795
7967
  context["destination.domain"] = parsed.hostname;
7796
7968
  context["url.risk"] = hostType;
7969
+ const requestHasSecret = recordHttpRequestFacts(context, args, detectedUrl);
7797
7970
  if (inbound) {
7798
7971
  context["source.domain"] = parsed.hostname;
7799
7972
  context["source.type"] = hostType;
7800
- } else {
7973
+ }
7974
+ if (!inbound || requestHasSecret) {
7801
7975
  context["destination.type"] = hostType;
7802
7976
  }
7803
7977
  } catch {
7804
7978
  context["url.risk"] = "invalid";
7805
7979
  }
7806
7980
  }
7807
- if (typeof args.destinationType === "string") context["destination.type"] = args.destinationType;
7808
7981
  if (typeof args.destination === "string") context.destination = args.destination;
7809
- if (typeof args.domain === "string") context["destination.domain"] = args.domain;
7810
- if (typeof args.toolStatus === "string") context["tool.status"] = args.toolStatus;
7811
7982
  if (args.to || args.recipient || args.email) {
7812
7983
  context.to = args.to || args.recipient || args.email;
7813
7984
  context["destination.type"] = context["destination.type"] || "external";
@@ -7830,6 +8001,77 @@ function inferToolContext(toolName, args) {
7830
8001
  }
7831
8002
  return { operation, context };
7832
8003
  }
8004
+ var RESERVED_INPUT_FACTS = /* @__PURE__ */ new Set([
8005
+ "__proto__",
8006
+ "constructor",
8007
+ "prototype",
8008
+ "verdict",
8009
+ "decision",
8010
+ "approval",
8011
+ "toolName",
8012
+ "agentName",
8013
+ "agentClient",
8014
+ "developerName",
8015
+ "machineName",
8016
+ "machineKind",
8017
+ "machineId",
8018
+ "humanPresent",
8019
+ "organizationId",
8020
+ "tenantId",
8021
+ "shieldId",
8022
+ "userId",
8023
+ "ipAddress",
8024
+ "gateway",
8025
+ "mcpServer"
8026
+ ]);
8027
+ var RESERVED_FACT_NAMESPACES = /* @__PURE__ */ new Set([
8028
+ "args",
8029
+ "toolArgs",
8030
+ "tool",
8031
+ "agent",
8032
+ "action",
8033
+ "operation",
8034
+ "url",
8035
+ "destination",
8036
+ "source",
8037
+ "request",
8038
+ "retry",
8039
+ "fanout",
8040
+ "runtime",
8041
+ "machine",
8042
+ "session",
8043
+ "policy",
8044
+ "tenant",
8045
+ "organization",
8046
+ "role",
8047
+ "roles"
8048
+ ]);
8049
+ function reservedInputFact(key) {
8050
+ return RESERVED_INPUT_FACTS.has(key) || key.includes(".") && RESERVED_FACT_NAMESPACES.has(key.split(".")[0]);
8051
+ }
8052
+ var LEGACY_TRUST_LABEL_ARGS = /* @__PURE__ */ new Set(["destinationType", "domain", "toolStatus"]);
8053
+ function inputContentFacts(argsText) {
8054
+ const secretKind = detectSecretKind(argsText);
8055
+ return {
8056
+ "toolArgs.bytes": String(Buffer.byteLength(argsText, "utf8")),
8057
+ "toolArgs.containsSecret": secretKind !== "none" ? "true" : "false",
8058
+ "toolArgs.secretKind": secretKind,
8059
+ "toolArgs.containsPii": hasPiiLikeValue(argsText) ? "true" : "false"
8060
+ };
8061
+ }
8062
+ function recordHttpRequestFacts(context, args, detectedUrl) {
8063
+ try {
8064
+ const parsed = new URL(detectedUrl);
8065
+ if (!["http:", "https:"].includes(parsed.protocol)) return false;
8066
+ const containsSecret = hasSecretLikeValue(stringifyArgs({ url: detectedUrl, headers: args.headers, body: args.body, data: args.data }));
8067
+ context["request.destination.domain"] = parsed.hostname;
8068
+ context["request.destination.type"] = isInternalHost(parsed.hostname) ? "internal" : "external";
8069
+ context["request.containsSecret"] = containsSecret ? "true" : "false";
8070
+ return containsSecret;
8071
+ } catch {
8072
+ return false;
8073
+ }
8074
+ }
7833
8075
  function inferInventoryToolOperations(toolName, toolActions = []) {
7834
8076
  const ops = /* @__PURE__ */ new Set([toolName, toolName.toUpperCase(), ...toolActions]);
7835
8077
  const upper = toolName.toUpperCase();
@@ -7863,6 +8105,7 @@ function inventoryToolMatchesActionPolicy(toolName, toolActions, policies) {
7863
8105
  detectSecretKind,
7864
8106
  effectiveApprovalScope,
7865
8107
  evaluateActionPolicies,
8108
+ inferBundledToolContext,
7866
8109
  inferToolContext,
7867
8110
  inventoryToolMatchesActionPolicy,
7868
8111
  isLocalFileContentAction,
@@ -14,9 +14,7 @@ export declare const AGENT_TERMINAL_ASK_BLOCKED: string;
14
14
  /** Shown when the IDE prompt already answered for this exact command. */
15
15
  export declare const AGENT_TERMINAL_IDE_CONFIRMED = "allowed through the IDE permission gate; confirmation is recorded by the IDE post-execution hook.";
16
16
  /** Pending-marker key the IDE hook wrote for this shell command (see devConfirm.pendingKey). */
17
- export declare function ideShellPendingKey(command: string): string;
18
- /** Remembered-confirmation key for a Local Safety ask rule on the shell tool (see devConfirm rememberKey). */
19
- export declare function ideLocalSafetyRememberKey(ruleId: string): string;
17
+ export declare function ideShellPendingKey(command: string, cwd?: string): string;
20
18
  /** Maximum lifetime of an exact-command IDE permission handoff (not consent). */
21
19
  export declare const IDE_CONFIRMATION_WINDOW_MS: number;
22
20
  /**