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.
- package/dist/actionIdentity.js +2 -1
- package/dist/actionPolicyEngine.d.ts +38 -0
- package/dist/actionPolicyEngine.js +383 -24
- package/dist/approvalPrompt.js +2 -2
- package/dist/askDialog.js +5 -4
- package/dist/blockExplanation.js +2 -0
- package/dist/callerEnv.d.ts +33 -0
- package/dist/callerEnv.js +90 -0
- package/dist/commandNarrative.js +111 -4
- package/dist/commands/daemon.js +1 -1
- package/dist/commands/deterministicGuard.d.ts +32 -0
- package/dist/commands/deterministicGuard.js +214 -19
- package/dist/commands/hook.d.ts +34 -1
- package/dist/commands/hook.js +188 -31
- package/dist/commands/mcpGateway.js +2 -0
- package/dist/commands/taintLedger.js +3 -1
- package/dist/commands/workloadProtect.js +6 -2
- package/dist/devConfirm.d.ts +28 -12
- package/dist/devConfirm.js +134 -48
- package/dist/hookSlim.d.ts +1 -1
- package/dist/hookSlim.js +22 -2
- package/dist/nativeHook.d.ts +2 -1
- package/dist/nativeHook.js +109 -3
- package/dist/notify.js +4 -0
- package/dist/shellAst.d.ts +117 -0
- package/dist/shellAst.js +590 -0
- package/dist/shellAstLoader.d.ts +32 -0
- package/dist/shellAstLoader.js +271 -0
- package/dist/telemetry.d.ts +20 -0
- package/dist/telemetry.js +7 -0
- package/dist/vendor/tree-sitter/LICENSE.tree-sitter-bash +21 -0
- package/dist/vendor/tree-sitter/LICENSE.web-tree-sitter +21 -0
- package/dist/vendor/tree-sitter/VERSIONS.json +4 -0
- package/dist/vendor/tree-sitter/tree-sitter-bash.wasm +0 -0
- package/dist/vendor/tree-sitter/tree-sitter.cjs +4031 -0
- package/dist/vendor/tree-sitter/tree-sitter.wasm +0 -0
- package/dist/verdictIpc.js +5 -2
- package/dist/verdictIpcClient.d.ts +11 -1
- package/dist/verdictIpcClient.js +12 -2
- package/dist/version.json +1 -1
- 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
|
|
6485
|
-
const
|
|
6486
|
-
const
|
|
6487
|
-
if (
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
const
|
|
6491
|
-
return
|
|
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
|
|
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.-]
|
|
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
|
|
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 (
|
|
7738
|
-
if (
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
7946
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
});
|
package/dist/approvalPrompt.js
CHANGED
|
@@ -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
|
|
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
|
|
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');
|