fullcourtdefense-cli 1.34.11 → 1.34.14
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 +402 -16
- package/dist/version.json +1 -1
- package/package.json +1 -1
|
@@ -5738,6 +5738,14 @@ function parse3(input, options) {
|
|
|
5738
5738
|
}
|
|
5739
5739
|
|
|
5740
5740
|
// src/actionPolicyEngine.ts
|
|
5741
|
+
function isFirestoreQueryUrl(target) {
|
|
5742
|
+
try {
|
|
5743
|
+
const url = new URL(target);
|
|
5744
|
+
return url.origin === "https://firestore.googleapis.com" && !url.username && !url.password && !url.search && !url.hash && /^\/v1\/projects\/[^/]+\/databases\/[^/]+\/documents(?:\/[^/]+)*:runQuery$/.test(url.pathname);
|
|
5745
|
+
} catch {
|
|
5746
|
+
return false;
|
|
5747
|
+
}
|
|
5748
|
+
}
|
|
5741
5749
|
function provenJavaScriptReadUrls(code) {
|
|
5742
5750
|
if (code.length > 32768) return [];
|
|
5743
5751
|
const literal2 = (value) => ({ kind: "literal", value });
|
|
@@ -5758,6 +5766,8 @@ function provenJavaScriptReadUrls(code) {
|
|
|
5758
5766
|
const readProperty = (value, key) => {
|
|
5759
5767
|
if (["constructor", "__proto__", "prototype"].includes(key)) return fail();
|
|
5760
5768
|
if (value.kind === "object") return value.fields.get(key) || literal2(void 0);
|
|
5769
|
+
if (value.kind === "array" && key === "map") return { kind: "arrayMap", items: value.items };
|
|
5770
|
+
if (value.kind === "promise" && key === "catch") return callable("promise.catch");
|
|
5761
5771
|
if (value.kind === "response") return ["json", "text"].includes(key) ? callable(`response.${key}`) : { kind: "data" };
|
|
5762
5772
|
if (value.kind === "credential" && key === "trim") return callable("credential.trim");
|
|
5763
5773
|
if (value.kind === "string" || value.kind === "literal" && typeof value.value === "string") {
|
|
@@ -5822,24 +5832,97 @@ function provenJavaScriptReadUrls(code) {
|
|
|
5822
5832
|
const left = ev(node.left);
|
|
5823
5833
|
const right = ev(node.right);
|
|
5824
5834
|
if (node.operator !== "+") return fail();
|
|
5825
|
-
if (left.kind === "literal" && right.kind === "literal" && typeof left.value === "string" && typeof right.value === "string")
|
|
5835
|
+
if (left.kind === "literal" && right.kind === "literal" && typeof left.value === "string" && typeof right.value === "string") {
|
|
5836
|
+
if (left.value.length + right.value.length > 32768) return fail();
|
|
5837
|
+
return literal2(left.value + right.value);
|
|
5838
|
+
}
|
|
5826
5839
|
if (left.kind === "literal" && left.value === "Bearer " && right.kind === "credential") return { kind: "authorization" };
|
|
5827
5840
|
return fail();
|
|
5828
5841
|
}
|
|
5829
5842
|
case "ArrowFunctionExpression":
|
|
5830
5843
|
case "FunctionExpression":
|
|
5831
|
-
if (node.params.
|
|
5844
|
+
if (node.params.some((param) => param.type !== "Identifier") || node.generator) return fail();
|
|
5832
5845
|
return { kind: "function", node, env: new Map(env) };
|
|
5846
|
+
case "ForOfStatement": {
|
|
5847
|
+
const items = ev(node.right);
|
|
5848
|
+
if (node.await || items.kind !== "array" || items.items.length > 100 || node.left.type !== "VariableDeclaration" || node.left.kind !== "const" || node.left.declarations.length !== 1) return fail();
|
|
5849
|
+
for (const item of items.items) {
|
|
5850
|
+
const scope = new Map(env);
|
|
5851
|
+
const id = node.left.declarations[0].id;
|
|
5852
|
+
if (id.type === "Identifier") scope.set(id.name, item);
|
|
5853
|
+
else if (id.type === "ArrayPattern" && item.kind === "array" && id.elements.length === item.items.length) {
|
|
5854
|
+
id.elements.forEach((element, index) => {
|
|
5855
|
+
if (element?.type !== "Identifier") fail();
|
|
5856
|
+
scope.set(element.name, item.items[index]);
|
|
5857
|
+
});
|
|
5858
|
+
} else return fail();
|
|
5859
|
+
evaluate(node.body, scope, depth + 1);
|
|
5860
|
+
}
|
|
5861
|
+
return literal2(void 0);
|
|
5862
|
+
}
|
|
5863
|
+
case "AssignmentExpression": {
|
|
5864
|
+
if (node.operator !== "=" || node.left.type !== "MemberExpression" || node.left.computed || node.left.object.type !== "Identifier" || node.left.object.name !== "process" || env.has("process") || node.left.property.name !== "exitCode" || ev(node.right).kind !== "literal" || node.right.type !== "Literal" || node.right.value !== 1) return fail();
|
|
5865
|
+
return literal2(void 0);
|
|
5866
|
+
}
|
|
5867
|
+
case "NewExpression": {
|
|
5868
|
+
const constructor = ev(node.callee);
|
|
5869
|
+
if (constructor.kind !== "call" || constructor.name !== "GoogleAuth" || node.arguments.length !== 1) return fail();
|
|
5870
|
+
const options = ev(node.arguments[0]);
|
|
5871
|
+
if (options.kind !== "object" || options.fields.size !== 1) return fail();
|
|
5872
|
+
const scopes = options.fields.get("scopes");
|
|
5873
|
+
if (scopes?.kind !== "array" || scopes.items.length !== 1 || string(scopes.items[0]) !== "https://www.googleapis.com/auth/datastore") return fail();
|
|
5874
|
+
return object([["getClient", callable("google.getClient")]]);
|
|
5875
|
+
}
|
|
5833
5876
|
case "ReturnStatement":
|
|
5834
5877
|
return node.argument ? ev(node.argument) : literal2(void 0);
|
|
5835
5878
|
case "CallExpression": {
|
|
5836
5879
|
const callee = ev(node.callee);
|
|
5837
5880
|
const args = node.arguments.map(ev);
|
|
5838
5881
|
if (callee.kind === "function") {
|
|
5839
|
-
if (args.length) return fail();
|
|
5840
|
-
|
|
5882
|
+
if (args.length !== callee.node.params.length) return fail();
|
|
5883
|
+
const scope = new Map(callee.env);
|
|
5884
|
+
args.forEach((value, index) => scope.set(callee.node.params[index].name, value));
|
|
5885
|
+
const result = evaluate(callee.node.body, scope, depth + 1);
|
|
5886
|
+
return callee.node.async ? { kind: "promise" } : result;
|
|
5887
|
+
}
|
|
5888
|
+
if (callee.kind === "arrayMap") {
|
|
5889
|
+
if (args.length !== 1 || args[0].kind !== "function" || args[0].node.params.length !== 1 || args[0].node.async) return fail();
|
|
5890
|
+
const fn = args[0];
|
|
5891
|
+
return { kind: "array", items: callee.items.map((item) => {
|
|
5892
|
+
const scope = new Map(fn.env);
|
|
5893
|
+
scope.set(fn.node.params[0].name, item);
|
|
5894
|
+
return evaluate(fn.node.body, scope, depth + 1);
|
|
5895
|
+
}) };
|
|
5841
5896
|
}
|
|
5842
5897
|
if (callee.kind !== "call") return fail();
|
|
5898
|
+
if (callee.name === "google.getClient") {
|
|
5899
|
+
if (args.length) return fail();
|
|
5900
|
+
return object([["request", callable("google.request")]]);
|
|
5901
|
+
}
|
|
5902
|
+
if (callee.name === "google.request") {
|
|
5903
|
+
if (args.length !== 1 || args[0].kind !== "object") return fail();
|
|
5904
|
+
const fields = args[0].fields;
|
|
5905
|
+
if ([...fields.keys()].some((key) => !["url", "method", "data"].includes(key))) return fail();
|
|
5906
|
+
const target = string(fields.get("url") || fail());
|
|
5907
|
+
if (!isFirestoreQueryUrl(target) || string(fields.get("method") || fail()).toUpperCase() !== "POST") return fail();
|
|
5908
|
+
const body = fields.get("data");
|
|
5909
|
+
const passive = (value) => {
|
|
5910
|
+
if (--budget < 0) return false;
|
|
5911
|
+
if (value.kind === "literal") return true;
|
|
5912
|
+
if (value.kind === "object") return [...value.fields.values()].every(passive);
|
|
5913
|
+
return value.kind === "array" && value.items.every(passive);
|
|
5914
|
+
};
|
|
5915
|
+
if (body?.kind !== "object" || !body.fields.has("structuredQuery") || [...body.fields.keys()].some((key) => !["structuredQuery", "readTime", "transaction"].includes(key)) || !passive(body)) return fail();
|
|
5916
|
+
urls.push(target);
|
|
5917
|
+
return { kind: "data" };
|
|
5918
|
+
}
|
|
5919
|
+
if (callee.name === "promise.catch") {
|
|
5920
|
+
if (args.length !== 1 || args[0].kind !== "function" || args[0].node.params.length !== 1) return fail();
|
|
5921
|
+
const scope = new Map(args[0].env);
|
|
5922
|
+
scope.set(args[0].node.params[0].name, { kind: "data" });
|
|
5923
|
+
evaluate(args[0].node.body, scope, depth + 1);
|
|
5924
|
+
return { kind: "data" };
|
|
5925
|
+
}
|
|
5843
5926
|
if (callee.name === "fetch") {
|
|
5844
5927
|
if (args.length < 1 || args.length > 2) return fail();
|
|
5845
5928
|
const target = string(args[0]);
|
|
@@ -5866,6 +5949,7 @@ function provenJavaScriptReadUrls(code) {
|
|
|
5866
5949
|
return { kind: "response" };
|
|
5867
5950
|
}
|
|
5868
5951
|
if (callee.name === "require") {
|
|
5952
|
+
if (args.length === 1 && string(args[0]) === "google-auth-library") return object([["GoogleAuth", callable("GoogleAuth")]]);
|
|
5869
5953
|
if (args.length !== 1 || !["node:child_process", "child_process"].includes(string(args[0]))) return fail();
|
|
5870
5954
|
return object([["execFileSync", callable("execFileSync")]]);
|
|
5871
5955
|
}
|
|
@@ -5888,7 +5972,7 @@ function provenJavaScriptReadUrls(code) {
|
|
|
5888
5972
|
if (args.length !== 1 || !jsonSafe(args[0])) return fail();
|
|
5889
5973
|
return { kind: "string" };
|
|
5890
5974
|
}
|
|
5891
|
-
if (callee.name === "console.log") return literal2(void 0);
|
|
5975
|
+
if (callee.name === "console.log" || callee.name === "console.error") return literal2(void 0);
|
|
5892
5976
|
if (callee.name === "string.trim") {
|
|
5893
5977
|
if (args.length) return fail();
|
|
5894
5978
|
return { kind: "string" };
|
|
@@ -5913,14 +5997,14 @@ function provenJavaScriptReadUrls(code) {
|
|
|
5913
5997
|
["fetch", callable("fetch")],
|
|
5914
5998
|
["require", callable("require")],
|
|
5915
5999
|
["JSON", object([["stringify", callable("JSON.stringify")]])],
|
|
5916
|
-
["console", object([["log", callable("console.log")]])]
|
|
6000
|
+
["console", object([["log", callable("console.log")], ["error", callable("console.error")]])]
|
|
5917
6001
|
]));
|
|
5918
6002
|
return urls;
|
|
5919
6003
|
} catch {
|
|
5920
6004
|
return [];
|
|
5921
6005
|
}
|
|
5922
6006
|
}
|
|
5923
|
-
function
|
|
6007
|
+
function provenInlineJavaScriptReadUrls(command) {
|
|
5924
6008
|
const urls = [];
|
|
5925
6009
|
for (const segment of splitShellSegments(command)) {
|
|
5926
6010
|
const here = segment.match(/^\s*@'\r?\n([\s\S]*?)\r?\n'@\s*\|\s*node(?:\.exe)?(?:\s+-)?\s*$/i);
|
|
@@ -5928,14 +6012,197 @@ function provenInlineJavaScriptRead(command, detectedUrl) {
|
|
|
5928
6012
|
const code = here?.[1] ?? inline?.[1];
|
|
5929
6013
|
if (code !== void 0) {
|
|
5930
6014
|
const proven = provenJavaScriptReadUrls(code);
|
|
5931
|
-
if (!proven.length) return
|
|
6015
|
+
if (!proven.length) return [];
|
|
5932
6016
|
urls.push(...proven);
|
|
5933
6017
|
} else {
|
|
5934
6018
|
const lead = shellSegmentLead(segment);
|
|
5935
|
-
if (!(SHELL_READ_PROGRAMS.has(lead) || ["git", "gh", "cd", "set-location"].includes(lead)) || classifyShellCommandOperation(segment) !== "read" || /https?:\/\//i.test(segment)) return
|
|
6019
|
+
if (!(SHELL_READ_PROGRAMS.has(lead) || ["git", "gh", "cd", "set-location"].includes(lead)) || classifyShellCommandOperation(segment) !== "read" || /https?:\/\//i.test(segment)) return [];
|
|
6020
|
+
}
|
|
6021
|
+
}
|
|
6022
|
+
return urls;
|
|
6023
|
+
}
|
|
6024
|
+
function provenPowerShellQueryUrls(command) {
|
|
6025
|
+
if (command.length > 32768) return [];
|
|
6026
|
+
const prefix = /^\s*(?:git\s+status(?:\s+--short)?(?:\s+--branch)?|(?:cd|Set-Location)\s+(?:[A-Za-z]:[\\/][A-Za-z0-9_./\\-]+|'[A-Za-z]:[\\/][A-Za-z0-9_ ./\\-]+'))\s*(?:&&|;)\s*/i;
|
|
6027
|
+
for (let count = 0; count < 8 && prefix.test(command); count++) command = command.replace(prefix, "");
|
|
6028
|
+
const credential = Object.freeze({ credential: 1 });
|
|
6029
|
+
const authorization = Object.freeze({ authorization: 1 });
|
|
6030
|
+
const urls = [];
|
|
6031
|
+
const env = /* @__PURE__ */ new Map();
|
|
6032
|
+
const tokens = [];
|
|
6033
|
+
const fail = () => {
|
|
6034
|
+
throw new Error("Unsupported PowerShell query");
|
|
6035
|
+
};
|
|
6036
|
+
const lexer = /[ \t]+|\r?\n|@\{|@\(|'(?:[^']|'')*'|"[^"`]*"|\$[A-Za-z_][A-Za-z0-9_]*|-[A-Za-z]+|[A-Za-z_][A-Za-z0-9_-]*|\d+|[{}();=+|,]/gy;
|
|
6037
|
+
let offset2 = 0;
|
|
6038
|
+
let budget = 8e3;
|
|
6039
|
+
let pos = 0;
|
|
6040
|
+
const take = () => {
|
|
6041
|
+
if (--budget < 0 || pos >= tokens.length) return fail();
|
|
6042
|
+
return tokens[pos++];
|
|
6043
|
+
};
|
|
6044
|
+
const peek = () => tokens[pos];
|
|
6045
|
+
const expect = (token) => {
|
|
6046
|
+
if (take().toLowerCase() !== token.toLowerCase()) fail();
|
|
6047
|
+
};
|
|
6048
|
+
const separators = () => {
|
|
6049
|
+
while (peek() === ";") take();
|
|
6050
|
+
};
|
|
6051
|
+
const passive = (value) => {
|
|
6052
|
+
if (--budget < 0 || value === credential || value === authorization) return false;
|
|
6053
|
+
if (typeof value !== "object") return true;
|
|
6054
|
+
return Object.values(value).every(passive);
|
|
6055
|
+
};
|
|
6056
|
+
const expression = () => {
|
|
6057
|
+
const token = take();
|
|
6058
|
+
let value;
|
|
6059
|
+
if (token.startsWith("'")) value = token.slice(1, -1).replace(/''/g, "'");
|
|
6060
|
+
else if (token.startsWith('"')) {
|
|
6061
|
+
const text = token.slice(1, -1);
|
|
6062
|
+
const bearer = text.match(/^Bearer (\$[A-Za-z_][A-Za-z0-9_]*)$/);
|
|
6063
|
+
if (bearer && env.get(bearer[1].toLowerCase()) === credential) value = authorization;
|
|
6064
|
+
else {
|
|
6065
|
+
if (/[$`]/.test(text)) return fail();
|
|
6066
|
+
value = text;
|
|
6067
|
+
}
|
|
6068
|
+
} else if (token.startsWith("$")) value = env.get(token.toLowerCase()) ?? fail();
|
|
6069
|
+
else if (/^\d+$/.test(token)) value = Number(token);
|
|
6070
|
+
else if (token === "(") {
|
|
6071
|
+
value = expression();
|
|
6072
|
+
expect(")");
|
|
6073
|
+
} else if (token === "@(") {
|
|
6074
|
+
const items = [];
|
|
6075
|
+
separators();
|
|
6076
|
+
while (peek() !== ")") {
|
|
6077
|
+
items.push(expression());
|
|
6078
|
+
separators();
|
|
6079
|
+
if (peek() !== ")") expect(",");
|
|
6080
|
+
}
|
|
6081
|
+
expect(")");
|
|
6082
|
+
value = items;
|
|
6083
|
+
} else if (token === "@{") {
|
|
6084
|
+
const fields = /* @__PURE__ */ Object.create(null);
|
|
6085
|
+
separators();
|
|
6086
|
+
while (peek() !== "}") {
|
|
6087
|
+
const key = take();
|
|
6088
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key) || Object.hasOwn(fields, key)) return fail();
|
|
6089
|
+
expect("=");
|
|
6090
|
+
fields[key] = expression();
|
|
6091
|
+
if (peek() !== "}") expect(";");
|
|
6092
|
+
separators();
|
|
6093
|
+
}
|
|
6094
|
+
expect("}");
|
|
6095
|
+
value = fields;
|
|
6096
|
+
} else if (token.toLowerCase() === "if") {
|
|
6097
|
+
expect("(");
|
|
6098
|
+
const left = expression();
|
|
6099
|
+
expect("-eq");
|
|
6100
|
+
const right = expression();
|
|
6101
|
+
expect(")");
|
|
6102
|
+
if (typeof left === "object" || typeof right === "object") return fail();
|
|
6103
|
+
expect("{");
|
|
6104
|
+
const yes = expression();
|
|
6105
|
+
expect("}");
|
|
6106
|
+
expect("else");
|
|
6107
|
+
expect("{");
|
|
6108
|
+
const no = expression();
|
|
6109
|
+
expect("}");
|
|
6110
|
+
value = left === right ? yes : no;
|
|
6111
|
+
} else if (token.toLowerCase() === "gcloud") {
|
|
6112
|
+
expect("auth");
|
|
6113
|
+
expect("print-access-token");
|
|
6114
|
+
value = credential;
|
|
6115
|
+
} else return fail();
|
|
6116
|
+
while (peek() === "+") {
|
|
6117
|
+
take();
|
|
6118
|
+
const right = expression();
|
|
6119
|
+
if (typeof value !== "string" || typeof right !== "string" || value.length + right.length > 32768) return fail();
|
|
6120
|
+
value += right;
|
|
6121
|
+
}
|
|
6122
|
+
if (peek() === "|") {
|
|
6123
|
+
take();
|
|
6124
|
+
expect("ConvertTo-Json");
|
|
6125
|
+
expect("-Depth");
|
|
6126
|
+
const depth = take();
|
|
6127
|
+
if (!/^\d+$/.test(depth) || Number(depth) > 100 || !passive(value)) return fail();
|
|
6128
|
+
}
|
|
6129
|
+
return value;
|
|
6130
|
+
};
|
|
6131
|
+
const statement = () => {
|
|
6132
|
+
if (peek()?.startsWith("$")) {
|
|
6133
|
+
const name = take().toLowerCase();
|
|
6134
|
+
expect("=");
|
|
6135
|
+
if (["$env", "$psdefaultparametervalues", "$executioncontext", "$erroractionpreference", "$home", "$profile"].includes(name)) return fail();
|
|
6136
|
+
env.set(name, expression());
|
|
6137
|
+
return;
|
|
6138
|
+
}
|
|
6139
|
+
if (peek()?.toLowerCase() === "foreach") {
|
|
6140
|
+
take();
|
|
6141
|
+
expect("(");
|
|
6142
|
+
const name = take().toLowerCase();
|
|
6143
|
+
if (!/^\$[a-z_][a-z0-9_]*$/.test(name)) return fail();
|
|
6144
|
+
expect("in");
|
|
6145
|
+
const items = expression();
|
|
6146
|
+
expect(")");
|
|
6147
|
+
expect("{");
|
|
6148
|
+
if (!Array.isArray(items) || !items.length || items.length > 100) return fail();
|
|
6149
|
+
const start = pos;
|
|
6150
|
+
for (const item of items) {
|
|
6151
|
+
env.set(name, item);
|
|
6152
|
+
pos = start;
|
|
6153
|
+
separators();
|
|
6154
|
+
while (peek() !== "}") {
|
|
6155
|
+
statement();
|
|
6156
|
+
if (peek() !== "}") expect(";");
|
|
6157
|
+
separators();
|
|
6158
|
+
}
|
|
6159
|
+
expect("}");
|
|
6160
|
+
}
|
|
6161
|
+
return;
|
|
6162
|
+
}
|
|
6163
|
+
expect("Invoke-RestMethod");
|
|
6164
|
+
const options = /* @__PURE__ */ new Map();
|
|
6165
|
+
while (peek()?.startsWith("-")) {
|
|
6166
|
+
const flag = take().toLowerCase();
|
|
6167
|
+
if (options.has(flag)) return fail();
|
|
6168
|
+
options.set(flag, flag === "-method" ? take() : expression());
|
|
6169
|
+
}
|
|
6170
|
+
if ([...options.keys()].some((key) => !["-method", "-uri", "-headers", "-contenttype", "-body"].includes(key))) return fail();
|
|
6171
|
+
const target = options.get("-uri");
|
|
6172
|
+
if (typeof target !== "string") return fail();
|
|
6173
|
+
if (!isFirestoreQueryUrl(target) || String(options.get("-method")).toUpperCase() !== "POST" || options.get("-contenttype") !== "application/json") return fail();
|
|
6174
|
+
const headers = options.get("-headers");
|
|
6175
|
+
const body = options.get("-body");
|
|
6176
|
+
if (!headers || typeof headers !== "object" || Array.isArray(headers) || Object.keys(headers).length !== 1 || headers.Authorization !== authorization) return fail();
|
|
6177
|
+
if (!body || typeof body !== "object" || Array.isArray(body) || !Object.hasOwn(body, "structuredQuery") || Object.keys(body).some((key) => !["structuredQuery", "transaction", "readTime"].includes(key)) || !passive(body)) return fail();
|
|
6178
|
+
urls.push(target);
|
|
6179
|
+
if (peek() === "|") {
|
|
6180
|
+
take();
|
|
6181
|
+
expect("ConvertTo-Json");
|
|
6182
|
+
expect("-Depth");
|
|
6183
|
+
if (!/^\d+$/.test(take())) fail();
|
|
6184
|
+
}
|
|
6185
|
+
};
|
|
6186
|
+
try {
|
|
6187
|
+
while (offset2 < command.length) {
|
|
6188
|
+
lexer.lastIndex = offset2;
|
|
6189
|
+
const match = lexer.exec(command);
|
|
6190
|
+
if (!match) return [];
|
|
6191
|
+
offset2 = lexer.lastIndex;
|
|
6192
|
+
const token = match[0];
|
|
6193
|
+
if (/^[ \t]+$/.test(token)) continue;
|
|
6194
|
+
tokens.push(/\r?\n/.test(token) && token.length <= 2 ? ";" : token);
|
|
6195
|
+
}
|
|
6196
|
+
separators();
|
|
6197
|
+
while (pos < tokens.length) {
|
|
6198
|
+
statement();
|
|
6199
|
+
if (pos < tokens.length) expect(";");
|
|
6200
|
+
separators();
|
|
5936
6201
|
}
|
|
6202
|
+
return urls;
|
|
6203
|
+
} catch {
|
|
6204
|
+
return [];
|
|
5937
6205
|
}
|
|
5938
|
-
return urls.includes(detectedUrl);
|
|
5939
6206
|
}
|
|
5940
6207
|
function approvalExceptionError(rule) {
|
|
5941
6208
|
const ids = rule.approval?.replacesPolicyIds;
|
|
@@ -6445,6 +6712,7 @@ var OPERATION_MATCH_SKIP_FIELDS = /* @__PURE__ */ new Set([
|
|
|
6445
6712
|
"destination.domain",
|
|
6446
6713
|
"destination.host",
|
|
6447
6714
|
"destination.type",
|
|
6715
|
+
"destination.repository",
|
|
6448
6716
|
"destinationHost",
|
|
6449
6717
|
"host",
|
|
6450
6718
|
"hostname",
|
|
@@ -6458,7 +6726,9 @@ var OPERATION_MATCH_SKIP_FIELDS = /* @__PURE__ */ new Set([
|
|
|
6458
6726
|
function buildOperationMatchText(_toolName, context) {
|
|
6459
6727
|
const parts = [];
|
|
6460
6728
|
for (const [key, value] of Object.entries(context)) {
|
|
6461
|
-
|
|
6729
|
+
const fieldWords = key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().split(/[_.-]/);
|
|
6730
|
+
const identifierField = /^(?:id|ids|uuid|uuids|guid|guids)$/.test(fieldWords[fieldWords.length - 1]);
|
|
6731
|
+
if (OPERATION_MATCH_SKIP_FIELDS.has(key) || identifierField) continue;
|
|
6462
6732
|
if (typeof value !== "string" || value.length === 0 || value.length > 2e3) continue;
|
|
6463
6733
|
const text = COMMAND_LINE_FIELDS.has(key) ? shellVerbWords(value) : value;
|
|
6464
6734
|
parts.push(`${key} ${text}`);
|
|
@@ -6641,14 +6911,81 @@ function hasPiiLikeValue(raw) {
|
|
|
6641
6911
|
return false;
|
|
6642
6912
|
}
|
|
6643
6913
|
var URL_ACTION_FIELDS = ["command", "cmd", "script", "input", "target", "address", "host", "server", "baseUrl", "base_url", "apiUrl", "api_url", "remote", "repo", "repository", "source", "destination"];
|
|
6914
|
+
function explicitGitHubWriteRepository(command) {
|
|
6915
|
+
if (command.length > 65536 || /[%!]/.test(command)) return void 0;
|
|
6916
|
+
const segments = splitShellSegments(command);
|
|
6917
|
+
if (segments.length !== 1) return void 0;
|
|
6918
|
+
const segment = segments[0];
|
|
6919
|
+
const tokens = [];
|
|
6920
|
+
const token = /[ \t]*(?:'([^'\r\n]*)'|"([^"$`\\\r\n]*)"|([^\s'"$`\\|;&<>(){}\r\n]+))/gy;
|
|
6921
|
+
let offset2 = 0;
|
|
6922
|
+
while (offset2 < segment.length) {
|
|
6923
|
+
token.lastIndex = offset2;
|
|
6924
|
+
const match = token.exec(segment);
|
|
6925
|
+
if (!match || token.lastIndex < segment.length && !/[ \t]/.test(segment[token.lastIndex])) return void 0;
|
|
6926
|
+
if (match[3] !== void 0 && (!/^[a-z0-9_./:@=+-]+$/i.test(match[3]) || match[3].startsWith("@"))) return void 0;
|
|
6927
|
+
if (match[1] !== void 0 && /[|;&<>()$`^]/.test(match[1])) return void 0;
|
|
6928
|
+
tokens.push(match[1] ?? match[2] ?? match[3]);
|
|
6929
|
+
offset2 = token.lastIndex;
|
|
6930
|
+
}
|
|
6931
|
+
if (!/^gh(?:\.exe)?$/i.test(tokens[0] || "")) return void 0;
|
|
6932
|
+
let repository;
|
|
6933
|
+
let resource;
|
|
6934
|
+
let operation;
|
|
6935
|
+
let positional;
|
|
6936
|
+
const canonical = (value) => {
|
|
6937
|
+
const match = /^([a-z0-9][a-z0-9.-]*\.[a-z0-9-]+)\/([a-z0-9][a-z0-9-]*)\/([a-z0-9_.-]+)$/i.exec(value);
|
|
6938
|
+
if (!match || /^(?:\.|\.\.)$/.test(match[3]) || /\.git$/i.test(match[3])) return void 0;
|
|
6939
|
+
return `${match[1]}/${match[2]}/${match[3]}`.toLowerCase();
|
|
6940
|
+
};
|
|
6941
|
+
for (let i2 = 1; i2 < tokens.length; i2++) {
|
|
6942
|
+
const value = tokens[i2];
|
|
6943
|
+
if (value === "-R" || value === "--repo" || value.startsWith("--repo=") || /^-R.+/.test(value)) {
|
|
6944
|
+
if (repository) return void 0;
|
|
6945
|
+
const selector = value === "-R" || value === "--repo" ? tokens[++i2] : value.replace(/^(?:--repo=|-R)/, "");
|
|
6946
|
+
repository = canonical(selector || "");
|
|
6947
|
+
if (!repository) return void 0;
|
|
6948
|
+
} else if (["--body", "-b", "--body-file", "-F", "--title", "-t"].includes(value)) {
|
|
6949
|
+
if (!tokens[++i2]) return void 0;
|
|
6950
|
+
} else if (/^--(?:body|body-file|title)=.+/.test(value)) {
|
|
6951
|
+
continue;
|
|
6952
|
+
} else if (["--edit-last", "--create-if-none", "--delete-last", "--yes"].includes(value)) {
|
|
6953
|
+
continue;
|
|
6954
|
+
} else if (value.startsWith("-")) {
|
|
6955
|
+
return void 0;
|
|
6956
|
+
} else if (!resource) resource = value;
|
|
6957
|
+
else if (!operation) operation = value;
|
|
6958
|
+
else if (!positional) positional = value;
|
|
6959
|
+
else return void 0;
|
|
6960
|
+
}
|
|
6961
|
+
if (!["pr", "issue"].includes(resource || "") || !["comment", "create", "edit"].includes(operation || "")) return void 0;
|
|
6962
|
+
if (positional?.startsWith("https://")) {
|
|
6963
|
+
const target = /^https:\/\/([^/]+)\/([^/]+)\/([^/]+)\/(pull|issues)\/[1-9][0-9]*\/?$/.exec(positional);
|
|
6964
|
+
if (!target || (resource === "pr" ? target[4] !== "pull" : target[4] !== "issues")) return void 0;
|
|
6965
|
+
const fromUrl = canonical(`${target[1]}/${target[2]}/${target[3]}`);
|
|
6966
|
+
if (!fromUrl || repository && repository !== fromUrl) return void 0;
|
|
6967
|
+
repository = fromUrl;
|
|
6968
|
+
} else if (positional && !/^[1-9][0-9]*$/.test(positional)) return void 0;
|
|
6969
|
+
return repository;
|
|
6970
|
+
}
|
|
6644
6971
|
function extractUrl(args, _argsText) {
|
|
6645
6972
|
const direct = args.url || args.uri || args.endpoint || args.webhookUrl || args.callbackUrl || args.href || args.link;
|
|
6646
6973
|
if (typeof direct === "string" && direct.trim()) return direct.trim();
|
|
6647
6974
|
for (const field of URL_ACTION_FIELDS) {
|
|
6648
6975
|
const raw = args[field];
|
|
6649
6976
|
if (typeof raw !== "string") continue;
|
|
6650
|
-
const
|
|
6651
|
-
const
|
|
6977
|
+
const shellField = ["command", "cmd", "script", "input"].includes(field);
|
|
6978
|
+
const value = shellField ? shellUrlActionText(raw) : raw;
|
|
6979
|
+
const firstUrl = (text) => text.match(/https?:\/\/[^\s"'<>]+/i)?.[0] || text.match(/\b(?:s3|gs|ftp|sftp|smb):\/\/[^\s"'<>]+/i)?.[0];
|
|
6980
|
+
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)) : [];
|
|
6981
|
+
const externalOutbound = outboundUrls.find((url) => {
|
|
6982
|
+
try {
|
|
6983
|
+
return !isInternalHost(new URL(url).hostname);
|
|
6984
|
+
} catch {
|
|
6985
|
+
return false;
|
|
6986
|
+
}
|
|
6987
|
+
});
|
|
6988
|
+
const found = externalOutbound || outboundUrls[0] || firstUrl(value);
|
|
6652
6989
|
if (found) return found;
|
|
6653
6990
|
}
|
|
6654
6991
|
return "";
|
|
@@ -7018,6 +7355,44 @@ function classifyShellCommandOperation(command, depth = 0) {
|
|
|
7018
7355
|
if (segments.length === 0) return "SHELL";
|
|
7019
7356
|
return worstShellOp(segments.map((segment) => classifyShellSegment(segment, depth)), "read");
|
|
7020
7357
|
}
|
|
7358
|
+
function provenCompoundDownload(command, detectedUrl) {
|
|
7359
|
+
const segments = splitShellSegments(command).filter((segment) => /\b(?:https?|s3|gs|ftp|sftp|smb):\/\//i.test(segment));
|
|
7360
|
+
if (!segments.length || !command.includes(detectedUrl)) return false;
|
|
7361
|
+
return segments.every((segment) => {
|
|
7362
|
+
if (/[$`{}()<>;&]/.test(segment)) return false;
|
|
7363
|
+
const pipeline = splitShellSegments(segment, true);
|
|
7364
|
+
if (pipeline.slice(1).some((part) => !/^(?:Select-Object|Format-Table|Format-List)\s+[\w., *-]+$|^Out-Null$/i.test(part))) return false;
|
|
7365
|
+
const raw = pipeline[0];
|
|
7366
|
+
const pieces = raw.match(/"[^"\r\n]*"|'[^'\r\n]*'|[^\s"']+/g) || [];
|
|
7367
|
+
if (pieces.join("").replace(/\s/g, "") !== raw.replace(/\s/g, "")) return false;
|
|
7368
|
+
const tokens = pieces.map((piece) => piece.replace(/^["']|["']$/g, ""));
|
|
7369
|
+
if (!/^(?:curl(?:\.exe)?|wget(?:\.exe)?|Invoke-RestMethod|Invoke-WebRequest|irm|iwr)$/i.test(tokens.shift() || "")) return false;
|
|
7370
|
+
let urls = 0;
|
|
7371
|
+
for (let i2 = 0; i2 < tokens.length; i2++) {
|
|
7372
|
+
const token = tokens[i2];
|
|
7373
|
+
if (/^https?:\/\//i.test(token)) {
|
|
7374
|
+
urls++;
|
|
7375
|
+
continue;
|
|
7376
|
+
}
|
|
7377
|
+
if (/^-Uri$/i.test(token) && /^https?:\/\//i.test(tokens[i2 + 1] || "")) {
|
|
7378
|
+
urls++;
|
|
7379
|
+
i2++;
|
|
7380
|
+
continue;
|
|
7381
|
+
}
|
|
7382
|
+
if (/^(?:-X|--request|-Method)$/.test(token) && /^(?:GET|HEAD|OPTIONS)$/i.test(tokens[i2 + 1] || "")) {
|
|
7383
|
+
i2++;
|
|
7384
|
+
continue;
|
|
7385
|
+
}
|
|
7386
|
+
if (/^-[sfSLI]+$|^--(?:silent|fail|location|head)$|^-UseBasicParsing$/.test(token)) continue;
|
|
7387
|
+
if (/^-TimeoutSec$/i.test(token) && /^\d+$/.test(tokens[i2 + 1] || "")) {
|
|
7388
|
+
i2++;
|
|
7389
|
+
continue;
|
|
7390
|
+
}
|
|
7391
|
+
return false;
|
|
7392
|
+
}
|
|
7393
|
+
return urls === 1;
|
|
7394
|
+
});
|
|
7395
|
+
}
|
|
7021
7396
|
function isInternalHost(hostname) {
|
|
7022
7397
|
const host = hostname.toLowerCase();
|
|
7023
7398
|
if (!host || host === "localhost" || host === "::1" || host.endsWith(".local") || host.endsWith(".internal")) return true;
|
|
@@ -7175,6 +7550,7 @@ function inferToolContext(toolName, args) {
|
|
|
7175
7550
|
let operation = toolName;
|
|
7176
7551
|
const toolNameLower = toolName.toLowerCase();
|
|
7177
7552
|
const argsText = stringifyArgs(args);
|
|
7553
|
+
const commandText = String(args.command || args.cmd || args.code || args.script || args.input || "");
|
|
7178
7554
|
const retryCount = Number(args.retryCount ?? args.retries ?? args.attempts ?? 0);
|
|
7179
7555
|
const fanoutCount = Number(args.fanoutCount ?? args.recipientCount ?? args.childCalls ?? (Array.isArray(args.recipients) ? args.recipients.length : 0));
|
|
7180
7556
|
const burstCount = Math.max(
|
|
@@ -7214,7 +7590,7 @@ function inferToolContext(toolName, args) {
|
|
|
7214
7590
|
}
|
|
7215
7591
|
}
|
|
7216
7592
|
if (operation === toolName && CODE_EXEC_TOOL_NAME_RE.test(nameWords)) {
|
|
7217
|
-
operation = classifyShellCommandOperation(
|
|
7593
|
+
operation = classifyShellCommandOperation(commandText);
|
|
7218
7594
|
}
|
|
7219
7595
|
const pathArg = args.path || args.filepath || args.file || args.filename || "";
|
|
7220
7596
|
if (typeof pathArg === "string" && pathArg.length > 0) {
|
|
@@ -7231,13 +7607,14 @@ function inferToolContext(toolName, args) {
|
|
|
7231
7607
|
operation = methodArg;
|
|
7232
7608
|
if (args.url) context.url = args.url;
|
|
7233
7609
|
}
|
|
7234
|
-
const
|
|
7610
|
+
const provenReadUrls = isCodeExecTool && !hasSecretLikeValue(argsText) ? [...provenInlineJavaScriptReadUrls(commandText), ...provenPowerShellQueryUrls(commandText)] : [];
|
|
7611
|
+
const detectedUrl = provenReadUrls[0] || extractUrl(args, argsText);
|
|
7235
7612
|
if (detectedUrl) {
|
|
7236
7613
|
context.url = detectedUrl;
|
|
7237
7614
|
try {
|
|
7238
7615
|
const parsed = new URL(detectedUrl);
|
|
7239
7616
|
const hostType = isInternalHost(parsed.hostname) ? "internal" : "external";
|
|
7240
|
-
const inbound = operation === "read" || ["GET", "HEAD", "OPTIONS", "SELECT", "SHOW", "DESCRIBE", "EXPLAIN"].includes(operation) ||
|
|
7617
|
+
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);
|
|
7241
7618
|
context["destination.domain"] = parsed.hostname;
|
|
7242
7619
|
context["url.risk"] = hostType;
|
|
7243
7620
|
if (inbound) {
|
|
@@ -7265,6 +7642,15 @@ function inferToolContext(toolName, args) {
|
|
|
7265
7642
|
context.amount = String(args.amount ?? args.price ?? args.total);
|
|
7266
7643
|
}
|
|
7267
7644
|
context["action.kind"] = isCodeExecTool || toolNameLower === "shell" ? "shell" : context.query !== void 0 && operation !== toolName && /^(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|TRUNCATE|GRANT|REVOKE|SHOW|DESCRIBE|EXPLAIN)$/.test(operation) ? "database" : ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"].includes(methodArg) ? "http" : context.path !== void 0 && (operation === "read" || operation === "write" || LOCAL_FILE_ACTION_RE.test(toolName) || FILE_REMOVAL_TOOL_RE.test(toolName)) ? "file" : "tool";
|
|
7645
|
+
delete context["destination.repository"];
|
|
7646
|
+
if (isCodeExecTool) {
|
|
7647
|
+
const repository = explicitGitHubWriteRepository(String(args.command || args.cmd || args.code || args.script || args.input || ""));
|
|
7648
|
+
if (repository) {
|
|
7649
|
+
context["destination.repository"] = repository;
|
|
7650
|
+
context["destination.domain"] = repository.split("/")[0];
|
|
7651
|
+
context["destination.type"] = isInternalHost(context["destination.domain"]) ? "internal" : "external";
|
|
7652
|
+
}
|
|
7653
|
+
}
|
|
7268
7654
|
return { operation, context };
|
|
7269
7655
|
}
|
|
7270
7656
|
function inferInventoryToolOperations(toolName, toolActions = []) {
|
package/dist/version.json
CHANGED