@node9/proxy 2.45.0 → 2.47.0
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/cli.js +8948 -8935
- package/dist/cli.mjs +8923 -8910
- package/dist/dashboard.mjs +685 -506
- package/dist/index.js +278 -180
- package/dist/index.mjs +279 -181
- package/dist/scan-ink.mjs +123 -25
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -64,22 +64,16 @@ var init_forecast_context = __esm({
|
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
// packages/policy-engine/dist/index.mjs
|
|
67
|
-
import mvdanSh3 from "mvdan-sh";
|
|
68
67
|
import picomatch from "picomatch";
|
|
69
68
|
import safeRegex from "safe-regex2";
|
|
70
69
|
import pm from "picomatch";
|
|
71
|
-
import mvdanSh from "mvdan-sh";
|
|
72
70
|
import mvdanSh2 from "mvdan-sh";
|
|
71
|
+
import mvdanSh from "mvdan-sh";
|
|
72
|
+
import mvdanSh3 from "mvdan-sh";
|
|
73
73
|
import safeRegex2 from "safe-regex2";
|
|
74
74
|
import { createHash as createHash2 } from "crypto";
|
|
75
75
|
import { createHash as createHash22 } from "crypto";
|
|
76
|
-
import mvdanSh4 from "mvdan-sh";
|
|
77
|
-
import mvdanSh5 from "mvdan-sh";
|
|
78
|
-
import mvdanSh6 from "mvdan-sh";
|
|
79
|
-
import mvdanSh8 from "mvdan-sh";
|
|
80
|
-
import mvdanSh7 from "mvdan-sh";
|
|
81
76
|
import { createHash as createHash3 } from "crypto";
|
|
82
|
-
import mvdanSh9 from "mvdan-sh";
|
|
83
77
|
import crypto from "crypto";
|
|
84
78
|
function validateRegex(pattern) {
|
|
85
79
|
if (!pattern) return "Pattern is required";
|
|
@@ -661,6 +655,62 @@ function resolveCheckMap(settings) {
|
|
|
661
655
|
for (const c of CHECKS) out[c.id] = checkValue(settings, c.id);
|
|
662
656
|
return out;
|
|
663
657
|
}
|
|
658
|
+
function isGoNode(v) {
|
|
659
|
+
return v !== null && typeof v === "object" && typeof v.Pos === "function";
|
|
660
|
+
}
|
|
661
|
+
function toPlainTree(goNode) {
|
|
662
|
+
const kind = syntax.NodeType(goNode);
|
|
663
|
+
const out = { kind, pos: goNode.Pos().Offset(), end: goNode.End().Offset() };
|
|
664
|
+
const spec = KINDS[kind];
|
|
665
|
+
if (!spec) {
|
|
666
|
+
unknownKinds.add(kind);
|
|
667
|
+
return out;
|
|
668
|
+
}
|
|
669
|
+
convertFields(goNode, spec, out, kind);
|
|
670
|
+
return out;
|
|
671
|
+
}
|
|
672
|
+
function convertFields(goNode, spec, out, kind) {
|
|
673
|
+
for (const f of spec.scalars ?? []) out[f] = goNode[f];
|
|
674
|
+
for (const f of spec.nodes ?? []) {
|
|
675
|
+
const v = goNode[f];
|
|
676
|
+
if (v === void 0 || v === null) {
|
|
677
|
+
out[f] = null;
|
|
678
|
+
} else if (Array.isArray(v)) {
|
|
679
|
+
out[f] = v.map((c) => isGoNode(c) ? toPlainTree(c) : c);
|
|
680
|
+
} else if (isGoNode(v)) {
|
|
681
|
+
out[f] = toPlainTree(v);
|
|
682
|
+
} else if (typeof v === "object" && STRUCTS[`${kind}.${f}`]) {
|
|
683
|
+
const sub = { struct: `${kind}.${f}` };
|
|
684
|
+
convertFields(v, STRUCTS[`${kind}.${f}`], sub, `${kind}.${f}`);
|
|
685
|
+
out[f] = sub;
|
|
686
|
+
} else {
|
|
687
|
+
out[f] = v;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
function childrenOf(n) {
|
|
692
|
+
const spec = n.kind ? KINDS[n.kind] : STRUCTS[n.struct];
|
|
693
|
+
if (!spec?.nodes) return [];
|
|
694
|
+
const out = [];
|
|
695
|
+
for (const f of spec.nodes) {
|
|
696
|
+
if (spec.notWalked?.includes(f)) continue;
|
|
697
|
+
const v = n[f];
|
|
698
|
+
if (!v) continue;
|
|
699
|
+
if (Array.isArray(v)) {
|
|
700
|
+
for (const c of v) if (c) out.push(c);
|
|
701
|
+
} else if (v.kind) {
|
|
702
|
+
out.push(v);
|
|
703
|
+
} else if (v.struct) {
|
|
704
|
+
out.push(...childrenOf(v));
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
return out;
|
|
708
|
+
}
|
|
709
|
+
function walk(root, fn) {
|
|
710
|
+
if (!root) return;
|
|
711
|
+
if (!fn(root)) return;
|
|
712
|
+
for (const c of childrenOf(root)) walk(c, fn);
|
|
713
|
+
}
|
|
664
714
|
function isInvisibleChar(char) {
|
|
665
715
|
return INVISIBLE_ONE.test(char);
|
|
666
716
|
}
|
|
@@ -816,7 +866,7 @@ function judgeInlineCode(language, code, isProtected) {
|
|
|
816
866
|
}
|
|
817
867
|
function isCatHeredocOrLit(part) {
|
|
818
868
|
if (!part) return false;
|
|
819
|
-
const t =
|
|
869
|
+
const t = part.kind;
|
|
820
870
|
if (t === "Lit") return true;
|
|
821
871
|
if (t !== "CmdSubst") return false;
|
|
822
872
|
const stmts = part.Stmts || [];
|
|
@@ -826,9 +876,9 @@ function isCatHeredocOrLit(part) {
|
|
|
826
876
|
const hasHeredoc = redirs.some((r) => r && r.Hdoc);
|
|
827
877
|
if (!hasHeredoc) return false;
|
|
828
878
|
const cmd = stmt.Cmd;
|
|
829
|
-
if (!cmd ||
|
|
879
|
+
if (!cmd || cmd.kind !== "CallExpr") return false;
|
|
830
880
|
const firstArg = cmd.Args?.[0]?.Parts || [];
|
|
831
|
-
if (firstArg.length !== 1 ||
|
|
881
|
+
if (firstArg.length !== 1 || firstArg[0].kind !== "Lit") return false;
|
|
832
882
|
return (firstArg[0].Value || "").toLowerCase() === "cat";
|
|
833
883
|
}
|
|
834
884
|
function parseShared(command) {
|
|
@@ -840,7 +890,7 @@ function parseShared(command) {
|
|
|
840
890
|
}
|
|
841
891
|
let parsed;
|
|
842
892
|
try {
|
|
843
|
-
parsed = sharedParser.Parse(command, "cmd");
|
|
893
|
+
parsed = toPlainTree(sharedParser.Parse(command, "cmd"));
|
|
844
894
|
} catch {
|
|
845
895
|
parsed = PARSE_FAIL;
|
|
846
896
|
}
|
|
@@ -901,10 +951,10 @@ function fileAccessViewImpl(command) {
|
|
|
901
951
|
const at = (byteOffset) => toCharIndex === null ? byteOffset : toCharIndex(byteOffset);
|
|
902
952
|
const edits = [];
|
|
903
953
|
try {
|
|
904
|
-
|
|
954
|
+
walk(f, (node) => {
|
|
905
955
|
if (!node) return false;
|
|
906
956
|
const n = node;
|
|
907
|
-
if (
|
|
957
|
+
if (n.kind !== "CallExpr") return true;
|
|
908
958
|
const args = n.Args || [];
|
|
909
959
|
const words = args.map((a) => resolveWordLiteral(a));
|
|
910
960
|
const head = unwrapCommandHead(words);
|
|
@@ -916,8 +966,8 @@ function fileAccessViewImpl(command) {
|
|
|
916
966
|
if (!PROGRAM_DIR.test(word)) continue;
|
|
917
967
|
const name = word.split(/[\\/]/).pop() ?? "";
|
|
918
968
|
if (!name) continue;
|
|
919
|
-
const s = at(args[i].
|
|
920
|
-
const e = at(args[i].
|
|
969
|
+
const s = at(args[i].pos);
|
|
970
|
+
const e = at(args[i].end);
|
|
921
971
|
if (s < 0 || e < 0) continue;
|
|
922
972
|
edits.push([s, e, name]);
|
|
923
973
|
}
|
|
@@ -945,10 +995,10 @@ function literalPayloadsOf(command, depth) {
|
|
|
945
995
|
if (f === PARSE_FAIL) return [];
|
|
946
996
|
const out = [];
|
|
947
997
|
try {
|
|
948
|
-
|
|
998
|
+
walk(f, (node) => {
|
|
949
999
|
if (!node || out.length >= MAX_READING_PAYLOADS) return false;
|
|
950
1000
|
const n = node;
|
|
951
|
-
if (
|
|
1001
|
+
if (n.kind !== "CallExpr") return true;
|
|
952
1002
|
const words = (n.Args || []).map((a) => resolveWordLiteral(a));
|
|
953
1003
|
if (words.length === 0) return true;
|
|
954
1004
|
const payload = literalShellPayload(words, baseWord(words[0]));
|
|
@@ -968,23 +1018,23 @@ function foldCommandWords(reading) {
|
|
|
968
1018
|
const at = (byteOffset) => toCharIndex === null ? byteOffset : toCharIndex(byteOffset);
|
|
969
1019
|
const edits = [];
|
|
970
1020
|
try {
|
|
971
|
-
|
|
1021
|
+
walk(f, (node) => {
|
|
972
1022
|
if (!node) return false;
|
|
973
1023
|
const n = node;
|
|
974
|
-
if (
|
|
1024
|
+
if (n.kind !== "CallExpr") return true;
|
|
975
1025
|
const args = n.Args || [];
|
|
976
1026
|
const words = args.map((a) => resolveWordLiteral(a));
|
|
977
1027
|
const folded = words.map((w) => w === null ? null : foldHomoglyphs(w));
|
|
978
1028
|
const head = unwrapCommandHead(folded);
|
|
979
1029
|
for (let i = 0; i <= head && i < args.length; i++) {
|
|
980
1030
|
const parts = args[i].Parts || [];
|
|
981
|
-
if (parts.length !== 1 ||
|
|
1031
|
+
if (parts.length !== 1 || parts[0].kind !== "Lit") continue;
|
|
982
1032
|
const value = parts[0].Value ?? "";
|
|
983
1033
|
const word = folded[i];
|
|
984
1034
|
if (word === null || word === value || !/[^\x00-\x7f]/.test(value)) continue;
|
|
985
1035
|
if (!/^[A-Za-z0-9._/-]+$/.test(word)) continue;
|
|
986
|
-
const s = at(args[i].
|
|
987
|
-
const e = at(args[i].
|
|
1036
|
+
const s = at(args[i].pos);
|
|
1037
|
+
const e = at(args[i].end);
|
|
988
1038
|
if (s < 0 || e < 0) continue;
|
|
989
1039
|
edits.push([s, e, word]);
|
|
990
1040
|
}
|
|
@@ -1021,10 +1071,10 @@ function interpreterHeredocSpans(f, at) {
|
|
|
1021
1071
|
const stmts = f?.Stmts || [];
|
|
1022
1072
|
const candidates = [];
|
|
1023
1073
|
let unsafe = false;
|
|
1024
|
-
|
|
1074
|
+
walk(f, (node) => {
|
|
1025
1075
|
if (!node || unsafe) return false;
|
|
1026
1076
|
const n = node;
|
|
1027
|
-
const t =
|
|
1077
|
+
const t = n.kind;
|
|
1028
1078
|
if (t === "FuncDecl") {
|
|
1029
1079
|
unsafe = true;
|
|
1030
1080
|
return false;
|
|
@@ -1044,7 +1094,7 @@ function interpreterHeredocSpans(f, at) {
|
|
|
1044
1094
|
});
|
|
1045
1095
|
if (unsafe) return [];
|
|
1046
1096
|
for (const st of stmts) {
|
|
1047
|
-
if (!st?.Cmd ||
|
|
1097
|
+
if (!st?.Cmd || st.Cmd.kind !== "CallExpr") continue;
|
|
1048
1098
|
const words = (st.Cmd.Args || []).map((a) => resolveWordLiteral(a));
|
|
1049
1099
|
const name = (words[0] ?? "").toLowerCase();
|
|
1050
1100
|
if (!STDIN_CODE_INTERPRETERS.test(name)) continue;
|
|
@@ -1059,8 +1109,8 @@ function interpreterHeredocSpans(f, at) {
|
|
|
1059
1109
|
if (stdin.length !== 1 || others > 0) continue;
|
|
1060
1110
|
const r = stdin[0];
|
|
1061
1111
|
if (heredocText(r.Hdoc) === null) continue;
|
|
1062
|
-
const hs = at(r.Hdoc.
|
|
1063
|
-
const he = at(r.Hdoc.
|
|
1112
|
+
const hs = at(r.Hdoc.pos);
|
|
1113
|
+
const he = at(r.Hdoc.end);
|
|
1064
1114
|
if (hs < 0 || he < 0 || he <= hs) continue;
|
|
1065
1115
|
spans.push([hs, he]);
|
|
1066
1116
|
}
|
|
@@ -1080,24 +1130,24 @@ function normalizeCommandForPolicyImpl(command, codeBodies) {
|
|
|
1080
1130
|
strips.push([hs, he]);
|
|
1081
1131
|
msgSpans.add(`${hs}:${he}`);
|
|
1082
1132
|
}
|
|
1083
|
-
|
|
1133
|
+
walk(f, (node) => {
|
|
1084
1134
|
if (!node) return false;
|
|
1085
1135
|
const n = node;
|
|
1086
|
-
if (
|
|
1136
|
+
if (n.kind !== "CallExpr") return true;
|
|
1087
1137
|
const args = n.Args || [];
|
|
1088
1138
|
for (let i = 0; i < args.length - 1; i++) {
|
|
1089
1139
|
const argParts = args[i].Parts || [];
|
|
1090
|
-
if (argParts.length !== 1 ||
|
|
1140
|
+
if (argParts.length !== 1 || argParts[0].kind !== "Lit") continue;
|
|
1091
1141
|
const flagVal = argParts[0].Value || "";
|
|
1092
1142
|
if (!MESSAGE_FLAGS.has(flagVal.toLowerCase())) continue;
|
|
1093
1143
|
const next = args[i + 1];
|
|
1094
1144
|
const nextParts = next.Parts || [];
|
|
1095
1145
|
if (nextParts.length !== 1) continue;
|
|
1096
1146
|
const quotedNode = nextParts[0];
|
|
1097
|
-
const nt =
|
|
1147
|
+
const nt = quotedNode.kind;
|
|
1098
1148
|
const markStrip = () => {
|
|
1099
|
-
const s = at(next.
|
|
1100
|
-
const e = at(next.
|
|
1149
|
+
const s = at(next.pos);
|
|
1150
|
+
const e = at(next.end);
|
|
1101
1151
|
if (s < 0 || e < 0) return;
|
|
1102
1152
|
strips.push([s, e]);
|
|
1103
1153
|
msgSpans.add(`${s}:${e}`);
|
|
@@ -1106,7 +1156,7 @@ function normalizeCommandForPolicyImpl(command, codeBodies) {
|
|
|
1106
1156
|
markStrip();
|
|
1107
1157
|
} else if (nt === "DblQuoted") {
|
|
1108
1158
|
const innerParts = quotedNode.Parts || [];
|
|
1109
|
-
const allLit = innerParts.length === 0 || innerParts.every((p) =>
|
|
1159
|
+
const allLit = innerParts.length === 0 || innerParts.every((p) => p.kind === "Lit");
|
|
1110
1160
|
if (allLit) {
|
|
1111
1161
|
markStrip();
|
|
1112
1162
|
} else if (innerParts.every((p) => isCatHeredocOrLit(p))) {
|
|
@@ -1115,8 +1165,8 @@ function normalizeCommandForPolicyImpl(command, codeBodies) {
|
|
|
1115
1165
|
}
|
|
1116
1166
|
}
|
|
1117
1167
|
for (const arg of args) {
|
|
1118
|
-
const s = at(arg.
|
|
1119
|
-
const e = at(arg.
|
|
1168
|
+
const s = at(arg.pos);
|
|
1169
|
+
const e = at(arg.end);
|
|
1120
1170
|
if (s < 0 || e < 0) continue;
|
|
1121
1171
|
if (msgSpans.has(`${s}:${e}`)) continue;
|
|
1122
1172
|
const resolved = resolveWordLiteral(arg);
|
|
@@ -1156,10 +1206,10 @@ function scanArgsForDynamicExec(args, startIdx) {
|
|
|
1156
1206
|
let hasParamExp = false;
|
|
1157
1207
|
let hasCurl = false;
|
|
1158
1208
|
for (let i = startIdx; i < args.length; i++) {
|
|
1159
|
-
|
|
1209
|
+
walk(args[i], (inner) => {
|
|
1160
1210
|
if (!inner) return false;
|
|
1161
1211
|
const inn = inner;
|
|
1162
|
-
const it =
|
|
1212
|
+
const it = inn.kind;
|
|
1163
1213
|
if (it === "CmdSubst") hasCmdSubst = true;
|
|
1164
1214
|
if (it === "ParamExp") hasParamExp = true;
|
|
1165
1215
|
if (it === "Lit" && DOWNLOAD_CMDS.has(inn.Value?.toLowerCase())) hasCurl = true;
|
|
@@ -1172,15 +1222,16 @@ function scanArgsForDynamicExec(args, startIdx) {
|
|
|
1172
1222
|
}
|
|
1173
1223
|
function detectDangerousShellExec(command) {
|
|
1174
1224
|
try {
|
|
1175
|
-
const f =
|
|
1225
|
+
const f = parseShared(command);
|
|
1226
|
+
if (f === PARSE_FAIL) return null;
|
|
1176
1227
|
let result = null;
|
|
1177
1228
|
const assigned = assignedNames(f);
|
|
1178
|
-
|
|
1229
|
+
walk(f, (node) => {
|
|
1179
1230
|
if (!node || result === "block") return false;
|
|
1180
1231
|
const n = node;
|
|
1181
|
-
if (
|
|
1232
|
+
if (n.kind === "BinaryCmd" && n.Y && !listOps().has(n.Op)) {
|
|
1182
1233
|
const call = n.Y.Cmd;
|
|
1183
|
-
if (call &&
|
|
1234
|
+
if (call && call.kind === "CallExpr") {
|
|
1184
1235
|
const words = (call.Args || []).map((a) => resolveWordLiteral(a));
|
|
1185
1236
|
const head2 = unwrapCommandHead(words);
|
|
1186
1237
|
const name = (words[head2] ?? "").split("/").pop() ?? "";
|
|
@@ -1193,7 +1244,7 @@ function detectDangerousShellExec(command) {
|
|
|
1193
1244
|
result = "review";
|
|
1194
1245
|
}
|
|
1195
1246
|
}
|
|
1196
|
-
if (
|
|
1247
|
+
if (n.kind !== "CallExpr") return true;
|
|
1197
1248
|
const args = n.Args || [];
|
|
1198
1249
|
if (args.length === 0) return true;
|
|
1199
1250
|
const headWords = args.map(
|
|
@@ -1204,7 +1255,7 @@ function detectDangerousShellExec(command) {
|
|
|
1204
1255
|
if (!lookup && (opaqueExecutable(args[0], assigned) || opaqueExecutable(args[head], assigned)))
|
|
1205
1256
|
result = "review";
|
|
1206
1257
|
const firstParts = args[0].Parts || [];
|
|
1207
|
-
if (firstParts.length !== 1 ||
|
|
1258
|
+
if (firstParts.length !== 1 || firstParts[0].kind !== "Lit") return true;
|
|
1208
1259
|
const cmdName = firstParts[0].Value?.toLowerCase() ?? "";
|
|
1209
1260
|
if (cmdName === "eval") {
|
|
1210
1261
|
const v = scanArgsForDynamicExec(args, 1);
|
|
@@ -1212,7 +1263,7 @@ function detectDangerousShellExec(command) {
|
|
|
1212
1263
|
} else if (SHELL_INTERPRETERS.has(cmdName)) {
|
|
1213
1264
|
for (let i = 1; i < args.length - 1; i++) {
|
|
1214
1265
|
const flagParts = args[i].Parts || [];
|
|
1215
|
-
if (flagParts.length !== 1 ||
|
|
1266
|
+
if (flagParts.length !== 1 || flagParts[0].kind !== "Lit" || flagParts[0].Value !== "-c")
|
|
1216
1267
|
continue;
|
|
1217
1268
|
const v = scanArgsForDynamicExec(args, i + 1);
|
|
1218
1269
|
if (v === "block" || v === "review" && result === null) result = v;
|
|
@@ -1236,8 +1287,7 @@ function isDefaultExpansion(part) {
|
|
|
1236
1287
|
}
|
|
1237
1288
|
function lookupSubstitution(part) {
|
|
1238
1289
|
const stmts = part.Stmts ?? [];
|
|
1239
|
-
if (stmts.length !== 1 || !stmts[0].Cmd ||
|
|
1240
|
-
return null;
|
|
1290
|
+
if (stmts.length !== 1 || !stmts[0].Cmd || stmts[0].Cmd.kind !== "CallExpr") return null;
|
|
1241
1291
|
const words = (stmts[0].Cmd.Args ?? []).map((a) => resolveWordLiteral(a));
|
|
1242
1292
|
if (words.some((w) => w === null)) return null;
|
|
1243
1293
|
if (words.length === 2 && words[0] === "which") return words[1];
|
|
@@ -1247,9 +1297,9 @@ function lookupSubstitution(part) {
|
|
|
1247
1297
|
}
|
|
1248
1298
|
function assignedNames(f) {
|
|
1249
1299
|
const names = /* @__PURE__ */ new Set();
|
|
1250
|
-
|
|
1300
|
+
walk(f, (node) => {
|
|
1251
1301
|
if (!node) return false;
|
|
1252
|
-
const kind =
|
|
1302
|
+
const kind = node.kind;
|
|
1253
1303
|
if (kind === "Assign" && node.Name?.Value) names.add(node.Name.Value);
|
|
1254
1304
|
if (kind === "ForClause" && node.Loop?.Name?.Value) names.add(node.Loop.Name.Value);
|
|
1255
1305
|
if (kind === "CallExpr") {
|
|
@@ -1264,7 +1314,7 @@ function assignedNames(f) {
|
|
|
1264
1314
|
}
|
|
1265
1315
|
function executableParts(word, assigned, quoted = false) {
|
|
1266
1316
|
return (word?.Parts ?? []).flatMap((part) => {
|
|
1267
|
-
const kind =
|
|
1317
|
+
const kind = part.kind;
|
|
1268
1318
|
if (kind === "DblQuoted") return executableParts(part, assigned, true);
|
|
1269
1319
|
if (kind === "Lit") return [{ kind: "Lit", value: part.Value ?? "", quoted }];
|
|
1270
1320
|
if (kind === "SglQuoted")
|
|
@@ -1332,10 +1382,10 @@ function chmodHasOpenPermMode(command) {
|
|
|
1332
1382
|
if (f === PARSE_FAIL) return false;
|
|
1333
1383
|
let found = false;
|
|
1334
1384
|
try {
|
|
1335
|
-
|
|
1385
|
+
walk(f, (node) => {
|
|
1336
1386
|
if (!node || found) return false;
|
|
1337
1387
|
const n = node;
|
|
1338
|
-
if (
|
|
1388
|
+
if (n.kind !== "CallExpr") return true;
|
|
1339
1389
|
const words = (n.Args || []).map((a) => resolveWordLiteral(a));
|
|
1340
1390
|
if (words.length === 0) return true;
|
|
1341
1391
|
const name = (words[0] ?? "").toLowerCase();
|
|
@@ -1388,11 +1438,11 @@ function redirStdinOps() {
|
|
|
1388
1438
|
}
|
|
1389
1439
|
function deriveBinaryOp(sample) {
|
|
1390
1440
|
try {
|
|
1391
|
-
const f = sharedParser.Parse(sample, "cmd");
|
|
1441
|
+
const f = toPlainTree(sharedParser.Parse(sample, "cmd"));
|
|
1392
1442
|
let op = -1;
|
|
1393
|
-
|
|
1443
|
+
walk(f, (node) => {
|
|
1394
1444
|
const n = node;
|
|
1395
|
-
if (n &&
|
|
1445
|
+
if (n && n.kind === "BinaryCmd" && op < 0) op = n.Op;
|
|
1396
1446
|
return true;
|
|
1397
1447
|
});
|
|
1398
1448
|
return op;
|
|
@@ -1466,7 +1516,7 @@ function heredocText(word) {
|
|
|
1466
1516
|
const parts = word?.Parts ?? [];
|
|
1467
1517
|
let out = "";
|
|
1468
1518
|
for (const p of parts) {
|
|
1469
|
-
if (
|
|
1519
|
+
if (p.kind !== "Lit") return null;
|
|
1470
1520
|
out += p.Value ?? "";
|
|
1471
1521
|
}
|
|
1472
1522
|
return out;
|
|
@@ -1502,7 +1552,7 @@ function attachedCode(interp, flag) {
|
|
|
1502
1552
|
}
|
|
1503
1553
|
function inlineExecCall(stmt, pipeFed) {
|
|
1504
1554
|
const cmd = stmt?.Cmd;
|
|
1505
|
-
if (!cmd ||
|
|
1555
|
+
if (!cmd || cmd.kind !== "CallExpr") return null;
|
|
1506
1556
|
const words = (cmd.Args || []).map((a) => resolveWordLiteral(a));
|
|
1507
1557
|
if (words.length === 0) return null;
|
|
1508
1558
|
const headIdx = unwrapCommandHead(words);
|
|
@@ -1583,10 +1633,10 @@ function shellPayloadNeedsReview(payload) {
|
|
|
1583
1633
|
let review = false;
|
|
1584
1634
|
payloadNesting++;
|
|
1585
1635
|
try {
|
|
1586
|
-
|
|
1636
|
+
walk(f, (node) => {
|
|
1587
1637
|
if (!node || review) return false;
|
|
1588
1638
|
const n = node;
|
|
1589
|
-
if (
|
|
1639
|
+
if (n.kind !== "CallExpr") return true;
|
|
1590
1640
|
const words = (n.Args || []).map((a) => resolveWordLiteral(a));
|
|
1591
1641
|
if (words.length === 0) return true;
|
|
1592
1642
|
const h = unwrapCommandHead(words);
|
|
@@ -1620,10 +1670,10 @@ function inlineExecCalls(command) {
|
|
|
1620
1670
|
if (f === PARSE_FAIL) return PARSE_FAIL;
|
|
1621
1671
|
const calls = [];
|
|
1622
1672
|
try {
|
|
1623
|
-
|
|
1673
|
+
walk(f, (node) => {
|
|
1624
1674
|
if (!node || calls.length >= MAX_INLINE_CALLS) return false;
|
|
1625
1675
|
const n = node;
|
|
1626
|
-
const t =
|
|
1676
|
+
const t = n.kind;
|
|
1627
1677
|
if (t === "BinaryCmd" && n.Y && !listOps().has(n.Op)) {
|
|
1628
1678
|
const call = inlineExecCall(n.Y, true);
|
|
1629
1679
|
if (call) calls.push(call);
|
|
@@ -1758,7 +1808,7 @@ function shellScriptOperands(command) {
|
|
|
1758
1808
|
walkWithCalls(f, (node) => {
|
|
1759
1809
|
if (!node) return false;
|
|
1760
1810
|
const n = node;
|
|
1761
|
-
if (
|
|
1811
|
+
if (n.kind !== "CallExpr") return true;
|
|
1762
1812
|
const callArgs = n.Args || [];
|
|
1763
1813
|
if (callArgs.length === 0) return true;
|
|
1764
1814
|
const words = callArgs.map((a) => resolveWordLiteral(a));
|
|
@@ -1794,7 +1844,7 @@ function shellScriptOperands(command) {
|
|
|
1794
1844
|
}
|
|
1795
1845
|
function windowsDrivePathSpelling(word) {
|
|
1796
1846
|
const parts = word?.Parts || [];
|
|
1797
|
-
if (parts.length === 0 || !parts.every((p) =>
|
|
1847
|
+
if (parts.length === 0 || !parts.every((p) => p.kind === "Lit")) return null;
|
|
1798
1848
|
const raw = parts.map((p) => p.Value ?? "").join("");
|
|
1799
1849
|
return /^[A-Za-z]:\\[^\s'"$`]*$/.test(raw) ? raw : null;
|
|
1800
1850
|
}
|
|
@@ -1832,15 +1882,15 @@ function recordTopLevelAssignments(f) {
|
|
|
1832
1882
|
}
|
|
1833
1883
|
function probeBinOp(src) {
|
|
1834
1884
|
try {
|
|
1835
|
-
const cmd =
|
|
1836
|
-
if (cmd &&
|
|
1885
|
+
const cmd = toPlainTree(syntax2.NewParser().Parse(src, "probe"))?.Stmts?.[0]?.Cmd;
|
|
1886
|
+
if (cmd && cmd.kind === "BinaryCmd") return cmd.Op;
|
|
1837
1887
|
} catch {
|
|
1838
1888
|
}
|
|
1839
1889
|
return null;
|
|
1840
1890
|
}
|
|
1841
1891
|
function recordTopLevelStmt(stmt) {
|
|
1842
1892
|
if (!stmt || !stmt.Cmd) return false;
|
|
1843
|
-
const t =
|
|
1893
|
+
const t = stmt.Cmd.kind;
|
|
1844
1894
|
if (t === "BinaryCmd") {
|
|
1845
1895
|
if (!AND_OR_OPS.has(stmt.Cmd.Op)) return false;
|
|
1846
1896
|
if (!recordTopLevelStmt(stmt.Cmd.X)) return false;
|
|
@@ -1850,7 +1900,7 @@ function recordTopLevelStmt(stmt) {
|
|
|
1850
1900
|
if (t !== "CallExpr" && t !== "DeclClause") return false;
|
|
1851
1901
|
let at = 0;
|
|
1852
1902
|
try {
|
|
1853
|
-
at = stmt.
|
|
1903
|
+
at = stmt.pos;
|
|
1854
1904
|
} catch {
|
|
1855
1905
|
at = 0;
|
|
1856
1906
|
}
|
|
@@ -1861,14 +1911,14 @@ function recordTopLevelStmt(stmt) {
|
|
|
1861
1911
|
}
|
|
1862
1912
|
function recordAssignments(n, at) {
|
|
1863
1913
|
if (!assignmentTable) return;
|
|
1864
|
-
const t =
|
|
1914
|
+
const t = n.kind;
|
|
1865
1915
|
let assigns = [];
|
|
1866
1916
|
if (t === "CallExpr") {
|
|
1867
1917
|
if ((n.Args || []).length > 0) return;
|
|
1868
1918
|
assigns = n.Assigns || [];
|
|
1869
1919
|
} else if (t === "DeclClause") {
|
|
1870
1920
|
if (!ASSIGNMENT_HEADS.has(n.Variant?.Value ?? "")) return;
|
|
1871
|
-
assigns = (n.Args || []).filter((a) =>
|
|
1921
|
+
assigns = (n.Args || []).filter((a) => a.kind === "Assign");
|
|
1872
1922
|
} else return;
|
|
1873
1923
|
for (const a of assigns) {
|
|
1874
1924
|
const name = a?.Name?.Value;
|
|
@@ -1877,13 +1927,13 @@ function recordAssignments(n, at) {
|
|
|
1877
1927
|
}
|
|
1878
1928
|
}
|
|
1879
1929
|
function resolveTrivialSubst(part) {
|
|
1880
|
-
if (
|
|
1930
|
+
if (part.kind !== "CmdSubst") return void 0;
|
|
1881
1931
|
const stmts = part.Stmts || [];
|
|
1882
1932
|
if (stmts.length !== 1) return void 0;
|
|
1883
1933
|
const st = stmts[0];
|
|
1884
1934
|
if ((st.Redirs || []).length > 0 || st.Negated || st.Background) return void 0;
|
|
1885
1935
|
const cmd = st.Cmd;
|
|
1886
|
-
if (!cmd ||
|
|
1936
|
+
if (!cmd || cmd.kind !== "CallExpr") return void 0;
|
|
1887
1937
|
if ((cmd.Assigns || []).length > 0) return void 0;
|
|
1888
1938
|
const words = (cmd.Args || []).map((a) => resolveWordLiteral(a));
|
|
1889
1939
|
if (words.length === 0 || words.some((w) => w === null)) return void 0;
|
|
@@ -1908,7 +1958,7 @@ function recordedExpansion(name) {
|
|
|
1908
1958
|
return rec.value;
|
|
1909
1959
|
}
|
|
1910
1960
|
function isPlainParam(p) {
|
|
1911
|
-
if (
|
|
1961
|
+
if (p.kind !== "ParamExp") return false;
|
|
1912
1962
|
return !(p.Excl || p.Length || p.Width || p.Index || p.Slice || p.Repl || p.Exp);
|
|
1913
1963
|
}
|
|
1914
1964
|
function expandPlainParam(p) {
|
|
@@ -1920,9 +1970,9 @@ function expandPlainParam(p) {
|
|
|
1920
1970
|
}
|
|
1921
1971
|
function functionBodies(f) {
|
|
1922
1972
|
const out = /* @__PURE__ */ new Map();
|
|
1923
|
-
|
|
1973
|
+
walk(f, (node) => {
|
|
1924
1974
|
const n = node;
|
|
1925
|
-
if (n &&
|
|
1975
|
+
if (n && n.kind === "FuncDecl" && n.Name?.Value && n.Body) {
|
|
1926
1976
|
const list = out.get(n.Name.Value) ?? [];
|
|
1927
1977
|
list.push(n.Body);
|
|
1928
1978
|
out.set(n.Name.Value, list);
|
|
@@ -1933,10 +1983,10 @@ function functionBodies(f) {
|
|
|
1933
1983
|
}
|
|
1934
1984
|
function rebindsPositionals(body) {
|
|
1935
1985
|
let found = false;
|
|
1936
|
-
|
|
1986
|
+
walk(body, (node) => {
|
|
1937
1987
|
const n = node;
|
|
1938
1988
|
if (found || !n) return false;
|
|
1939
|
-
if (
|
|
1989
|
+
if (n.kind === "CallExpr") {
|
|
1940
1990
|
const words = (n.Args || []).map((a) => resolveWordLiteral(a));
|
|
1941
1991
|
if (words[0] === "shift") found = true;
|
|
1942
1992
|
else if (words[0] === "set" && words.slice(1).some((w) => w === null || w === "--" || !/^[-+]/.test(w)))
|
|
@@ -1956,7 +2006,7 @@ function tableBindings() {
|
|
|
1956
2006
|
function walkWithCalls(f, visit) {
|
|
1957
2007
|
const bodies = functionBodies(f);
|
|
1958
2008
|
if (bodies.size === 0) {
|
|
1959
|
-
|
|
2009
|
+
walk(f, visit);
|
|
1960
2010
|
return;
|
|
1961
2011
|
}
|
|
1962
2012
|
const seenCalls = /* @__PURE__ */ new Set();
|
|
@@ -1990,8 +2040,8 @@ function walkWithCalls(f, visit) {
|
|
|
1990
2040
|
currentStmtOffset = Number.MAX_SAFE_INTEGER;
|
|
1991
2041
|
callDepth++;
|
|
1992
2042
|
try {
|
|
1993
|
-
if (body.Cmd &&
|
|
1994
|
-
|
|
2043
|
+
if (body.Cmd && body.Cmd.kind === "Block") recordTopLevelAssignments(body.Cmd);
|
|
2044
|
+
walk(body, step);
|
|
1995
2045
|
} finally {
|
|
1996
2046
|
callDepth--;
|
|
1997
2047
|
assignmentTable = outerTable;
|
|
@@ -2002,10 +2052,10 @@ function walkWithCalls(f, visit) {
|
|
|
2002
2052
|
const step = (node) => {
|
|
2003
2053
|
if (!node) return false;
|
|
2004
2054
|
const go = visit(node);
|
|
2005
|
-
if (go !== false &&
|
|
2055
|
+
if (go !== false && node.kind === "CallExpr") inline(node);
|
|
2006
2056
|
return go;
|
|
2007
2057
|
};
|
|
2008
|
-
|
|
2058
|
+
walk(f, step);
|
|
2009
2059
|
}
|
|
2010
2060
|
function withAssignments(f, body) {
|
|
2011
2061
|
const outerTable = assignmentTable;
|
|
@@ -2030,7 +2080,7 @@ function walkCommandScope(command, visit) {
|
|
|
2030
2080
|
if (f === PARSE_FAIL) return "parse-fail";
|
|
2031
2081
|
withAssignments(f, () => {
|
|
2032
2082
|
walkWithCalls(f, (node) => {
|
|
2033
|
-
if (node &&
|
|
2083
|
+
if (node && node.kind === "Stmt") trackStatement(node);
|
|
2034
2084
|
return visit(node);
|
|
2035
2085
|
});
|
|
2036
2086
|
});
|
|
@@ -2038,7 +2088,7 @@ function walkCommandScope(command, visit) {
|
|
|
2038
2088
|
}
|
|
2039
2089
|
function trackStatement(n) {
|
|
2040
2090
|
try {
|
|
2041
|
-
currentStmtOffset = n.
|
|
2091
|
+
currentStmtOffset = n.pos;
|
|
2042
2092
|
} catch {
|
|
2043
2093
|
currentStmtOffset = Number.MAX_SAFE_INTEGER;
|
|
2044
2094
|
}
|
|
@@ -2054,11 +2104,11 @@ function resolveWordLiteral(w) {
|
|
|
2054
2104
|
return s;
|
|
2055
2105
|
}
|
|
2056
2106
|
function resolveWordPrefix(w) {
|
|
2057
|
-
const
|
|
2107
|
+
const walk3 = (parts) => {
|
|
2058
2108
|
let s = "";
|
|
2059
2109
|
for (const p of parts) {
|
|
2060
2110
|
if (p && typeof p === "object" && Array.isArray(p.Parts) && p.Dollar !== void 0) {
|
|
2061
|
-
const inner =
|
|
2111
|
+
const inner = walk3(p.Parts);
|
|
2062
2112
|
s += inner.prefix;
|
|
2063
2113
|
if (inner.dynamic) return { prefix: s, dynamic: true };
|
|
2064
2114
|
continue;
|
|
@@ -2069,10 +2119,10 @@ function resolveWordPrefix(w) {
|
|
|
2069
2119
|
}
|
|
2070
2120
|
return { prefix: s, dynamic: false };
|
|
2071
2121
|
};
|
|
2072
|
-
return
|
|
2122
|
+
return walk3(w?.Parts || []);
|
|
2073
2123
|
}
|
|
2074
2124
|
function resolvePart(p, inQuotes) {
|
|
2075
|
-
const t =
|
|
2125
|
+
const t = p.kind;
|
|
2076
2126
|
if (t === "Lit") {
|
|
2077
2127
|
const raw = p.Value ?? "";
|
|
2078
2128
|
if (!inQuotes) return raw.replace(/\\(.)/g, "$1");
|
|
@@ -2168,7 +2218,7 @@ function extractShellDestinations(command) {
|
|
|
2168
2218
|
() => walkWithCalls(f, (node) => {
|
|
2169
2219
|
if (!node) return false;
|
|
2170
2220
|
const n = node;
|
|
2171
|
-
if (
|
|
2221
|
+
if (n.kind === "Stmt") {
|
|
2172
2222
|
trackStatement(n);
|
|
2173
2223
|
for (const raw of redirectWords(n)) {
|
|
2174
2224
|
const host2 = devNetRedirectHost(raw);
|
|
@@ -2180,7 +2230,7 @@ function extractShellDestinations(command) {
|
|
|
2180
2230
|
out.push({ host: host2, binary, raw });
|
|
2181
2231
|
}
|
|
2182
2232
|
}
|
|
2183
|
-
if (
|
|
2233
|
+
if (n.kind !== "CallExpr") return true;
|
|
2184
2234
|
const callArgs = n.Args || [];
|
|
2185
2235
|
if (callArgs.length === 0) return true;
|
|
2186
2236
|
const words = callArgs.map((a) => resolveWordLiteral(a));
|
|
@@ -2215,7 +2265,7 @@ function extractShellDestTokens(command) {
|
|
|
2215
2265
|
() => walkWithCalls(f, (node) => {
|
|
2216
2266
|
if (!node) return false;
|
|
2217
2267
|
const n = node;
|
|
2218
|
-
if (
|
|
2268
|
+
if (n.kind === "Stmt") {
|
|
2219
2269
|
trackStatement(n);
|
|
2220
2270
|
for (const raw of redirectWords(n)) {
|
|
2221
2271
|
const host2 = devNetRedirectHost(raw);
|
|
@@ -2227,7 +2277,7 @@ function extractShellDestTokens(command) {
|
|
|
2227
2277
|
out.push({ token: host2, binary });
|
|
2228
2278
|
}
|
|
2229
2279
|
}
|
|
2230
|
-
if (
|
|
2280
|
+
if (n.kind !== "CallExpr") return true;
|
|
2231
2281
|
const callArgs = n.Args || [];
|
|
2232
2282
|
if (callArgs.length === 0) return true;
|
|
2233
2283
|
const words = callArgs.map((a) => resolveWordLiteral(a));
|
|
@@ -2295,11 +2345,11 @@ function isWaivableCleanupTarget(p) {
|
|
|
2295
2345
|
}
|
|
2296
2346
|
function deriveRedirOp(sample) {
|
|
2297
2347
|
try {
|
|
2298
|
-
const f = sharedParser.Parse(sample, "cmd");
|
|
2348
|
+
const f = toPlainTree(sharedParser.Parse(sample, "cmd"));
|
|
2299
2349
|
let op = -1;
|
|
2300
|
-
|
|
2350
|
+
walk(f, (node) => {
|
|
2301
2351
|
const n = node;
|
|
2302
|
-
if (n &&
|
|
2352
|
+
if (n && n.kind === "Redirect" && op < 0) op = n.Op;
|
|
2303
2353
|
return true;
|
|
2304
2354
|
});
|
|
2305
2355
|
return op;
|
|
@@ -2312,7 +2362,7 @@ function collectSameCommandCreations(f) {
|
|
|
2312
2362
|
try {
|
|
2313
2363
|
const stmts = Array.isArray(f?.Stmts) ? f.Stmts : [];
|
|
2314
2364
|
for (const stmt of stmts) {
|
|
2315
|
-
if (!stmt || !stmt.Cmd ||
|
|
2365
|
+
if (!stmt || !stmt.Cmd || stmt.Cmd.kind !== "CallExpr") continue;
|
|
2316
2366
|
const redirs = stmt.Redirs || [];
|
|
2317
2367
|
if (!redirs.some((r) => r && REDIR_HEREDOC_OPS.has(r.Op))) continue;
|
|
2318
2368
|
for (const r of redirs) {
|
|
@@ -2336,10 +2386,10 @@ function isRmCreatedInCommandCleanup(command) {
|
|
|
2336
2386
|
let sawRm = false;
|
|
2337
2387
|
let ok = true;
|
|
2338
2388
|
try {
|
|
2339
|
-
|
|
2389
|
+
walk(f, (node) => {
|
|
2340
2390
|
if (!node || !ok) return false;
|
|
2341
2391
|
const n = node;
|
|
2342
|
-
if (
|
|
2392
|
+
if (n.kind !== "CallExpr") return true;
|
|
2343
2393
|
const args = n.Args || [];
|
|
2344
2394
|
const name = (resolveWordLiteral(args[0]) ?? "").toLowerCase();
|
|
2345
2395
|
if (name !== "rm") return true;
|
|
@@ -2389,10 +2439,10 @@ function analyzeFsOperationImpl(command, depth = 0) {
|
|
|
2389
2439
|
walkWithCalls(f, (node) => {
|
|
2390
2440
|
if (!node || result?.verdict === "block") return false;
|
|
2391
2441
|
const n = node;
|
|
2392
|
-
const nodeType =
|
|
2442
|
+
const nodeType = n.kind;
|
|
2393
2443
|
if (nodeType === "Stmt") {
|
|
2394
2444
|
try {
|
|
2395
|
-
currentStmtOffset = n.
|
|
2445
|
+
currentStmtOffset = n.pos;
|
|
2396
2446
|
} catch {
|
|
2397
2447
|
currentStmtOffset = Number.MAX_SAFE_INTEGER;
|
|
2398
2448
|
}
|
|
@@ -2905,11 +2955,12 @@ function analyzeShellCommand(command) {
|
|
|
2905
2955
|
if (lower2.startsWith("-")) allTokens.push(lower2.replace(/^-+/, ""));
|
|
2906
2956
|
};
|
|
2907
2957
|
try {
|
|
2908
|
-
const f =
|
|
2909
|
-
|
|
2958
|
+
const f = parseShared(command);
|
|
2959
|
+
if (f === PARSE_FAIL) throw new Error("parse failed");
|
|
2960
|
+
walk(f, (node) => {
|
|
2910
2961
|
if (!node) return false;
|
|
2911
2962
|
const n = node;
|
|
2912
|
-
if (
|
|
2963
|
+
if (n.kind !== "CallExpr") return true;
|
|
2913
2964
|
const wordValues2 = (n.Args || []).map((arg) => {
|
|
2914
2965
|
return (arg.Parts || []).map((p) => (p.Value ?? "").replace(/\\(.)/g, "$1")).join("");
|
|
2915
2966
|
}).filter((s) => s.length > 0);
|
|
@@ -3181,7 +3232,7 @@ function runnablePrefix(command) {
|
|
|
3181
3232
|
let text = command;
|
|
3182
3233
|
for (let attempt = 0; attempt < 32; attempt++) {
|
|
3183
3234
|
try {
|
|
3184
|
-
return
|
|
3235
|
+
return toPlainTree(syntax3.NewParser().Parse(text, ""));
|
|
3185
3236
|
} catch (error) {
|
|
3186
3237
|
const message = String(typeof error?.Error === "function" ? error.Error() : error?.message);
|
|
3187
3238
|
const line = Number(/^(\d+):/.exec(message)?.[1]);
|
|
@@ -3249,7 +3300,7 @@ function wordValues(word, state) {
|
|
|
3249
3300
|
out2 = next.slice(0, MAX_ALTERNATIVES);
|
|
3250
3301
|
};
|
|
3251
3302
|
for (const node of nodes) {
|
|
3252
|
-
const kind =
|
|
3303
|
+
const kind = node.kind;
|
|
3253
3304
|
let pieces;
|
|
3254
3305
|
if (kind === "ParamExp") pieces = paramValues(node, quoted);
|
|
3255
3306
|
else if (kind === "DblQuoted") {
|
|
@@ -3317,9 +3368,9 @@ function collectShellFileAccesses(command, context = {}) {
|
|
|
3317
3368
|
}
|
|
3318
3369
|
function substitutions(word, state, depth) {
|
|
3319
3370
|
if (!word) return;
|
|
3320
|
-
|
|
3371
|
+
walk(word, (node) => {
|
|
3321
3372
|
if (!node) return false;
|
|
3322
|
-
if (["CmdSubst", "ProcSubst"].includes(
|
|
3373
|
+
if (["CmdSubst", "ProcSubst"].includes(node.kind)) {
|
|
3323
3374
|
statements(node.Stmts ?? [], clone(state), depth + 1);
|
|
3324
3375
|
return false;
|
|
3325
3376
|
}
|
|
@@ -3743,7 +3794,7 @@ function collectShellFileAccesses(command, context = {}) {
|
|
|
3743
3794
|
}
|
|
3744
3795
|
function readsScriptFromStdin(stmt) {
|
|
3745
3796
|
const cmd = stmt?.Cmd;
|
|
3746
|
-
if (!cmd ||
|
|
3797
|
+
if (!cmd || cmd.kind !== "CallExpr") return false;
|
|
3747
3798
|
const words = (cmd.Args ?? []).map((a) => resolveWordLiteral(a));
|
|
3748
3799
|
const head = unwrapCommandHead(words);
|
|
3749
3800
|
return SHELL_INTERPRETERS.has(base(words[head])) && shellConsumesPipe(words.slice(head + 1));
|
|
@@ -3757,7 +3808,7 @@ function collectShellFileAccesses(command, context = {}) {
|
|
|
3757
3808
|
}
|
|
3758
3809
|
function producedText(stmt) {
|
|
3759
3810
|
const cmd = stmt?.Cmd;
|
|
3760
|
-
if (!cmd ||
|
|
3811
|
+
if (!cmd || cmd.kind !== "CallExpr") return null;
|
|
3761
3812
|
const words = (cmd.Args ?? []).map((a) => resolveWordLiteral(a));
|
|
3762
3813
|
if (words.some((w) => w === null)) return null;
|
|
3763
3814
|
const head = unwrapCommandHead(words);
|
|
@@ -3776,7 +3827,7 @@ function collectShellFileAccesses(command, context = {}) {
|
|
|
3776
3827
|
if (!stmt || depth > MAX_DEPTH) return;
|
|
3777
3828
|
state.onSuccess = void 0;
|
|
3778
3829
|
if (stmt.Background) state = clone(state);
|
|
3779
|
-
const headWord = stmt.Cmd &&
|
|
3830
|
+
const headWord = stmt.Cmd && stmt.Cmd.kind === "CallExpr" ? base(resolveWordLiteral(stmt.Cmd.Args?.[0])) : "";
|
|
3780
3831
|
const sends = SENDING_STDIN.has(headWord);
|
|
3781
3832
|
for (const redir of stmt.Redirs ?? []) {
|
|
3782
3833
|
substitutions(redir.Word, state, depth);
|
|
@@ -3792,7 +3843,7 @@ function collectShellFileAccesses(command, context = {}) {
|
|
|
3792
3843
|
const cmd = stmt.Cmd;
|
|
3793
3844
|
if (!cmd) return;
|
|
3794
3845
|
stdinScript(stmt, state, depth);
|
|
3795
|
-
switch (
|
|
3846
|
+
switch (cmd.kind) {
|
|
3796
3847
|
case "CallExpr":
|
|
3797
3848
|
call(cmd, state, depth);
|
|
3798
3849
|
break;
|
|
@@ -3806,7 +3857,7 @@ function collectShellFileAccesses(command, context = {}) {
|
|
|
3806
3857
|
for (; ; ) {
|
|
3807
3858
|
segments2.unshift({ op: node.Op, part: node.Y });
|
|
3808
3859
|
const left = node.X;
|
|
3809
|
-
if (left?.Cmd &&
|
|
3860
|
+
if (left?.Cmd && left.Cmd.kind === "BinaryCmd" && !left.Redirs?.length && !left.Negated && !left.Background)
|
|
3810
3861
|
node = left.Cmd;
|
|
3811
3862
|
else {
|
|
3812
3863
|
segments2.unshift({ op: node.Op, part: left });
|
|
@@ -5144,11 +5195,10 @@ function shellWriteUnseen(command, target) {
|
|
|
5144
5195
|
let unseen = false;
|
|
5145
5196
|
let seen = false;
|
|
5146
5197
|
try {
|
|
5147
|
-
|
|
5198
|
+
walk(f, (node) => {
|
|
5148
5199
|
if (!node || unseen) return false;
|
|
5149
5200
|
const n = node;
|
|
5150
|
-
if (
|
|
5151
|
-
return true;
|
|
5201
|
+
if (n.kind !== "Stmt" || !n.Cmd || n.Cmd.kind !== "CallExpr") return true;
|
|
5152
5202
|
const words = (n.Cmd.Args || []).map(
|
|
5153
5203
|
(a) => resolveWordLiteral(a)
|
|
5154
5204
|
);
|
|
@@ -5203,14 +5253,14 @@ function analyzeGitImpl(command) {
|
|
|
5203
5253
|
seen.add(key);
|
|
5204
5254
|
findings.push(f);
|
|
5205
5255
|
};
|
|
5206
|
-
const
|
|
5207
|
-
const parsed =
|
|
5256
|
+
const walk3 = new GitWalk((inv) => classify(inv).forEach(found), found);
|
|
5257
|
+
const parsed = walk3.run(command, 0);
|
|
5208
5258
|
return { parsed, findings };
|
|
5209
5259
|
}
|
|
5210
5260
|
function isPositionalParam(word) {
|
|
5211
5261
|
let parts = word?.Parts || [];
|
|
5212
|
-
if (parts.length === 1 &&
|
|
5213
|
-
if (parts.length !== 1 ||
|
|
5262
|
+
if (parts.length === 1 && parts[0].kind === "DblQuoted") parts = parts[0].Parts || [];
|
|
5263
|
+
if (parts.length !== 1 || parts[0].kind !== "ParamExp") return false;
|
|
5214
5264
|
return /^(\d+|@|\*)$/.test(parts[0].Param?.Value ?? "");
|
|
5215
5265
|
}
|
|
5216
5266
|
function boundPositionals() {
|
|
@@ -5224,11 +5274,11 @@ function boundPositionals() {
|
|
|
5224
5274
|
function plainParamName(word) {
|
|
5225
5275
|
let parts = word?.Parts || [];
|
|
5226
5276
|
let quoted = false;
|
|
5227
|
-
if (parts.length === 1 &&
|
|
5277
|
+
if (parts.length === 1 && parts[0].kind === "DblQuoted") {
|
|
5228
5278
|
parts = parts[0].Parts || [];
|
|
5229
5279
|
quoted = true;
|
|
5230
5280
|
}
|
|
5231
|
-
if (parts.length !== 1 ||
|
|
5281
|
+
if (parts.length !== 1 || parts[0].kind !== "ParamExp") return null;
|
|
5232
5282
|
const p = parts[0];
|
|
5233
5283
|
if (p.Excl || p.Length || p.Width || p.Index || p.Slice || p.Repl || p.Exp) return null;
|
|
5234
5284
|
const name = p.Param?.Value;
|
|
@@ -5253,7 +5303,7 @@ function resolveGitWords(word) {
|
|
|
5253
5303
|
}
|
|
5254
5304
|
const parts = word?.Parts || [];
|
|
5255
5305
|
const expansion = parts.some((p) => {
|
|
5256
|
-
const t =
|
|
5306
|
+
const t = p.kind;
|
|
5257
5307
|
return t === "ParamExp" || t === "CmdSubst";
|
|
5258
5308
|
});
|
|
5259
5309
|
return expansion ? splitFields(value) : [value];
|
|
@@ -5654,14 +5704,14 @@ function hookCommands(text) {
|
|
|
5654
5704
|
const root = parsed?.hooks;
|
|
5655
5705
|
if (!root || typeof root !== "object") return null;
|
|
5656
5706
|
const out = [];
|
|
5657
|
-
const
|
|
5707
|
+
const walk3 = (v, depth) => {
|
|
5658
5708
|
if (depth > 8 || !v || typeof v !== "object") return;
|
|
5659
5709
|
for (const [k, x] of Object.entries(v)) {
|
|
5660
5710
|
if (k === "command" && typeof x === "string") out.push(x);
|
|
5661
|
-
else
|
|
5711
|
+
else walk3(x, depth + 1);
|
|
5662
5712
|
}
|
|
5663
5713
|
};
|
|
5664
|
-
|
|
5714
|
+
walk3(root, 0);
|
|
5665
5715
|
return out;
|
|
5666
5716
|
}
|
|
5667
5717
|
function hooksDisarmed(text) {
|
|
@@ -5828,9 +5878,9 @@ function isDupInOp(op) {
|
|
|
5828
5878
|
if (dupInOp === null) {
|
|
5829
5879
|
let found = -1;
|
|
5830
5880
|
try {
|
|
5831
|
-
|
|
5881
|
+
walk(parseShared("cat <&3"), (node) => {
|
|
5832
5882
|
const n = node;
|
|
5833
|
-
if (found === -1 && n &&
|
|
5883
|
+
if (found === -1 && n && n.kind === "Stmt" && n.Redirs?.[0]) {
|
|
5834
5884
|
found = n.Redirs[0].Op;
|
|
5835
5885
|
}
|
|
5836
5886
|
return true;
|
|
@@ -5851,10 +5901,10 @@ function analyzeOutbound(command) {
|
|
|
5851
5901
|
return hit;
|
|
5852
5902
|
}
|
|
5853
5903
|
const findings = [];
|
|
5854
|
-
const
|
|
5855
|
-
const parsed =
|
|
5904
|
+
const walk3 = new OutboundWalk(findings);
|
|
5905
|
+
const parsed = walk3.run(command, 0);
|
|
5856
5906
|
const envOut = findings.find((f) => f.checkId === "data.env-exfil");
|
|
5857
|
-
if (envOut) envOut.destinations =
|
|
5907
|
+
if (envOut) envOut.destinations = walk3.envDestinations();
|
|
5858
5908
|
const computed = { parsed, findings };
|
|
5859
5909
|
if (cache2.size >= CACHE_MAX2) {
|
|
5860
5910
|
const oldest = cache2.keys().next().value;
|
|
@@ -5870,7 +5920,7 @@ function wordShape(word) {
|
|
|
5870
5920
|
const parts = word?.Parts || [];
|
|
5871
5921
|
let out = "";
|
|
5872
5922
|
for (const p of parts) {
|
|
5873
|
-
const t =
|
|
5923
|
+
const t = p.kind;
|
|
5874
5924
|
if (t === "Lit" || t === "SglQuoted") out += p.Value ?? "";
|
|
5875
5925
|
else if (t === "DblQuoted") out += wordShape(p);
|
|
5876
5926
|
else out += "X";
|
|
@@ -6005,16 +6055,16 @@ function declDumpsEnv(decl) {
|
|
|
6005
6055
|
}
|
|
6006
6056
|
function paramName(word) {
|
|
6007
6057
|
let parts = word?.Parts || [];
|
|
6008
|
-
if (parts.length === 1 &&
|
|
6009
|
-
if (parts.length !== 1 ||
|
|
6058
|
+
if (parts.length === 1 && parts[0].kind === "DblQuoted") parts = parts[0].Parts || [];
|
|
6059
|
+
if (parts.length !== 1 || parts[0].kind !== "ParamExp") return null;
|
|
6010
6060
|
return parts[0].Param?.Value ?? null;
|
|
6011
6061
|
}
|
|
6012
6062
|
function refersTo(word, names) {
|
|
6013
6063
|
let hit = false;
|
|
6014
|
-
|
|
6064
|
+
walk(word, (node) => {
|
|
6015
6065
|
if (hit || !node) return false;
|
|
6016
6066
|
const n = node;
|
|
6017
|
-
if (
|
|
6067
|
+
if (n.kind === "ParamExp" && names.has(n.Param?.Value)) hit = true;
|
|
6018
6068
|
return !hit;
|
|
6019
6069
|
});
|
|
6020
6070
|
return hit;
|
|
@@ -6022,10 +6072,10 @@ function refersTo(word, names) {
|
|
|
6022
6072
|
function runsCommandsIn(stmt) {
|
|
6023
6073
|
const readVars = /* @__PURE__ */ new Set();
|
|
6024
6074
|
let hit = false;
|
|
6025
|
-
|
|
6075
|
+
walk(stmt, (node) => {
|
|
6026
6076
|
if (hit || !node) return false;
|
|
6027
6077
|
const n = node;
|
|
6028
|
-
if (
|
|
6078
|
+
if (n.kind !== "CallExpr") return true;
|
|
6029
6079
|
const words = wordsOf(n);
|
|
6030
6080
|
if (words.length === 0) return true;
|
|
6031
6081
|
const { head, at } = headOf(words);
|
|
@@ -6602,7 +6652,7 @@ function localContexts(file2) {
|
|
|
6602
6652
|
if (depth > 64) return null;
|
|
6603
6653
|
if (!stmt || stmt.Background || stmt.Negated || stmt.Redirs?.length) return null;
|
|
6604
6654
|
const cmd = stmt.Cmd;
|
|
6605
|
-
const kind =
|
|
6655
|
+
const kind = cmd.kind;
|
|
6606
6656
|
if (kind === "BinaryCmd" && cmd.Op === andOp) {
|
|
6607
6657
|
const next = visit(cmd.X, steps, depth + 1);
|
|
6608
6658
|
return next === null ? null : visit(cmd.Y, next, depth + 1);
|
|
@@ -6618,7 +6668,7 @@ function localContexts(file2) {
|
|
|
6618
6668
|
)
|
|
6619
6669
|
))
|
|
6620
6670
|
return null;
|
|
6621
|
-
contexts.set(cmd.
|
|
6671
|
+
contexts.set(cmd.pos, steps);
|
|
6622
6672
|
return null;
|
|
6623
6673
|
}
|
|
6624
6674
|
if (file2.Stmts?.length === 1) visit(file2.Stmts[0], []);
|
|
@@ -6629,12 +6679,12 @@ function collect(command, out, depth) {
|
|
|
6629
6679
|
const f = parseShared(command);
|
|
6630
6680
|
if (typeof f === "symbol") return;
|
|
6631
6681
|
const contexts = depth === 0 ? localContexts(f) : /* @__PURE__ */ new Map();
|
|
6632
|
-
|
|
6633
|
-
if (node &&
|
|
6682
|
+
walk(f, (node) => {
|
|
6683
|
+
if (node && node.kind === "CallExpr") {
|
|
6634
6684
|
const words = (node.Args || []).map((a) => resolveWordLiteral(a));
|
|
6635
6685
|
const start = out.length;
|
|
6636
6686
|
if (words.length > 0) fromCall(words, out, depth);
|
|
6637
|
-
const steps = contexts.get(node.
|
|
6687
|
+
const steps = contexts.get(node.pos);
|
|
6638
6688
|
for (let i = start; i < out.length; i++) {
|
|
6639
6689
|
if (["npx", "bunx", "npm exec"].includes(out[i].manager)) {
|
|
6640
6690
|
if (!steps) out[i].localCwd = null;
|
|
@@ -6681,7 +6731,7 @@ function collect2(command, out, depth) {
|
|
|
6681
6731
|
try {
|
|
6682
6732
|
walkCommandScope(command, (node) => {
|
|
6683
6733
|
const n = node;
|
|
6684
|
-
if (!n ||
|
|
6734
|
+
if (!n || n.kind !== "CallExpr") return true;
|
|
6685
6735
|
const words = (n.Args || []).map((a) => resolveWordLiteral(a));
|
|
6686
6736
|
if (words.length === 0) return true;
|
|
6687
6737
|
const prefix = (n.Assigns || []).filter((a) => a?.Name?.Value).map(
|
|
@@ -8066,12 +8116,12 @@ function extractContentDestinations(text) {
|
|
|
8066
8116
|
function extractArgDestinations(args) {
|
|
8067
8117
|
const out = /* @__PURE__ */ new Set();
|
|
8068
8118
|
try {
|
|
8069
|
-
|
|
8119
|
+
walk2(args, void 0, out, 0);
|
|
8070
8120
|
} catch {
|
|
8071
8121
|
}
|
|
8072
8122
|
return [...out];
|
|
8073
8123
|
}
|
|
8074
|
-
function
|
|
8124
|
+
function walk2(value, key, out, depth) {
|
|
8075
8125
|
if (out.size >= MAX_DESTINATIONS || depth > 12) return;
|
|
8076
8126
|
if (typeof value === "string") {
|
|
8077
8127
|
const s = value.length > MAX_SCAN_CHARS ? value.slice(0, MAX_SCAN_CHARS) : value;
|
|
@@ -8090,12 +8140,12 @@ function walk(value, key, out, depth) {
|
|
|
8090
8140
|
return;
|
|
8091
8141
|
}
|
|
8092
8142
|
if (Array.isArray(value)) {
|
|
8093
|
-
for (const v of value)
|
|
8143
|
+
for (const v of value) walk2(v, key, out, depth + 1);
|
|
8094
8144
|
return;
|
|
8095
8145
|
}
|
|
8096
8146
|
if (value && typeof value === "object") {
|
|
8097
8147
|
for (const [k, v] of Object.entries(value))
|
|
8098
|
-
|
|
8148
|
+
walk2(v, k, out, depth + 1);
|
|
8099
8149
|
}
|
|
8100
8150
|
}
|
|
8101
8151
|
function destinationDomain(dest) {
|
|
@@ -8747,9 +8797,9 @@ function classifyCall(head, args, prefixes, env, state, out) {
|
|
|
8747
8797
|
function walkCalls(command, out, depth, state) {
|
|
8748
8798
|
const f = parseShared(command);
|
|
8749
8799
|
if (typeof f === "symbol" || !f) return;
|
|
8750
|
-
|
|
8800
|
+
walk(f, (node) => {
|
|
8751
8801
|
if (!node) return false;
|
|
8752
|
-
const type =
|
|
8802
|
+
const type = node.kind;
|
|
8753
8803
|
if (type === "DeclClause") {
|
|
8754
8804
|
for (const a of node.Args || []) if (a?.Name?.Value) state.env.add(a.Name.Value);
|
|
8755
8805
|
return true;
|
|
@@ -8890,7 +8940,7 @@ function matchCanaryArgs(args, values) {
|
|
|
8890
8940
|
if (values.length === 0) return null;
|
|
8891
8941
|
const needles = prepare(values);
|
|
8892
8942
|
if (needles.length === 0) return null;
|
|
8893
|
-
const
|
|
8943
|
+
const walk3 = (v, depth, fieldPath) => {
|
|
8894
8944
|
if (depth > MAX_DEPTH4) return null;
|
|
8895
8945
|
if (typeof v === "string") {
|
|
8896
8946
|
const hit = matchPrepared(v, needles);
|
|
@@ -8899,31 +8949,31 @@ function matchCanaryArgs(args, values) {
|
|
|
8899
8949
|
const trimmed = v.trim();
|
|
8900
8950
|
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
8901
8951
|
try {
|
|
8902
|
-
return
|
|
8952
|
+
return walk3(JSON.parse(v), depth + 1, fieldPath);
|
|
8903
8953
|
} catch {
|
|
8904
8954
|
}
|
|
8905
8955
|
}
|
|
8906
8956
|
}
|
|
8907
8957
|
return null;
|
|
8908
8958
|
}
|
|
8909
|
-
if (typeof v === "number" && Number.isFinite(v)) return
|
|
8959
|
+
if (typeof v === "number" && Number.isFinite(v)) return walk3(String(v), depth, fieldPath);
|
|
8910
8960
|
if (Array.isArray(v)) {
|
|
8911
8961
|
for (let i = 0; i < v.length; i++) {
|
|
8912
|
-
const h =
|
|
8962
|
+
const h = walk3(v[i], depth + 1, `${fieldPath}[${i}]`);
|
|
8913
8963
|
if (h) return h;
|
|
8914
8964
|
}
|
|
8915
8965
|
return null;
|
|
8916
8966
|
}
|
|
8917
8967
|
if (v && typeof v === "object") {
|
|
8918
8968
|
for (const [k, child] of Object.entries(v)) {
|
|
8919
|
-
const h =
|
|
8969
|
+
const h = walk3(child, depth + 1, fieldPath ? `${fieldPath}.${k}` : k);
|
|
8920
8970
|
if (h) return h;
|
|
8921
8971
|
}
|
|
8922
8972
|
}
|
|
8923
8973
|
return null;
|
|
8924
8974
|
};
|
|
8925
8975
|
try {
|
|
8926
|
-
return
|
|
8976
|
+
return walk3(args, 0, "");
|
|
8927
8977
|
} catch (e) {
|
|
8928
8978
|
console.error(
|
|
8929
8979
|
"[node9 engine] canary args walk failed:",
|
|
@@ -8932,7 +8982,7 @@ function matchCanaryArgs(args, values) {
|
|
|
8932
8982
|
return null;
|
|
8933
8983
|
}
|
|
8934
8984
|
}
|
|
8935
|
-
var MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, finance_default, identity_default, publishing_default, settings_default, branches_default, ACTION_TEMPLATES, ACTION_CHECKS, GAP, prog, httpTo, httpWrite, HTTP_WRITE, HTTP_WRITE_ANY, NOT_STRIPE_TEST, postTo, STRIPE_OBJECTS, APP_SHIELD_SPECS, APP_SHIELDS, APP_SHIELD_NAMES, APP_SERVER_GLOB, GENERAL_ACTION_RULES, BUILTIN_SHIELDS, SSRF_MAX_HOST, METADATA_ADDRESSES, STRICT_TIERS, METADATA_HOSTNAMES, v4Octets, TIER_REASON, CHECK_TEXT, LEVELS, MODE_VALUES, SOFT, STRICT, BLOCK_ALL, LEVEL_CHECKS, VERDICTS, CHECK_VERDICT_RANK, ALL, BUILTIN_DANGEROUS_WORDS, PRODUCT_CHECKS, SHIELD_RULE_NAME, SHIELD_RULES_FOLDED_INTO_PRODUCT, CHECKS, CHECK_BY_ID, LEGACY_TO_ID, PROJECT_JAIL_BLOCK_READ, OVERRIDE_PREFIX, LABEL_WRAPPERS, LEGACY_READERS, INVISIBLE_RE, INVISIBLE_ONE, HOMOGLYPHS, HOMOGLYPH_RE, MAX_BLOBS, MIN_DECODED, BASE64_RUN_RE, HEX_RUN_RE, HEX_ESCAPE_RUN_RE, SYSTEM_AUTH_FILES, SYSTEM_AUTH_DIRS, PASSWORD_HASH_FILES, STARTUP_NAMES, SYSTEM_DIRS, SYSTEM_DIRS_EXEMPT, UPLOAD_SYSTEM_DIRS, AGENT_INSTRUCTION_FILES, AGENT_INSTRUCTION_DIRS, LEGACY_SYSTEM_PATH_RE, MAX_INLINE_CODE, WHAT, OUTSIDE, pyImport, either, PYTHON, NODE, PERL, RUBY, PHP, TABLES, STRING_LITERAL,
|
|
8985
|
+
var MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, finance_default, identity_default, publishing_default, settings_default, branches_default, ACTION_TEMPLATES, ACTION_CHECKS, GAP, prog, httpTo, httpWrite, HTTP_WRITE, HTTP_WRITE_ANY, NOT_STRIPE_TEST, postTo, STRIPE_OBJECTS, APP_SHIELD_SPECS, APP_SHIELDS, APP_SHIELD_NAMES, APP_SERVER_GLOB, GENERAL_ACTION_RULES, BUILTIN_SHIELDS, SSRF_MAX_HOST, METADATA_ADDRESSES, STRICT_TIERS, METADATA_HOSTNAMES, v4Octets, TIER_REASON, CHECK_TEXT, LEVELS, MODE_VALUES, SOFT, STRICT, BLOCK_ALL, LEVEL_CHECKS, VERDICTS, CHECK_VERDICT_RANK, ALL, BUILTIN_DANGEROUS_WORDS, PRODUCT_CHECKS, SHIELD_RULE_NAME, SHIELD_RULES_FOLDED_INTO_PRODUCT, CHECKS, CHECK_BY_ID, LEGACY_TO_ID, PROJECT_JAIL_BLOCK_READ, OVERRIDE_PREFIX, LABEL_WRAPPERS, LEGACY_READERS, syntax, KINDS, STRUCTS, unknownKinds, INVISIBLE_RE, INVISIBLE_ONE, HOMOGLYPHS, HOMOGLYPH_RE, MAX_BLOBS, MIN_DECODED, BASE64_RUN_RE, HEX_RUN_RE, HEX_ESCAPE_RUN_RE, SYSTEM_AUTH_FILES, SYSTEM_AUTH_DIRS, PASSWORD_HASH_FILES, STARTUP_NAMES, SYSTEM_DIRS, SYSTEM_DIRS_EXEMPT, UPLOAD_SYSTEM_DIRS, AGENT_INSTRUCTION_FILES, AGENT_INSTRUCTION_DIRS, LEGACY_SYSTEM_PATH_RE, MAX_INLINE_CODE, WHAT, OUTSIDE, pyImport, either, PYTHON, NODE, PERL, RUBY, PHP, TABLES, STRING_LITERAL, syntax2, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, codeBodiesCache, AST_CACHE_MAX, astCache, PARSE_FAIL, INTERPRETER_LINE, PROGRAM_DIR, ACCESS_VIEW_CACHE_MAX, accessViewCache, MAX_READING_PAYLOADS, STDIN_CODE_INTERPRETERS, RUNS_TEXT_ELSEWHERE, defaultExpansionOps, VARIABLE_READERS, FS_READ_TOOLS, GREP_SHAPE, PATTERN_VERBS, PATTERN_VERB_NAMES, GREP_FILE_OPERANDS, READER_VALUE_LETTERS, FILE_OPERAND_FLAGS, SCP_VALUE_FLAGS, CURL_SOURCE_FLAGS, CURL_VALUE_LETTERS, WGET_VALUE_LETTERS, TAR_VALUE_LETTERS, ZIP_VALUE_LETTERS, RSYNC_VALUE_LETTERS, RSYNC_SKIP, CP_VALUE_LETTERS, INSTALL_VALUE_LETTERS, COPY_VERBS, TAR_MODE_WORD, COPY_VERB_HEADS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, AST_FS_REGEX_RULES, SQL_DB_CLIS, SQL_DDL_RE, CHMOD_OPEN_PERM_TOKENS, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, _redirStdinOps, _listOps, WRAPPER_TAKES_TARGET, FIND_EXEC_FLAGS, FS_MUTATION_TOOLS, INTERP_LEADING_TARGET, MAX_PAYLOAD_NESTING, payloadNesting, MAX_INLINE_CALLS, INLINE_EXEC_FALLBACK, UNREADABLE_SOURCE, SCRIPT_RUNNERS, SCRIPT_RUNNER_VALUE_FLAGS, MAX_SCRIPT_OPERANDS, NET_BINARIES, VALUE_FLAGS, HOME_VARIABLES, assignmentTable, currentStmtOffset, PAYLOAD_BUDGET, payloadBudget, seenPayloads, UNANALYSABLE_NESTING, ASSIGNMENT_HEADS, AND_OP, OR_OP, AND_OR_OPS, MAX_CALL_DEPTH, MAX_CALL_WALKS, callDepth, callWalks, FS_OP_CACHE_MAX, fsOpCache, stripDotSlash, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, FIND_OPTIONS, COPY_RULE_OF, isReaderWord, positionalAfter, NONE, UNKNOWN, FORBIDDEN_PATH_SEGMENTS, BASH_TOOL_NAMES, MCP_EXECUTORS, BUILTIN_FIELDS, ARGV_FIELDS, BARE_WORD, MAX_ARGV_CHARACTERS, syntax3, MAX_ALTERNATIVES, MAX_CALL_DEPTH2, MAX_CALL_WALKS2, POSITIONAL, MAX_DEPTH, MAX_PAYLOAD_DEPTH, unique, clone, SENDING_STDIN, DOWNLOAD_SHORT_VALUE, DOWNLOAD_LONG_VALUE, DOWNLOAD_OUTPUT_FLAGS, DOWNLOAD_DIR_FLAGS, base, quoteGlob, asWord, WRITES, APPENDS, READS, DUP_OUT, HERE_STRING, AND, OR, DEFAULT_OPS, exempt, RANK, SEQUENCE_OPS, BIP39_ENGLISH, BIP39_PUBLIC_VECTORS, indices, lengths, hash, MAX_MNEMONIC_HASHES, SECRET_VIEW_LIMITS, IBAN_LENGTH, IBAN_BBAN_SHAPE, B58, B58_INDEX, XPRV_VERSIONS, B62, CRC32_TABLE, GITHUB_TOKEN_RE, B64URL, B64URL_INDEX, CASK_TWO_ZERO_SUFFIX, CASK_FOUR_ZERO_SUFFIX, CF_ODD, CF_EVEN, SPANISH_LETTERS, HETU_CHARS, VERHOEFF_D, VERHOEFF_P, MAX, UNTRUSTED_TOOLS, INJECTION_SIGNALS, MENTION_QUOTE_RE, MENTION_WORD_RE, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, REFERENCE_VALUE_RE, IDENTIFIER_VALUE_RE, PATH_VALUE_RE, DOTTED_REFERENCE_RE, CONCATENATION_RE, EMAIL_VALUE_RE, FIXTURE_NAME_RE, DERIVATIVE_NAME_RE, QUERY_CREDENTIAL_VALUE_RE, OTP_SECRET_RE, OTP_DOC_SECRETS, ASSIGNED_SECRET_VALUE_RE, ASSIGNED_SECRET_NAME_RE, NAME_KEYWORD_RE, ARN_CONTEXT_RE, CIPHER_KEY_NAME_RE, CIPHER_NAME_PARTS, CIPHER_KEY_VALUE_RE, LARAVEL_KEY_VALUE_RE, SQL_PASSWORD_VALUE_RE, GRAFANA_KEY_VALUE_RE, BASIC_VALUE_RE, BASIC_PAIR_RE, BASIC_HEADER_RE, BASIC_HEADER_PAIR_RE, BASIC_PLACEHOLDER_RE, BASIC_TEMPLATE_MARKERS, DROPBOX_SECRET_RE, OTP_CAPTURE_RE, DB_URL_PASSWORD_RE, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, SENSITIVE_PATH_REGEXES, MAX_DEPTH2, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, BATCH_SEPARATOR, IMPERATIVE, SECRET_TARGET, GUARD, SIGNALS, DESTINATION_RE, REUSED_SIGNALS, NEVER_NEGATED, NEGATION_BEFORE, RDR_IN, COPY_VERBS2, LITERAL_VERBS, GIT_PRESCREEN_RE, MAX_PAYLOAD_DEPTH2, MAX_ALIAS_DEPTH, CACHE_MAX, cache, PAYLOAD_SHELLS, CONFIG_SCOPE_FLAGS, CONFIG_VALUE_FLAGS, GLOBAL_VALUE_OPTIONS, GLOBAL_FLAG_OPTIONS, MAX_LOOP_ITEMS, GitWalk, POSITIONAL2, FORCE_PUSH, DESTRUCTIVE, UNREADABLE, PUSH_LONG_VALUE, BRANCH_LONG_VALUE, TAG_LONG_VALUE, CLEAN_LONG_VALUE, HOOKS_BYPASS, NO_VERIFY_SUBS, HOOK_KILL_SWITCHES, COMMIT_LONG_VALUE, CHMOD_OPTION_RE, STAGE_ALL, ADD_LONG_VALUE, CHECK_ID, RULE_NAME, INERT_LINE_RE, CHECK_ID2, INSTRUCTION_NAMES, SETTINGS_RE, HOOK_WORDS_RE, SHELLS, NETCATS, NET_CLIENTS, SENDERS, NETCAT_EXEC_LONG, DECL_DUMPS, PROC_ENVIRON_RE, DEV_NET_RE, SOCAT_PROGRAM_RE, SOCAT_NET_RE, PRESCREEN_RE, MAX_PAYLOAD_DEPTH3, CACHE_MAX2, cache2, dupInOp, REVERSE_SHELL_DESCRIPTION, ENV_EXFIL_DESCRIPTION, DYNAMIC_WORD, MULTICALL, SHELL_VALUE_OPTIONS, INTERPRETERS, OutboundWalk, NPM_INSTALL_VERBS, NPM_EXEC_VERBS, PNPM_INSTALL_VERBS, YARN_INSTALL_VERBS, BUN_INSTALL_VERBS, DLX_VERBS, NON_REGISTRY_RE, NPM_NAME_RE, EXACT_SEMVER_RE, NPM_VALUE_FLAGS, PNPM_VALUE_FLAGS, YARN_VALUE_FLAGS, BUN_VALUE_FLAGS, PIP_VALUE_FLAGS, UV_VALUE_FLAGS, POETRY_VALUE_FLAGS, PIPENV_VALUE_FLAGS, PIPX_VALUE_FLAGS, PY_SPEC_RE, PY_EXACT_RE, EXACT_VERSION_RE, GEM_NAME_RE, GEM_VALUE_FLAGS, BUNDLE_VALUE_FLAGS, CRATE_NAME_RE, CARGO_ADD_VALUE_FLAGS, CARGO_INSTALL_VALUE_FLAGS, GO_VALUE_FLAGS, GO_PATH_RE, NUGET_NAME_RE, DOTNET_ADD_VALUE_FLAGS, DOTNET_TOOL_VALUE_FLAGS, NUGET_VALUE_FLAGS, MAVEN_COORD_RE, COMPOSER_NAME_RE, COMPOSER_VALUE_FLAGS, EDITOR_HEADS, EXTENSION_ID_RE, MANAGER_HEADS, SHELLS2, MAX_NESTING, SHELL_VALUE_FLAGS, NPM_FAMILY, PIP_FAMILY, NPM_FETCH_VERBS, PIP_FETCH_VERBS, NPM_FLAGS, PIP_FLAGS, NPM_VARS, PIP_VARS, PRESCREEN_RE2, PIP_ATTACHED_RE, MAX_PAYLOAD_DEPTH4, PUBLIC_REGISTRY_HOSTS, TUNING_FIELDS, MAP_GOVERNED, KNOB_GOVERNED, CONFIGURABLE_CHECK_IDS, DEFAULT_EGRESS_ALLOWLIST, PRIVATE_HOST_SUFFIXES, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, DESTINATION_ARGS, MCP_DESTINATION_KEYS, ARGUMENT_SCAN_LIMITS, RUN_CHAR, runChar, VERDICT_RANK, SQL_DML_KEYWORDS, MAX_DESTINATIONS, MAX_SCAN_CHARS, EMAIL_RE, URL_RE, IBAN_RE, RECIPIENT_KEYS, DESTRUCTIVE_VERB_RE, PII_EMAIL_RE, PII_SSN_RE, PII_PHONE_RE, PII_CC16_RE, PII_CC15_RE, PII_CC14_RE, PII_CC13_RE, PII_CC19_RE, CARD_PATTERNS, PII_ROUTING_RE, ROUTING_LABEL_RE, ROUTING_LABEL_WINDOW, NATIONAL_ID_SHAPES, NATIONAL_ID_LABEL_RE, NATIONAL_ID_LABEL_CS_RE, idLabelIn, NATIONAL_ID_LABEL_WINDOW, NATIONAL_ID_GUARD_RE, PII_IBAN_RE, REALTIME_PII_PATTERNS, MAX_PII_SCAN_BYTES, IBAN_DESTINATION_KEYS, WHOLE_IBAN_RE, JSON_ESCAPE_RE, JSON_ESCAPES, MASKED_KINDS, ENTRY_RE, IBAN_TEXT_RE, EMAIL_TEXT_RE, ACCOUNT_KEY_RE, ACCOUNT_VALUE_RE, MAX_MASK_DEPTH, EMPTY, CURL_SEND_FLAGS, WGET_SEND_FLAGS, IWR_SEND_FLAGS, WRITE_METHODS, GH_READ, GH_WRITE, GH_HOSTNAME_FLAGS, GH_REPO_VALUE_FLAGS, GH_API_VALUE_FLAGS, GIT_FETCH, PUBLISH, SHELLS3, OWN_HEADS, MAX_DEPTH3, CWD_CHANGERS, TERMINAL_ESCAPE_RE, LOOP_MAX_RECORDS, CANARY_MIN_LENGTH, MAX_TEXT, MAX_DEPTH4, MAX_JSON_PARSE, URL_DEPTH, B64_DEPTH, MIN_SEGMENT, SEPARATORS, stripSeparators, looksText, CANARY_DECODERS, VIEWS, LONG_OUTPUT_THRESHOLD_BYTES;
|
|
8936
8986
|
var init_dist = __esm({
|
|
8937
8987
|
"packages/policy-engine/dist/index.mjs"() {
|
|
8938
8988
|
"use strict";
|
|
@@ -11580,6 +11630,61 @@ var init_dist = __esm({
|
|
|
11580
11630
|
return s.untrustedContent.afterExposure === "log-only" ? "log" : "review";
|
|
11581
11631
|
}
|
|
11582
11632
|
};
|
|
11633
|
+
({ syntax } = mvdanSh);
|
|
11634
|
+
KINDS = {
|
|
11635
|
+
File: { nodes: ["Stmts"] },
|
|
11636
|
+
Stmt: { nodes: ["Cmd", "Redirs"], scalars: ["Negated", "Background", "Coprocess"] },
|
|
11637
|
+
CallExpr: { nodes: ["Assigns", "Args"] },
|
|
11638
|
+
Assign: { nodes: ["Name", "Value", "Index", "Array"], scalars: ["Append", "Naked"] },
|
|
11639
|
+
Word: { nodes: ["Parts"] },
|
|
11640
|
+
Lit: { scalars: ["Value"] },
|
|
11641
|
+
SglQuoted: { scalars: ["Value", "Dollar"] },
|
|
11642
|
+
DblQuoted: { nodes: ["Parts"], scalars: ["Dollar"] },
|
|
11643
|
+
ParamExp: {
|
|
11644
|
+
nodes: ["Param", "Index", "Slice", "Repl", "Exp"],
|
|
11645
|
+
scalars: ["Short", "Excl", "Length", "Width"]
|
|
11646
|
+
},
|
|
11647
|
+
CmdSubst: { nodes: ["Stmts"], scalars: ["Backquotes", "TempFile", "ReplyVar"] },
|
|
11648
|
+
ArithmExp: { nodes: ["X"], scalars: ["Bracket", "Unsigned"] },
|
|
11649
|
+
ArithmCmd: { nodes: ["X"], scalars: ["Unsigned"] },
|
|
11650
|
+
ProcSubst: { nodes: ["Stmts"], scalars: ["Op"] },
|
|
11651
|
+
ExtGlob: { nodes: ["Pattern"], scalars: ["Op"] },
|
|
11652
|
+
BraceExp: { nodes: ["Elems"], scalars: ["Sequence"] },
|
|
11653
|
+
// only with SplitBraces, unused here
|
|
11654
|
+
BinaryCmd: { nodes: ["X", "Y"], scalars: ["Op"] },
|
|
11655
|
+
BinaryArithm: { nodes: ["X", "Y"], scalars: ["Op"] },
|
|
11656
|
+
UnaryArithm: { nodes: ["X"], scalars: ["Op", "Post"] },
|
|
11657
|
+
ParenArithm: { nodes: ["X"] },
|
|
11658
|
+
BinaryTest: { nodes: ["X", "Y"], scalars: ["Op"] },
|
|
11659
|
+
UnaryTest: { nodes: ["X"], scalars: ["Op"] },
|
|
11660
|
+
ParenTest: { nodes: ["X"] },
|
|
11661
|
+
Redirect: { nodes: ["N", "Word", "Hdoc"], scalars: ["Op"] },
|
|
11662
|
+
Block: { nodes: ["Stmts"] },
|
|
11663
|
+
Subshell: { nodes: ["Stmts"] },
|
|
11664
|
+
IfClause: { nodes: ["Cond", "Then", "Else"] },
|
|
11665
|
+
WhileClause: { nodes: ["Cond", "Do"], scalars: ["Until"] },
|
|
11666
|
+
ForClause: { nodes: ["Loop", "Do"], scalars: ["Select"] },
|
|
11667
|
+
WordIter: { nodes: ["Name", "Items"] },
|
|
11668
|
+
CStyleLoop: { nodes: ["Init", "Cond", "Post"] },
|
|
11669
|
+
CaseClause: { nodes: ["Word", "Items"] },
|
|
11670
|
+
CaseItem: { nodes: ["Patterns", "Stmts"], scalars: ["Op"] },
|
|
11671
|
+
FuncDecl: { nodes: ["Name", "Body"], scalars: ["RsrvWord"] },
|
|
11672
|
+
DeclClause: { nodes: ["Variant", "Args"], notWalked: ["Variant"] },
|
|
11673
|
+
LetClause: { nodes: ["Exprs"] },
|
|
11674
|
+
TimeClause: { nodes: ["Stmt"], scalars: ["PosixFormat"] },
|
|
11675
|
+
CoprocClause: { nodes: ["Name", "Stmt"] },
|
|
11676
|
+
TestClause: { nodes: ["X"] },
|
|
11677
|
+
TestDecl: { nodes: ["Description", "Body"] },
|
|
11678
|
+
ArrayExpr: { nodes: ["Elems"] },
|
|
11679
|
+
ArrayElem: { nodes: ["Index", "Value"] },
|
|
11680
|
+
Comment: { scalars: ["Text"] }
|
|
11681
|
+
};
|
|
11682
|
+
STRUCTS = {
|
|
11683
|
+
"ParamExp.Slice": { nodes: ["Offset", "Length"], notWalked: ["Offset", "Length"] },
|
|
11684
|
+
"ParamExp.Repl": { nodes: ["Orig", "With"], scalars: ["All"] },
|
|
11685
|
+
"ParamExp.Exp": { nodes: ["Word"], scalars: ["Op"] }
|
|
11686
|
+
};
|
|
11687
|
+
unknownKinds = /* @__PURE__ */ new Set();
|
|
11583
11688
|
INVISIBLE_RE = /[͏----︀-️]|[\u{E0000}-\u{E007F}]/gu;
|
|
11584
11689
|
INVISIBLE_ONE = new RegExp(`^(?:${INVISIBLE_RE.source.split("|")[0]})$`, "u");
|
|
11585
11690
|
HOMOGLYPHS = {
|
|
@@ -11865,8 +11970,8 @@ var init_dist = __esm({
|
|
|
11865
11970
|
php: PHP
|
|
11866
11971
|
};
|
|
11867
11972
|
STRING_LITERAL = /(['"`])((?:\\.|(?!\1)[^\\\n]){1,4096})\1/g;
|
|
11868
|
-
({ syntax } =
|
|
11869
|
-
sharedParser =
|
|
11973
|
+
({ syntax: syntax2 } = mvdanSh2);
|
|
11974
|
+
sharedParser = syntax2.NewParser();
|
|
11870
11975
|
MESSAGE_FLAGS = /* @__PURE__ */ new Set([
|
|
11871
11976
|
"-m",
|
|
11872
11977
|
"--message",
|
|
@@ -11900,7 +12005,7 @@ var init_dist = __esm({
|
|
|
11900
12005
|
NORMALIZE_CACHE_MAX = 5e3;
|
|
11901
12006
|
normalizeCache = /* @__PURE__ */ new Map();
|
|
11902
12007
|
codeBodiesCache = /* @__PURE__ */ new Map();
|
|
11903
|
-
AST_CACHE_MAX =
|
|
12008
|
+
AST_CACHE_MAX = 256;
|
|
11904
12009
|
astCache = /* @__PURE__ */ new Map();
|
|
11905
12010
|
PARSE_FAIL = /* @__PURE__ */ Symbol("parse-fail");
|
|
11906
12011
|
INTERPRETER_LINE = /^#![ \t]*[\w./+-]+(?:[ \t]+[\w./+=:-]+)*[ \t]*\r?(?=\n|$)/;
|
|
@@ -12885,7 +12990,7 @@ var init_dist = __esm({
|
|
|
12885
12990
|
ARGV_FIELDS = ["args", "argv"];
|
|
12886
12991
|
BARE_WORD = /^[A-Za-z0-9_./+:@%,-]+$/;
|
|
12887
12992
|
MAX_ARGV_CHARACTERS = 262144;
|
|
12888
|
-
({ syntax:
|
|
12993
|
+
({ syntax: syntax3 } = mvdanSh3);
|
|
12889
12994
|
MAX_ALTERNATIVES = 64;
|
|
12890
12995
|
MAX_CALL_DEPTH2 = 4;
|
|
12891
12996
|
MAX_CALL_WALKS2 = 256;
|
|
@@ -12977,7 +13082,6 @@ var init_dist = __esm({
|
|
|
12977
13082
|
);
|
|
12978
13083
|
exempt = (p) => SYSTEM_DIRS_EXEMPT.some((d) => within(p, d));
|
|
12979
13084
|
RANK = { allow: 0, review: 1, block: 2 };
|
|
12980
|
-
({ syntax: syntax3 } = mvdanSh3);
|
|
12981
13085
|
SEQUENCE_OPS = (() => {
|
|
12982
13086
|
const ops = /* @__PURE__ */ new Set();
|
|
12983
13087
|
for (const text of ["x && y", "x || y"]) {
|
|
@@ -16138,7 +16242,6 @@ zoo`.split("\n");
|
|
|
16138
16242
|
);
|
|
16139
16243
|
NEVER_NEGATED = /* @__PURE__ */ new Set([SIGNALS[1].any[0]]);
|
|
16140
16244
|
NEGATION_BEFORE = /\b(never|do not|don'?t|must not|mustn'?t|should not|shouldn'?t|not allowed to|forbidden to|avoid|without|no|not)\b[^.!?;:\n]{0,24}$/i;
|
|
16141
|
-
({ syntax: syntax4 } = mvdanSh4);
|
|
16142
16245
|
RDR_IN = (() => {
|
|
16143
16246
|
try {
|
|
16144
16247
|
return parseShared("x <y").Stmts[0].Redirs[0].Op;
|
|
@@ -16167,7 +16270,6 @@ zoo`.split("\n");
|
|
|
16167
16270
|
"tar"
|
|
16168
16271
|
]);
|
|
16169
16272
|
LITERAL_VERBS = /* @__PURE__ */ new Set(["echo", "printf", ":", "true", "touch", "sed"]);
|
|
16170
|
-
({ syntax: syntax5 } = mvdanSh5);
|
|
16171
16273
|
GIT_PRESCREEN_RE = /git|husky/i;
|
|
16172
16274
|
MAX_PAYLOAD_DEPTH2 = 8;
|
|
16173
16275
|
MAX_ALIAS_DEPTH = 5;
|
|
@@ -16236,7 +16338,7 @@ zoo`.split("\n");
|
|
|
16236
16338
|
visit(node, depth) {
|
|
16237
16339
|
if (!node) return false;
|
|
16238
16340
|
const n = node;
|
|
16239
|
-
const type =
|
|
16341
|
+
const type = n.kind;
|
|
16240
16342
|
if (type === "Stmt") {
|
|
16241
16343
|
this.visitHeredocShell(n, depth);
|
|
16242
16344
|
return true;
|
|
@@ -16244,7 +16346,7 @@ zoo`.split("\n");
|
|
|
16244
16346
|
if (type === "ForClause") {
|
|
16245
16347
|
const loop = n.Loop;
|
|
16246
16348
|
const name = loop?.Name?.Value;
|
|
16247
|
-
if (name &&
|
|
16349
|
+
if (name && loop.kind === "WordIter") {
|
|
16248
16350
|
const items = (loop.Items || []).map((w) => resolveWordLiteral(w));
|
|
16249
16351
|
const literal = items.length > 0 && items.every((v) => v !== null);
|
|
16250
16352
|
this.loopVars.set(name, literal ? items.slice(0, MAX_LOOP_ITEMS) : null);
|
|
@@ -16311,7 +16413,7 @@ zoo`.split("\n");
|
|
|
16311
16413
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16312
16414
|
visitHeredocShell(stmt, depth) {
|
|
16313
16415
|
const cmd = stmt.Cmd;
|
|
16314
|
-
if (!cmd ||
|
|
16416
|
+
if (!cmd || cmd.kind !== "CallExpr" || depth >= MAX_PAYLOAD_DEPTH2) return;
|
|
16315
16417
|
const redirs = stmt.Redirs || [];
|
|
16316
16418
|
const hdoc = redirs.find((r) => r?.Hdoc && (r.N == null || r.N.Value === "0"));
|
|
16317
16419
|
if (!hdoc) return;
|
|
@@ -16565,7 +16667,6 @@ zoo`.split("\n");
|
|
|
16565
16667
|
]);
|
|
16566
16668
|
SETTINGS_RE = /\/(?:\.claude\/settings(?:\.local)?\.json|\.gemini\/settings\.json|\.codex\/hooks\.json|\.cursor\/hooks\.json)$/i;
|
|
16567
16669
|
HOOK_WORDS_RE = /"hooks"|\b(PreToolUse|PostToolUse|UserPromptSubmit|Notification|Stop|SubagentStop|SessionStart|SessionEnd|PreCompact|BeforeTool|AfterTool)\b|"command"\s*:/;
|
|
16568
|
-
({ syntax: syntax6 } = mvdanSh6);
|
|
16569
16670
|
SHELLS = /* @__PURE__ */ new Set([...SHELL_INTERPRETERS, "mksh", "ash", "csh", "tcsh"]);
|
|
16570
16671
|
NETCATS = /* @__PURE__ */ new Set(["nc", "ncat", "netcat", "nc.traditional", "nc.openbsd"]);
|
|
16571
16672
|
NET_CLIENTS = /* @__PURE__ */ new Set([...NETCATS, "telnet", "socat"]);
|
|
@@ -16623,7 +16724,7 @@ zoo`.split("\n");
|
|
|
16623
16724
|
const f = parseShared(command);
|
|
16624
16725
|
if (!f || typeof f !== "object" || !f.Stmts) return false;
|
|
16625
16726
|
try {
|
|
16626
|
-
|
|
16727
|
+
walk(f, (node) => this.visit(node, depth));
|
|
16627
16728
|
} catch {
|
|
16628
16729
|
return false;
|
|
16629
16730
|
}
|
|
@@ -16662,7 +16763,7 @@ zoo`.split("\n");
|
|
|
16662
16763
|
}
|
|
16663
16764
|
visit(n, depth) {
|
|
16664
16765
|
if (!n) return false;
|
|
16665
|
-
const type =
|
|
16766
|
+
const type = n.kind;
|
|
16666
16767
|
if (type === "Stmt") {
|
|
16667
16768
|
this.visitStmt(n, depth);
|
|
16668
16769
|
return true;
|
|
@@ -16682,7 +16783,7 @@ zoo`.split("\n");
|
|
|
16682
16783
|
const redirs = redirectWords(stmt);
|
|
16683
16784
|
const toSocket = redirs.some((w) => w !== null && DEV_NET_RE.test(w));
|
|
16684
16785
|
if (!cmd) return;
|
|
16685
|
-
const type =
|
|
16786
|
+
const type = cmd.kind;
|
|
16686
16787
|
if (type !== "CallExpr" || wordsOf(cmd).length > 0) {
|
|
16687
16788
|
if (this.readsNetFd(stmt) && runsCommandsIn(stmt)) {
|
|
16688
16789
|
this.reverseShell("commands are read from a network socket opened with exec");
|
|
@@ -16768,7 +16869,7 @@ zoo`.split("\n");
|
|
|
16768
16869
|
const stages = [];
|
|
16769
16870
|
const flatten = (s) => {
|
|
16770
16871
|
const c = s?.Cmd;
|
|
16771
|
-
if (c &&
|
|
16872
|
+
if (c && c.kind === "BinaryCmd" && isPipeOp(c.Op)) {
|
|
16772
16873
|
flatten(c.X);
|
|
16773
16874
|
flatten(c.Y);
|
|
16774
16875
|
} else stages.push(s);
|
|
@@ -16790,8 +16891,8 @@ zoo`.split("\n");
|
|
|
16790
16891
|
sinks.set(i, socketHosts(st));
|
|
16791
16892
|
}
|
|
16792
16893
|
if (!c) return;
|
|
16793
|
-
if (
|
|
16794
|
-
if (
|
|
16894
|
+
if (c.kind === "DeclClause" && declDumpsEnv(c) && dump < 0) dump = i;
|
|
16895
|
+
if (c.kind !== "CallExpr") {
|
|
16795
16896
|
if (runsCommandsIn(st)) shell = i;
|
|
16796
16897
|
return;
|
|
16797
16898
|
}
|
|
@@ -16832,10 +16933,10 @@ zoo`.split("\n");
|
|
|
16832
16933
|
/** A word (or assignment value) whose text includes an env dump's output. */
|
|
16833
16934
|
carriesEnv(word) {
|
|
16834
16935
|
let hit = false;
|
|
16835
|
-
|
|
16936
|
+
walk(word, (node) => {
|
|
16836
16937
|
if (hit || !node) return false;
|
|
16837
16938
|
const n = node;
|
|
16838
|
-
const t =
|
|
16939
|
+
const t = n.kind;
|
|
16839
16940
|
if (t === "ParamExp" && n.Param?.Value && this.envVars.has(n.Param.Value)) {
|
|
16840
16941
|
hit = true;
|
|
16841
16942
|
return false;
|
|
@@ -16852,11 +16953,11 @@ zoo`.split("\n");
|
|
|
16852
16953
|
stmtsDumpEnv(stmts) {
|
|
16853
16954
|
let hit = false;
|
|
16854
16955
|
for (const s of stmts) {
|
|
16855
|
-
|
|
16956
|
+
walk(s, (node) => {
|
|
16856
16957
|
if (hit || !node) return false;
|
|
16857
16958
|
const n = node;
|
|
16858
|
-
const t =
|
|
16859
|
-
if (t === "Stmt" && n.Cmd &&
|
|
16959
|
+
const t = n.kind;
|
|
16960
|
+
if (t === "Stmt" && n.Cmd && n.Cmd.kind === "CallExpr") {
|
|
16860
16961
|
const words = wordsOf(n.Cmd);
|
|
16861
16962
|
if (words.length > 0 && dumpsEnv(words, n)) hit = true;
|
|
16862
16963
|
}
|
|
@@ -16873,7 +16974,6 @@ zoo`.split("\n");
|
|
|
16873
16974
|
this.run(payload, depth + 1);
|
|
16874
16975
|
}
|
|
16875
16976
|
};
|
|
16876
|
-
({ syntax: syntax7 } = mvdanSh7);
|
|
16877
16977
|
NPM_INSTALL_VERBS = /* @__PURE__ */ new Set([
|
|
16878
16978
|
"install",
|
|
16879
16979
|
"i",
|
|
@@ -17255,7 +17355,6 @@ zoo`.split("\n");
|
|
|
17255
17355
|
SHELLS2 = /* @__PURE__ */ new Set(["sh", "bash", "zsh", "dash", "ksh", "ash"]);
|
|
17256
17356
|
MAX_NESTING = 3;
|
|
17257
17357
|
SHELL_VALUE_FLAGS = /* @__PURE__ */ new Set(["--rcfile", "--init-file", "-O", "+O", "-o", "+o"]);
|
|
17258
|
-
({ syntax: syntax8 } = mvdanSh8);
|
|
17259
17358
|
NPM_FAMILY = /* @__PURE__ */ new Set(["npm", "pnpm", "yarn", "bun", "npx", "pnpx", "bunx"]);
|
|
17260
17359
|
PIP_FAMILY = /* @__PURE__ */ new Set(["pip", "pip3", "uv", "uvx", "pipx", "pipenv"]);
|
|
17261
17360
|
NPM_FETCH_VERBS = /* @__PURE__ */ new Set([
|
|
@@ -17787,7 +17886,6 @@ zoo`.split("\n");
|
|
|
17787
17886
|
ACCOUNT_KEY_RE = /("(?:iban|account|account_number|accountNumber)"\s*:\s*")([A-Za-z0-9 .-]{6,40})(")/g;
|
|
17788
17887
|
ACCOUNT_VALUE_RE = /"([A-Z]{2}\d{2}[A-Z0-9]{11,30})"/g;
|
|
17789
17888
|
MAX_MASK_DEPTH = 64;
|
|
17790
|
-
({ syntax: syntax9 } = mvdanSh9);
|
|
17791
17889
|
EMPTY = {
|
|
17792
17890
|
fetches: false,
|
|
17793
17891
|
sends: false,
|