fullcourtdefense-cli 1.34.11 → 1.34.12
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/actionPolicyEngine.js +56 -5
- package/dist/version.json +1 -1
- package/package.json +1 -1
|
@@ -6458,7 +6458,9 @@ var OPERATION_MATCH_SKIP_FIELDS = /* @__PURE__ */ new Set([
|
|
|
6458
6458
|
function buildOperationMatchText(_toolName, context) {
|
|
6459
6459
|
const parts = [];
|
|
6460
6460
|
for (const [key, value] of Object.entries(context)) {
|
|
6461
|
-
|
|
6461
|
+
const fieldWords = key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().split(/[_.-]/);
|
|
6462
|
+
const identifierField = /^(?:id|ids|uuid|uuids|guid|guids)$/.test(fieldWords[fieldWords.length - 1]);
|
|
6463
|
+
if (OPERATION_MATCH_SKIP_FIELDS.has(key) || identifierField) continue;
|
|
6462
6464
|
if (typeof value !== "string" || value.length === 0 || value.length > 2e3) continue;
|
|
6463
6465
|
const text = COMMAND_LINE_FIELDS.has(key) ? shellVerbWords(value) : value;
|
|
6464
6466
|
parts.push(`${key} ${text}`);
|
|
@@ -6647,8 +6649,18 @@ function extractUrl(args, _argsText) {
|
|
|
6647
6649
|
for (const field of URL_ACTION_FIELDS) {
|
|
6648
6650
|
const raw = args[field];
|
|
6649
6651
|
if (typeof raw !== "string") continue;
|
|
6650
|
-
const
|
|
6651
|
-
const
|
|
6652
|
+
const shellField = ["command", "cmd", "script", "input"].includes(field);
|
|
6653
|
+
const value = shellField ? shellUrlActionText(raw) : raw;
|
|
6654
|
+
const firstUrl = (text) => text.match(/https?:\/\/[^\s"'<>]+/i)?.[0] || text.match(/\b(?:s3|gs|ftp|sftp|smb):\/\/[^\s"'<>]+/i)?.[0];
|
|
6655
|
+
const outboundUrls = shellField ? splitShellSegments(value).filter((segment) => /^(?:curl(?:\.exe)?|wget(?:\.exe)?|invoke-restmethod|invoke-webrequest|irm|iwr)$/i.test(shellSegmentLead(segment)) && classifyShellSegment(segment) === "write").map(firstUrl).filter((url) => Boolean(url)) : [];
|
|
6656
|
+
const externalOutbound = outboundUrls.find((url) => {
|
|
6657
|
+
try {
|
|
6658
|
+
return !isInternalHost(new URL(url).hostname);
|
|
6659
|
+
} catch {
|
|
6660
|
+
return false;
|
|
6661
|
+
}
|
|
6662
|
+
});
|
|
6663
|
+
const found = externalOutbound || outboundUrls[0] || firstUrl(value);
|
|
6652
6664
|
if (found) return found;
|
|
6653
6665
|
}
|
|
6654
6666
|
return "";
|
|
@@ -7018,6 +7030,44 @@ function classifyShellCommandOperation(command, depth = 0) {
|
|
|
7018
7030
|
if (segments.length === 0) return "SHELL";
|
|
7019
7031
|
return worstShellOp(segments.map((segment) => classifyShellSegment(segment, depth)), "read");
|
|
7020
7032
|
}
|
|
7033
|
+
function provenCompoundDownload(command, detectedUrl) {
|
|
7034
|
+
const segments = splitShellSegments(command).filter((segment) => /\b(?:https?|s3|gs|ftp|sftp|smb):\/\//i.test(segment));
|
|
7035
|
+
if (!segments.length || !command.includes(detectedUrl)) return false;
|
|
7036
|
+
return segments.every((segment) => {
|
|
7037
|
+
if (/[$`{}()<>;&]/.test(segment)) return false;
|
|
7038
|
+
const pipeline = splitShellSegments(segment, true);
|
|
7039
|
+
if (pipeline.slice(1).some((part) => !/^(?:Select-Object|Format-Table|Format-List)\s+[\w., *-]+$|^Out-Null$/i.test(part))) return false;
|
|
7040
|
+
const raw = pipeline[0];
|
|
7041
|
+
const pieces = raw.match(/"[^"\r\n]*"|'[^'\r\n]*'|[^\s"']+/g) || [];
|
|
7042
|
+
if (pieces.join("").replace(/\s/g, "") !== raw.replace(/\s/g, "")) return false;
|
|
7043
|
+
const tokens = pieces.map((piece) => piece.replace(/^["']|["']$/g, ""));
|
|
7044
|
+
if (!/^(?:curl(?:\.exe)?|wget(?:\.exe)?|Invoke-RestMethod|Invoke-WebRequest|irm|iwr)$/i.test(tokens.shift() || "")) return false;
|
|
7045
|
+
let urls = 0;
|
|
7046
|
+
for (let i2 = 0; i2 < tokens.length; i2++) {
|
|
7047
|
+
const token = tokens[i2];
|
|
7048
|
+
if (/^https?:\/\//i.test(token)) {
|
|
7049
|
+
urls++;
|
|
7050
|
+
continue;
|
|
7051
|
+
}
|
|
7052
|
+
if (/^-Uri$/i.test(token) && /^https?:\/\//i.test(tokens[i2 + 1] || "")) {
|
|
7053
|
+
urls++;
|
|
7054
|
+
i2++;
|
|
7055
|
+
continue;
|
|
7056
|
+
}
|
|
7057
|
+
if (/^(?:-X|--request|-Method)$/.test(token) && /^(?:GET|HEAD|OPTIONS)$/i.test(tokens[i2 + 1] || "")) {
|
|
7058
|
+
i2++;
|
|
7059
|
+
continue;
|
|
7060
|
+
}
|
|
7061
|
+
if (/^-[sfSLI]+$|^--(?:silent|fail|location|head)$|^-UseBasicParsing$/.test(token)) continue;
|
|
7062
|
+
if (/^-TimeoutSec$/i.test(token) && /^\d+$/.test(tokens[i2 + 1] || "")) {
|
|
7063
|
+
i2++;
|
|
7064
|
+
continue;
|
|
7065
|
+
}
|
|
7066
|
+
return false;
|
|
7067
|
+
}
|
|
7068
|
+
return urls === 1;
|
|
7069
|
+
});
|
|
7070
|
+
}
|
|
7021
7071
|
function isInternalHost(hostname) {
|
|
7022
7072
|
const host = hostname.toLowerCase();
|
|
7023
7073
|
if (!host || host === "localhost" || host === "::1" || host.endsWith(".local") || host.endsWith(".internal")) return true;
|
|
@@ -7175,6 +7225,7 @@ function inferToolContext(toolName, args) {
|
|
|
7175
7225
|
let operation = toolName;
|
|
7176
7226
|
const toolNameLower = toolName.toLowerCase();
|
|
7177
7227
|
const argsText = stringifyArgs(args);
|
|
7228
|
+
const commandText = String(args.command || args.cmd || args.code || args.script || args.input || "");
|
|
7178
7229
|
const retryCount = Number(args.retryCount ?? args.retries ?? args.attempts ?? 0);
|
|
7179
7230
|
const fanoutCount = Number(args.fanoutCount ?? args.recipientCount ?? args.childCalls ?? (Array.isArray(args.recipients) ? args.recipients.length : 0));
|
|
7180
7231
|
const burstCount = Math.max(
|
|
@@ -7214,7 +7265,7 @@ function inferToolContext(toolName, args) {
|
|
|
7214
7265
|
}
|
|
7215
7266
|
}
|
|
7216
7267
|
if (operation === toolName && CODE_EXEC_TOOL_NAME_RE.test(nameWords)) {
|
|
7217
|
-
operation = classifyShellCommandOperation(
|
|
7268
|
+
operation = classifyShellCommandOperation(commandText);
|
|
7218
7269
|
}
|
|
7219
7270
|
const pathArg = args.path || args.filepath || args.file || args.filename || "";
|
|
7220
7271
|
if (typeof pathArg === "string" && pathArg.length > 0) {
|
|
@@ -7237,7 +7288,7 @@ function inferToolContext(toolName, args) {
|
|
|
7237
7288
|
try {
|
|
7238
7289
|
const parsed = new URL(detectedUrl);
|
|
7239
7290
|
const hostType = isInternalHost(parsed.hostname) ? "internal" : "external";
|
|
7240
|
-
const inbound = operation === "read" || ["GET", "HEAD", "OPTIONS", "SELECT", "SHOW", "DESCRIBE", "EXPLAIN"].includes(operation) || operation === "SHELL" && isCodeExecTool && !hasSecretLikeValue(argsText) && provenInlineJavaScriptRead(
|
|
7291
|
+
const inbound = operation === "read" || ["GET", "HEAD", "OPTIONS", "SELECT", "SHOW", "DESCRIBE", "EXPLAIN"].includes(operation) || isCodeExecTool && !hasSecretLikeValue(argsText) && provenCompoundDownload(commandText, detectedUrl) || operation === "SHELL" && isCodeExecTool && !hasSecretLikeValue(argsText) && provenInlineJavaScriptRead(commandText, 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);
|
|
7241
7292
|
context["destination.domain"] = parsed.hostname;
|
|
7242
7293
|
context["url.risk"] = hostType;
|
|
7243
7294
|
if (inbound) {
|
package/dist/version.json
CHANGED