@node9/proxy 2.50.1 → 2.51.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.
@@ -2141,9 +2141,15 @@ function detectDangerousShellExec(command) {
2141
2141
  if (f === PARSE_FAIL) return null;
2142
2142
  let result = null;
2143
2143
  const assigned = assignedNames(f);
2144
- walk(f, (node) => {
2144
+ const known = plainlyNamedPrograms(f);
2145
+ const calledByName = literallyCalledFunctions(f);
2146
+ const bodies = [];
2147
+ const inBody = (pos) => bodies.some(([a, b]) => pos >= a && pos < b);
2148
+ walkWithCalls(f, (node) => {
2145
2149
  if (!node || result === "block") return false;
2146
2150
  const n = node;
2151
+ if (n.kind === "FuncDecl" && n.Body && callDepth === 0 && calledByName.has(n.Name?.Value))
2152
+ bodies.push([n.Body.pos, n.Body.end]);
2147
2153
  if (n.kind === "BinaryCmd" && n.Y && !listOps().has(n.Op)) {
2148
2154
  const call = n.Y.Cmd;
2149
2155
  if (call && call.kind === "CallExpr") {
@@ -2167,7 +2173,8 @@ function detectDangerousShellExec(command) {
2167
2173
  );
2168
2174
  const head = unwrapCommandHead(headWords, false, true);
2169
2175
  const lookup = headWords[0]?.toLowerCase() === "command" && headWords.slice(1).some((w) => w === "-v" || w === "-V");
2170
- if (!lookup && (opaqueExecutable(args[0], assigned) || opaqueExecutable(args[head], assigned)))
2176
+ const unboundBody = callDepth === 0 && inBody(n.pos ?? -1);
2177
+ if (!lookup && !unboundBody && (opaqueExecutable(args[0], assigned, known) || opaqueExecutable(args[head], assigned, known)))
2171
2178
  result = "review";
2172
2179
  const firstParts = args[0].Parts || [];
2173
2180
  if (firstParts.length !== 1 || firstParts[0].kind !== "Lit") return true;
@@ -2227,11 +2234,105 @@ function assignedNames(f) {
2227
2234
  });
2228
2235
  return names;
2229
2236
  }
2230
- function executableParts(word, assigned, quoted = false) {
2237
+ function literallyCalledFunctions(f) {
2238
+ const declared = /* @__PURE__ */ new Set();
2239
+ const called = /* @__PURE__ */ new Set();
2240
+ walk(f, (node) => {
2241
+ if (!node) return false;
2242
+ if (node.kind === "FuncDecl" && node.Name?.Value) declared.add(node.Name.Value);
2243
+ if (node.kind === "CallExpr") {
2244
+ const head = resolveWordLiteral(node.Args?.[0]);
2245
+ if (head) called.add(head);
2246
+ }
2247
+ return true;
2248
+ });
2249
+ return new Set([...declared].filter((name) => called.has(name)));
2250
+ }
2251
+ function plainlyNamedPrograms(f) {
2252
+ const values = /* @__PURE__ */ new Map();
2253
+ const arrays = /* @__PURE__ */ new Set();
2254
+ const give = (name, value) => {
2255
+ if (value === null || !PLAIN_PROGRAM_NAME.test(value)) {
2256
+ values.set(name, null);
2257
+ return;
2258
+ }
2259
+ const cur = values.get(name);
2260
+ if (cur === null) return;
2261
+ if (cur === void 0) values.set(name, /* @__PURE__ */ new Set([value]));
2262
+ else cur.add(value);
2263
+ };
2264
+ const literalOnly = (w) => {
2265
+ const parts = w?.Parts ?? [];
2266
+ if (parts.length !== 1) return null;
2267
+ const p = parts[0];
2268
+ if (p.kind === "Lit") return p.Value ?? null;
2269
+ if (p.kind === "ParamExp" && expansionName(p) === "FUNCNAME") return "FUNCNAME";
2270
+ if (p.kind === "SglQuoted" && !p.Dollar) return p.Value ?? null;
2271
+ if (p.kind === "DblQuoted") {
2272
+ const inner = p.Parts ?? [];
2273
+ return inner.length === 1 && inner[0].kind === "Lit" ? inner[0].Value ?? null : null;
2274
+ }
2275
+ return null;
2276
+ };
2277
+ let ifsTouched = false;
2278
+ walk(f, (node) => {
2279
+ if (!node) return false;
2280
+ const kind = node.kind;
2281
+ if (kind === "FuncDecl" && node.Name?.Value) give(node.Name.Value, node.Name.Value);
2282
+ if (kind === "Assign" && node.Name?.Value) {
2283
+ const name = node.Name.Value;
2284
+ if (name === "IFS") ifsTouched = true;
2285
+ if (node.Append) values.set(name, null);
2286
+ else if (node.Array) {
2287
+ arrays.add(name);
2288
+ for (const el of node.Array.Elems ?? []) give(name, literalOnly(el.Value));
2289
+ } else if (node.Index) {
2290
+ arrays.add(name);
2291
+ give(name, node.Value ? literalOnly(node.Value) : null);
2292
+ } else give(name, node.Value ? literalOnly(node.Value) : null);
2293
+ }
2294
+ if (kind === "ForClause" && node.Loop?.Name?.Value) values.set(node.Loop.Name.Value, null);
2295
+ if (kind === "CallExpr") {
2296
+ const words = (node.Args ?? []).map((a) => resolveWordLiteral(a));
2297
+ if (VARIABLE_READERS.has(words[0] ?? "")) {
2298
+ for (const w of words.slice(1)) if (w && /^[A-Za-z_]\w*$/.test(w)) values.set(w, null);
2299
+ }
2300
+ }
2301
+ return true;
2302
+ });
2303
+ if (ifsTouched) return /* @__PURE__ */ new Set();
2304
+ const functions = /* @__PURE__ */ new Set();
2305
+ walk(f, (node) => {
2306
+ if (node && node.kind === "FuncDecl" && node.Name?.Value) functions.add(node.Name.Value);
2307
+ return true;
2308
+ });
2309
+ const callable = (v) => functions.has(v) || v === "FUNCNAME" || v === "true" || v === "false" || v === ":";
2310
+ for (const [name, vals] of values) {
2311
+ if (vals && ![...vals].every(callable)) values.set(name, null);
2312
+ }
2313
+ const known = /* @__PURE__ */ new Set();
2314
+ for (const [name, vals] of values)
2315
+ if (vals && (vals.size === 1 || arrays.has(name) && vals.size > 0)) known.add(name);
2316
+ return known;
2317
+ }
2318
+ function expansionName(part) {
2319
+ if (part.kind !== "ParamExp") return null;
2320
+ if (part.Excl || part.Length || part.Width || part.Slice || part.Repl || part.Exp) return null;
2321
+ return part.Param?.Value ?? null;
2322
+ }
2323
+ function executableParts(word, assigned, quoted = false, known = EMPTY_NAMES) {
2231
2324
  return (word?.Parts ?? []).flatMap((part) => {
2232
2325
  const kind = part.kind;
2233
- if (kind === "DblQuoted") return executableParts(part, assigned, true);
2326
+ if (kind === "DblQuoted") return executableParts(part, assigned, true, known);
2234
2327
  if (kind === "Lit") return [{ kind: "Lit", value: part.Value ?? "", quoted }];
2328
+ const name = expansionName(part);
2329
+ if (name !== null && (known.has(name) || name === "FUNCNAME"))
2330
+ return [{ kind: "Lit", value: name, quoted }];
2331
+ if (name !== null) {
2332
+ const bound = recordedExpansion(name === "@" || name === "*" ? "1" : name);
2333
+ if (typeof bound === "string" && PLAIN_PROGRAM_NAME.test(bound))
2334
+ return [{ kind: "Lit", value: bound, quoted }];
2335
+ }
2235
2336
  if (kind === "SglQuoted")
2236
2337
  return [{ kind: part.Dollar ? "Dynamic" : "Lit", value: part.Value ?? "", quoted: true }];
2237
2338
  if (kind === "ParamExp" && isDefaultExpansion(part) && !assigned.has(part.Param?.Value)) {
@@ -2239,14 +2340,14 @@ function executableParts(word, assigned, quoted = false) {
2239
2340
  if (fallback !== null) return [{ kind: "Lit", value: fallback, quoted }];
2240
2341
  }
2241
2342
  if (kind === "CmdSubst") {
2242
- const name = lookupSubstitution(part);
2243
- if (name !== null) return [{ kind: "Lit", value: name, quoted }];
2343
+ const name2 = lookupSubstitution(part);
2344
+ if (name2 !== null) return [{ kind: "Lit", value: name2, quoted }];
2244
2345
  }
2245
2346
  return [{ kind: "Dynamic", value: "", quoted }];
2246
2347
  });
2247
2348
  }
2248
- function opaqueExecutable(word, assigned) {
2249
- const parts = executableParts(word, assigned);
2349
+ function opaqueExecutable(word, assigned, known = EMPTY_NAMES) {
2350
+ const parts = executableParts(word, assigned, false, known);
2250
2351
  let lastSlash = -1;
2251
2352
  parts.forEach((p, i) => {
2252
2353
  if (p.kind === "Lit" && p.value.includes("/")) lastSlash = i;
@@ -2368,6 +2469,9 @@ function deriveBinaryOp(sample) {
2368
2469
  function isListOp(op) {
2369
2470
  return listOps().has(op);
2370
2471
  }
2472
+ function isPipeOp(op) {
2473
+ return pipeOps().has(op);
2474
+ }
2371
2475
  function listOps() {
2372
2476
  if (_listOps) return _listOps;
2373
2477
  _listOps = new Set(
@@ -2612,6 +2716,33 @@ function isProtectedCodePath(p) {
2612
2716
  if (matchSensitivePath(p) !== null) return true;
2613
2717
  return SYSTEM_AUTH_FILES.includes(p) || SYSTEM_AUTH_DIRS.some((d) => p === d || p.startsWith(d + "/"));
2614
2718
  }
2719
+ function isJailedCodePath(literal, jailPaths) {
2720
+ const value = trimTrailingSlashes(literal.replace(/\\/g, "/"));
2721
+ if (!value) return false;
2722
+ for (const raw of jailPaths) {
2723
+ const jail = trimTrailingSlashes(raw.trim().replace(/\\/g, "/"));
2724
+ if (!jail) continue;
2725
+ const home = homeTail(jail);
2726
+ if (home === null) {
2727
+ if (value === jail || value.startsWith(jail + "/")) return true;
2728
+ continue;
2729
+ }
2730
+ if (!home) continue;
2731
+ const tail = homeTail(value);
2732
+ if (tail !== null && (tail === home || tail.startsWith(home + "/"))) return true;
2733
+ }
2734
+ return false;
2735
+ }
2736
+ function trimTrailingSlashes(s) {
2737
+ let end = s.length;
2738
+ while (end > 0 && s.charCodeAt(end - 1) === 47) end--;
2739
+ return s.slice(0, end);
2740
+ }
2741
+ function homeTail(p) {
2742
+ const m = /^(?:~|\$\{?HOME\}?|\/home\/[^/]+|\/Users\/[^/]+|\/root)(?:\/(.*))?$/.exec(p);
2743
+ if (!m) return null;
2744
+ return m[1] ?? "";
2745
+ }
2615
2746
  function inlineExecDescription(r) {
2616
2747
  const lead = r ? `The AI is running code directly from the command line: ${inlineExecReason(r).replace(/^This/, "this")}.` : "The AI is running code directly from the command line.";
2617
2748
  return `${lead} Review the full script below before allowing it to execute.`;
@@ -2620,7 +2751,8 @@ function inlineExecReason(r) {
2620
2751
  const who = r.interp ? `This inline ${r.interp} code` : "This inline code";
2621
2752
  return r.evidence ? `${who} ${r.what} (${r.evidence})` : `${who} ${r.what}`;
2622
2753
  }
2623
- function inlineExecReview(command) {
2754
+ function inlineExecReview(command, jailPaths = []) {
2755
+ const isProtected = jailPaths.length === 0 ? isProtectedCodePath : (p) => isProtectedCodePath(p) || isJailedCodePath(p, jailPaths);
2624
2756
  const calls = inlineExecCalls(command);
2625
2757
  if (calls === PARSE_FAIL) {
2626
2758
  return INLINE_EXEC_FALLBACK.test(command) ? { outcome: "unreadable", interp: "", what: "could not be parsed" } : null;
@@ -2643,7 +2775,7 @@ function inlineExecReview(command) {
2643
2775
  what: `is ${call.interp} code, which node9 does not read`
2644
2776
  };
2645
2777
  } else {
2646
- const judged = judgeInlineCode(language, code2, isProtectedCodePath);
2778
+ const judged = judgeInlineCode(language, code2, isProtected);
2647
2779
  if (judged) finding = { ...judged, interp: call.interp };
2648
2780
  }
2649
2781
  if (finding?.outcome === "dangerous") return finding;
@@ -2824,6 +2956,11 @@ function recordTopLevelStmt(stmt) {
2824
2956
  if (t === "DeclClause") return ASSIGNMENT_HEADS.has(stmt.Cmd.Variant?.Value ?? "");
2825
2957
  return (stmt.Cmd.Args || []).length === 0 && (stmt.Cmd.Assigns || []).length > 0;
2826
2958
  }
2959
+ function arrayElements(value) {
2960
+ if (typeof value !== "string" || !value.startsWith(ARRAY_MARK)) return null;
2961
+ const body = value.slice(ARRAY_MARK.length);
2962
+ return body === "" ? [] : body.split(ARRAY_SEP);
2963
+ }
2827
2964
  function recordAssignments(n, at) {
2828
2965
  if (!assignmentTable) return;
2829
2966
  const t = n.kind;
@@ -2837,7 +2974,19 @@ function recordAssignments(n, at) {
2837
2974
  } else return;
2838
2975
  for (const a of assigns) {
2839
2976
  const name = a?.Name?.Value;
2840
- if (!name || !a.Value || a.Append) continue;
2977
+ if (!name) continue;
2978
+ if (a.Array) {
2979
+ const elems = (a.Array.Elems || []).map(
2980
+ (e) => e?.Index ? null : resolveWordLiteral(e?.Value)
2981
+ );
2982
+ const literal = elems.every((v) => v !== null);
2983
+ assignmentTable.set(name, {
2984
+ value: literal && !a.Append ? ARRAY_MARK + elems.join(ARRAY_SEP) : null,
2985
+ at
2986
+ });
2987
+ continue;
2988
+ }
2989
+ if (!a.Value || a.Append) continue;
2841
2990
  assignmentTable.set(name, { value: resolveWordLiteral(a.Value), at });
2842
2991
  }
2843
2992
  }
@@ -2848,7 +2997,33 @@ function resolveTrivialSubst(part) {
2848
2997
  const st = stmts[0];
2849
2998
  if ((st.Redirs || []).length > 0 || st.Negated || st.Background) return void 0;
2850
2999
  const cmd = st.Cmd;
2851
- if (!cmd || cmd.kind !== "CallExpr") return void 0;
3000
+ if (!cmd) return void 0;
3001
+ if (cmd.kind === "BinaryCmd" && pipeOps().has(cmd.Op)) {
3002
+ const left = cmd.X?.Cmd;
3003
+ const right = cmd.Y?.Cmd;
3004
+ if (!left || !right || left.kind !== "CallExpr" || right.kind !== "CallExpr") return void 0;
3005
+ if ((cmd.X.Redirs || []).length > 0 || (cmd.Y.Redirs || []).length > 0) return void 0;
3006
+ const input = trivialCallOutput(left);
3007
+ if (input === void 0) return void 0;
3008
+ const filter = (right.Args || []).map((a) => resolveWordLiteral(a));
3009
+ if (filter.some((w) => w === null)) return void 0;
3010
+ const f = baseWord(filter[0]);
3011
+ const flags = filter.slice(1);
3012
+ if (f === "base64" && flags.length === 1 && /^(-d|-D|--decode)$/.test(flags[0])) {
3013
+ try {
3014
+ const out = Buffer.from(input, "base64").toString("utf8");
3015
+ return /^[\x20-\x7e]*$/.test(out) ? out : void 0;
3016
+ } catch {
3017
+ return void 0;
3018
+ }
3019
+ }
3020
+ if (f === "rev" && flags.length === 0) return [...input].reverse().join("");
3021
+ return void 0;
3022
+ }
3023
+ if (cmd.kind !== "CallExpr") return void 0;
3024
+ return trivialCallOutput(cmd);
3025
+ }
3026
+ function trivialCallOutput(cmd) {
2852
3027
  if ((cmd.Assigns || []).length > 0) return void 0;
2853
3028
  const words = (cmd.Args || []).map((a) => resolveWordLiteral(a));
2854
3029
  if (words.length === 0 || words.some((w) => w === null)) return void 0;
@@ -2860,9 +3035,16 @@ function resolveTrivialSubst(part) {
2860
3035
  return rest.slice(i).join(" ");
2861
3036
  }
2862
3037
  if (head === "printf") {
3038
+ if (rest.length === 1 && /^(?:\\(?:[0-7]{1,3}|x[0-9A-Fa-f]{1,2}))+$/.test(rest[0])) {
3039
+ return rest[0].replace(
3040
+ /\\(?:x([0-9A-Fa-f]{1,2})|([0-7]{1,3}))/g,
3041
+ (_, hex, oct) => String.fromCharCode(hex ? parseInt(hex, 16) : parseInt(oct, 8))
3042
+ );
3043
+ }
2863
3044
  if (rest.length !== 2) return void 0;
2864
- if (!/^%s(\\n)?$/.test(rest[0])) return void 0;
2865
- return rest[1];
3045
+ const m = /^([^%]*)%s([^%]*?)(\\n)?$/.exec(rest[0]);
3046
+ if (!m) return void 0;
3047
+ return m[1] + rest[1] + m[2];
2866
3048
  }
2867
3049
  return void 0;
2868
3050
  }
@@ -2876,11 +3058,28 @@ function isPlainParam(p) {
2876
3058
  if (p.kind !== "ParamExp") return false;
2877
3059
  return !(p.Excl || p.Length || p.Width || p.Index || p.Slice || p.Repl || p.Exp);
2878
3060
  }
3061
+ function indexedParam(p) {
3062
+ if (p.kind !== "ParamExp" || !p.Index) return null;
3063
+ if (p.Excl || p.Length || p.Width || p.Slice || p.Repl || p.Exp) return null;
3064
+ const index = resolveWordLiteral(p.Index);
3065
+ const name = p.Param?.Value;
3066
+ return name && index !== null ? { name, index } : null;
3067
+ }
2879
3068
  function expandPlainParam(p) {
2880
3069
  if (!assignmentTable) return void 0;
3070
+ const indexed = indexedParam(p);
3071
+ if (indexed) {
3072
+ const elems = arrayElements(recordedExpansion(indexed.name));
3073
+ if (elems === null) return void 0;
3074
+ if (indexed.index === "@" || indexed.index === "*") return elems.join(" ");
3075
+ return /^\d+$/.test(indexed.index) ? elems[Number(indexed.index)] ?? null : null;
3076
+ }
2881
3077
  if (!isPlainParam(p)) return void 0;
2882
3078
  const recorded = recordedExpansion(p.Param?.Value);
2883
- if (recorded !== void 0) return recorded;
3079
+ if (recorded !== void 0) {
3080
+ const elems = arrayElements(recorded);
3081
+ return elems === null ? recorded : elems[0] ?? "";
3082
+ }
2884
3083
  return HOME_VARIABLES.has(p.Param?.Value) ? "~" : void 0;
2885
3084
  }
2886
3085
  function functionBodies(f) {
@@ -5859,7 +6058,59 @@ function dynamicOptionWord(word) {
5859
6058
  if (/^--[a-z-]+=/.test(prefix) || /^-[cC]/.test(prefix)) return `${prefix}$\u2026`;
5860
6059
  return null;
5861
6060
  }
5862
- function resolveGitWords(word) {
6061
+ function arrayExpansionWords(word) {
6062
+ let parts = word?.Parts || [];
6063
+ if (parts.length === 1 && parts[0].kind === "DblQuoted") parts = parts[0].Parts || [];
6064
+ if (parts.length !== 1 || parts[0].kind !== "ParamExp" || !parts[0].Index) return null;
6065
+ const index = resolveWordLiteral(parts[0].Index);
6066
+ if (index !== "@" && index !== "*") return null;
6067
+ const elems = arrayElements(recordedExpansion(parts[0].Param?.Value));
6068
+ return elems;
6069
+ }
6070
+ function producedLiteral(call) {
6071
+ if ((call.Assigns || []).length > 0) return null;
6072
+ const words = (call.Args || []).map((a) => resolveWordLiteral(a));
6073
+ if (words.length === 0 || words.some((w) => w === null)) return null;
6074
+ const head = baseWord(words[0]);
6075
+ const rest = words.slice(1);
6076
+ if (head === "git" && rest[0] === "rev-parse" && rest[1] === "--show-toplevel" && rest.length === 2)
6077
+ return ":/";
6078
+ if (head === "echo") {
6079
+ let i = 0;
6080
+ while (i < rest.length && /^-[neE]+$/.test(rest[i])) i++;
6081
+ return rest.length - i === 1 ? rest[i] : null;
6082
+ }
6083
+ if (head === "printf" && rest.length === 2 && /^%s(\\[0n])?$/.test(rest[0])) return rest[1];
6084
+ return null;
6085
+ }
6086
+ function treeTopWord(word, treeTopVars = NO_VARS) {
6087
+ let parts = word?.Parts || [];
6088
+ if (parts.length === 1 && parts[0].kind === "DblQuoted") parts = parts[0].Parts || [];
6089
+ if (parts.length !== 1) return false;
6090
+ const p = parts[0];
6091
+ if (p.kind === "ParamExp") {
6092
+ const name = plainParamName(word)?.name;
6093
+ return name === "PWD" || name !== void 0 && treeTopVars.has(name);
6094
+ }
6095
+ if (p.kind !== "CmdSubst") return false;
6096
+ const stmts = p.Stmts || [];
6097
+ if (stmts.length !== 1 || !stmts[0].Cmd || stmts[0].Cmd.kind !== "CallExpr") return false;
6098
+ const words = (stmts[0].Cmd.Args || []).map((a) => resolveWordLiteral(a));
6099
+ if (words.some((w) => w === null)) return false;
6100
+ const head = baseWord(words[0]);
6101
+ if (head === "pwd") return words.length === 1 || words[1] === "-P" || words[1] === "-L";
6102
+ return head === "git" && words[1] === "rev-parse" && words[2] === "--show-toplevel";
6103
+ }
6104
+ function resolveGitWords(word, treeTopVars = NO_VARS, appended = NO_APPENDS) {
6105
+ if (treeTopWord(word, treeTopVars)) return [":/"];
6106
+ const plain = plainParamName(word);
6107
+ if (plain && appended.has(plain.name)) {
6108
+ const v = appended.get(plain.name);
6109
+ if (v === null || v === void 0) return [null];
6110
+ return plain.quoted ? [v] : splitFields(v);
6111
+ }
6112
+ const elems = arrayExpansionWords(word);
6113
+ if (elems !== null) return elems;
5863
6114
  const value = resolveWordLiteral(word);
5864
6115
  if (value === null) {
5865
6116
  if (isPositionalParam(word)) return boundPositionals() ?? [POSITIONAL2];
@@ -5967,6 +6218,8 @@ function classify(inv) {
5967
6218
  if (o.flags.has("-n") || o.flags.has("--dry-run")) break;
5968
6219
  const forced = o.flags.has("-f") || o.flags.has("--force") || o.flags.has("--force-with-lease") || o.flags.has("--force-if-includes") || o.flags.has("--mirror") || o.operands.some((r) => r.startsWith("+"));
5969
6220
  if (forced) found(FORCE_PUSH);
6221
+ const deletes = o.flags.has("-d") || o.flags.has("--delete") || o.operands.some((r) => /^:[^:]/.test(r));
6222
+ if (deletes) found(DESTRUCTIVE);
5970
6223
  break;
5971
6224
  }
5972
6225
  case "reset": {
@@ -5994,11 +6247,72 @@ function classify(inv) {
5994
6247
  if (o.flags.has("-d") || o.flags.has("--delete")) found(DESTRUCTIVE);
5995
6248
  break;
5996
6249
  }
6250
+ // node9Firewall#385: history and work discarded through plumbing. A ref
6251
+ // deleted by hand, a stash dropped, the reflog expired or pruned: the
6252
+ // commits behind them are gone the way `branch -D` loses them.
6253
+ case "update-ref": {
6254
+ const o = parseOptions(inv.args, "m", /* @__PURE__ */ new Set());
6255
+ if (o.flags.has("-d") && (o.dynamic || o.operands.some(isHistoryRef))) found(DESTRUCTIVE);
6256
+ break;
6257
+ }
6258
+ case "reflog": {
6259
+ const o = parseOptions(inv.args, "", /* @__PURE__ */ new Set());
6260
+ if (o.operands[0] === "expire" || o.operands[0] === "delete") found(DESTRUCTIVE);
6261
+ break;
6262
+ }
6263
+ case "gc": {
6264
+ if (inv.config.get("gc.reflogexpire") === "now") found(DESTRUCTIVE);
6265
+ break;
6266
+ }
6267
+ case "stash": {
6268
+ const o = parseOptions(inv.args, "", /* @__PURE__ */ new Set());
6269
+ if (o.operands[0] === "clear" || o.operands[0] === "drop") found(DESTRUCTIVE);
6270
+ break;
6271
+ }
6272
+ case "worktree": {
6273
+ const o = parseOptions(inv.args, "", /* @__PURE__ */ new Set());
6274
+ if (o.operands[0] === "remove" && (o.flags.has("-f") || o.flags.has("--force")))
6275
+ found(DESTRUCTIVE);
6276
+ break;
6277
+ }
6278
+ // The working tree overwritten: `restore` from another ref, a forced
6279
+ // checkout, or `checkout -- .` (every local change dropped). A bare
6280
+ // `restore .` or `checkout .` stays an ordinary undo, as pinned.
6281
+ case "restore": {
6282
+ const o = parseOptions(inv.args, "s", RESTORE_LONG_VALUE);
6283
+ const src = restoreSource(inv.args);
6284
+ const whole = o.dynamic || o.operands.some(wholeTree);
6285
+ if (src !== null && src !== "HEAD" && whole) found(DESTRUCTIVE);
6286
+ break;
6287
+ }
6288
+ case "checkout": {
6289
+ const o = parseOptions(inv.args, "bB", CHECKOUT_LONG_VALUE);
6290
+ const forced = o.flags.has("-f") || o.flags.has("--force");
6291
+ const dropAll = inv.args.includes("--") && o.operands.some(wholeTree);
6292
+ if (forced || dropAll) found(DESTRUCTIVE);
6293
+ break;
6294
+ }
5997
6295
  default:
5998
6296
  break;
5999
6297
  }
6000
6298
  return out;
6001
6299
  }
6300
+ function isExcludePathspec(operand) {
6301
+ return /^:[!^]/.test(operand) || /^:\([^)]*\bexclude\b[^)]*\)/.test(operand);
6302
+ }
6303
+ function restoreSource(args) {
6304
+ for (let i = 0; i < args.length; i++) {
6305
+ const w = args[i];
6306
+ if (w === null) continue;
6307
+ if (w.startsWith("--source=")) return w.slice("--source=".length);
6308
+ if (w === "--source" || w === "-s") return args[i + 1] ?? null;
6309
+ if (/^-s./.test(w)) return w.slice(2);
6310
+ }
6311
+ return null;
6312
+ }
6313
+ function isHistoryRef(ref) {
6314
+ return /^refs\/(heads|stash)(\/|$)/.test(ref);
6315
+ }
6002
6316
  function killSwitchSet(name, value) {
6003
6317
  if (value === null) return false;
6004
6318
  switch (name) {
@@ -6056,6 +6370,7 @@ function hooksBypass(inv) {
6056
6370
  const o = parseOptions(inv.args, "mFCcut", COMMIT_LONG_VALUE);
6057
6371
  return o.flags.has("-n") || o.flags.has("--no-verify");
6058
6372
  }
6373
+ if (sub === "commit-tree") return true;
6059
6374
  if (sub !== void 0 && NO_VERIFY_SUBS.has(sub)) {
6060
6375
  return inv.args.some((a) => a === "--no-verify");
6061
6376
  }
@@ -6093,7 +6408,20 @@ function configWritesHooksPath(args) {
6093
6408
  return write;
6094
6409
  }
6095
6410
  function isHookPath(word) {
6096
- return /(^|[\\/])(\.git[\\/]hooks|\.husky)([\\/]|$)/.test(word);
6411
+ return /(^|[\\/])(\.git[\\/]hooks|\.husky|\.githooks)([\\/]|$)/.test(word);
6412
+ }
6413
+ function isLiveHookFile(word) {
6414
+ if (!isHookPath(word)) return false;
6415
+ const m = /(^|[\\/])(\.git[\\/]hooks|\.husky|\.githooks)(?:[\\/](.*))?$/.exec(word);
6416
+ if (!m) return false;
6417
+ const rest = m[3] ?? "";
6418
+ if (rest === "" || rest === "_") return true;
6419
+ const leaf = rest.replace(/^_[\\/]/, "");
6420
+ return /^[a-z-]+$/.test(leaf);
6421
+ }
6422
+ function hookBodyIsEmpty(text) {
6423
+ const lines = text.split(/\r?\n/).map((l) => l.trim()).filter((l) => l !== "" && !l.startsWith("#"));
6424
+ return lines.every((l) => /^(exit(\s+0)?|true|:|set\s+-[a-zA-Z]+|set\s+-o\s+\w+)$/.test(l));
6097
6425
  }
6098
6426
  function modeRemovesExecute(mode) {
6099
6427
  if (/^[0-7]{3,4}$/.test(mode)) {
@@ -6102,6 +6430,22 @@ function modeRemovesExecute(mode) {
6102
6430
  }
6103
6431
  return mode.split(",").some((part) => /^[ugoa]*-[rwst]*x/.test(part));
6104
6432
  }
6433
+ function redirOpIsAppend(redir) {
6434
+ return redir?.Op === appendOp();
6435
+ }
6436
+ function appendOp() {
6437
+ if (_appendOp !== null) return _appendOp;
6438
+ try {
6439
+ const f = parseShared("echo x >> y");
6440
+ _appendOp = typeof f === "symbol" ? -1 : f.Stmts[0].Redirs[0].Op;
6441
+ } catch {
6442
+ _appendOp = -1;
6443
+ }
6444
+ return _appendOp;
6445
+ }
6446
+ function hooksBypassed(shown) {
6447
+ return { ...HOOKS_BYPASS, shown, reason: `${HOOKS_BYPASS.reason}: ${shown}` };
6448
+ }
6105
6449
  function chmodDisarmsHook(args, shown) {
6106
6450
  let i = 0;
6107
6451
  while (i < args.length) {
@@ -6144,6 +6488,7 @@ function stagesEverything(inv) {
6144
6488
  const sub = inv.sub?.toLowerCase();
6145
6489
  const prefix = chdirPrefix(inv);
6146
6490
  const judge = (operands2) => {
6491
+ if (operands2.length > 0 && operands2.every(isExcludePathspec)) return {};
6147
6492
  let conditional = false;
6148
6493
  for (const operand of operands2) {
6149
6494
  if (!wholeTree(operand)) continue;
@@ -6436,7 +6781,7 @@ function evaluateAgentFileWrite(args, accesses, context, settings, shell, hooks
6436
6781
  }
6437
6782
  return null;
6438
6783
  }
6439
- function isPipeOp(op) {
6784
+ function isPipeOp2(op) {
6440
6785
  return !isListOp(op);
6441
6786
  }
6442
6787
  function isDupInOp(op) {
@@ -8469,7 +8814,7 @@ async function evaluatePolicyCore(config, toolName, args, context = {}, hooks =
8469
8814
  if (v?.logged) noteLogged(v);
8470
8815
  else if (v) candidates.push(v);
8471
8816
  };
8472
- const inline = reviewsEveryInline(config.settings.mode) ? detectInlineExec(shellCommand) ? void 0 : null : inlineExecReview(shellCommand);
8817
+ const inline = reviewsEveryInline(config.settings.mode) ? detectInlineExec(shellCommand) ? void 0 : null : inlineExecReview(shellCommand, config.policy.jailPaths);
8473
8818
  if (inline !== null) {
8474
8819
  consider("commands.inline-exec", {
8475
8820
  decision: "review",
@@ -8812,7 +9157,7 @@ function* validCardRuns(text, base22, exemptFractions = true) {
8812
9157
  let m;
8813
9158
  while ((m = re.exec(text)) !== null) {
8814
9159
  const digits = m[0].replace(/\D/g, "");
8815
- const fraction = exemptFractions && isDecimalFraction(text, m.index, digits.length === m[0].length);
9160
+ const fraction = exemptFractions && (isDecimalFraction(text, m.index, digits.length === m[0].length) || isInteriorDotNumber(m[0]));
8816
9161
  if (!fraction && validateLuhn(digits)) {
8817
9162
  yield { index: m.index, length: m[0].length, digits };
8818
9163
  } else {
@@ -8839,6 +9184,12 @@ function replaceValidCards(text, replace, { exemptFractions = true } = {}) {
8839
9184
  }
8840
9185
  return out;
8841
9186
  }
9187
+ function isInteriorDotNumber(run) {
9188
+ const first = run.indexOf(".");
9189
+ if (first <= 0 || first === run.length - 1) return false;
9190
+ if (run.indexOf(".", first + 1) !== -1) return false;
9191
+ return !/[-\s]/.test(run);
9192
+ }
8842
9193
  function isDecimalFraction(text, index, unseparated) {
8843
9194
  if (!unseparated) return false;
8844
9195
  const sep = text[index - 1];
@@ -9573,7 +9924,7 @@ function matchCanaryArgs(args, values) {
9573
9924
  return null;
9574
9925
  }
9575
9926
  }
9576
- var getFilename2, __filename2, 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, ASI, ASI_IDS, PACK_SHIELD_OWASP, PACK_RULE_OWASP, POSTURE_CATEGORY_OWASP, 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_ALTERNATIVES, MAX_CALL_DEPTH, MAX_CALL_WALKS, MAX_XARGS_PAYLOAD, 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, ops, MAX_INLINE_CODE, WHAT, OUTSIDE, pyImport, either, PYTHON, NODE, PERL, RUBY, PHP, TABLES, STRING_LITERAL, 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, andOrOps, MAX_CALL_DEPTH2, MAX_CALL_WALKS2, callDepth, callWalks, FS_OP_CACHE_MAX, fsOpCache, stripDotSlash, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, FIND_OPTIONS, COPY_RULE_OF, INDIRECT_SHAPE_RE, SOURCES_FILE_RE, SHELL_SET_VARS, pipeOps, callWords, JAILED_GLOB_NAMES, JAILED_DIR_NAMES, shellGlob, GLOB_META, isReaderWord, positionalAfter, NONE, UNKNOWN, FORBIDDEN_PATH_SEGMENTS, BASH_TOOL_NAMES, MCP_EXECUTORS, BUILTIN_FIELDS, ARGV_FIELDS, BARE_WORD, MAX_ARGV_CHARACTERS, 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, ENCODED_RUN_RE, 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, CANONICAL_EXTRACTOR_VERSION;
9927
+ var getFilename2, __filename2, 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, ASI, ASI_IDS, PACK_SHIELD_OWASP, PACK_RULE_OWASP, POSTURE_CATEGORY_OWASP, 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_ALTERNATIVES, MAX_CALL_DEPTH, MAX_CALL_WALKS, MAX_XARGS_PAYLOAD, 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, ops, MAX_INLINE_CODE, WHAT, OUTSIDE, pyImport, either, PYTHON, NODE, PERL, RUBY, PHP, TABLES, STRING_LITERAL, 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, EMPTY_NAMES, VARIABLE_READERS, PLAIN_PROGRAM_NAME, 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, andOrOps, ARRAY_MARK, ARRAY_SEP, MAX_CALL_DEPTH2, MAX_CALL_WALKS2, callDepth, callWalks, FS_OP_CACHE_MAX, fsOpCache, stripDotSlash, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, FIND_OPTIONS, COPY_RULE_OF, INDIRECT_SHAPE_RE, SOURCES_FILE_RE, SHELL_SET_VARS, pipeOps, callWords, JAILED_GLOB_NAMES, JAILED_DIR_NAMES, shellGlob, GLOB_META, isReaderWord, positionalAfter, NONE, UNKNOWN, FORBIDDEN_PATH_SEGMENTS, BASH_TOOL_NAMES, MCP_EXECUTORS, BUILTIN_FIELDS, ARGV_FIELDS, BARE_WORD, MAX_ARGV_CHARACTERS, 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, ENCODED_RUN_RE, 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, NO_VARS, NO_APPENDS, FORCE_PUSH, DESTRUCTIVE, UNREADABLE, PUSH_LONG_VALUE, BRANCH_LONG_VALUE, TAG_LONG_VALUE, CLEAN_LONG_VALUE, RESTORE_LONG_VALUE, CHECKOUT_LONG_VALUE, HOOKS_BYPASS, NO_VERIFY_SUBS, HOOK_KILL_SWITCHES, COMMIT_LONG_VALUE, CHMOD_OPTION_RE, _appendOp, 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, CANONICAL_EXTRACTOR_VERSION;
9577
9928
  var init_dist = __esm({
9578
9929
  "packages/policy-engine/dist/index.mjs"() {
9579
9930
  "use strict";
@@ -12860,6 +13211,7 @@ var init_dist = __esm({
12860
13211
  "sqlite3"
12861
13212
  ]);
12862
13213
  defaultExpansionOps = null;
13214
+ EMPTY_NAMES = /* @__PURE__ */ new Set();
12863
13215
  VARIABLE_READERS = /* @__PURE__ */ new Set([
12864
13216
  "read",
12865
13217
  "mapfile",
@@ -12872,6 +13224,7 @@ var init_dist = __esm({
12872
13224
  "readonly",
12873
13225
  "printf"
12874
13226
  ]);
13227
+ PLAIN_PROGRAM_NAME = /^[A-Za-z_][\w.+-]*$/;
12875
13228
  FS_READ_TOOLS = /* @__PURE__ */ new Set([
12876
13229
  "cat",
12877
13230
  "less",
@@ -13766,6 +14119,8 @@ var init_dist = __esm({
13766
14119
  AND_OR_OPS: new Set([and, or].filter((o) => o !== null))
13767
14120
  };
13768
14121
  });
14122
+ ARRAY_MARK = "array";
14123
+ ARRAY_SEP = "";
13769
14124
  MAX_CALL_DEPTH2 = 4;
13770
14125
  MAX_CALL_WALKS2 = 256;
13771
14126
  callDepth = 0;
@@ -17096,7 +17451,22 @@ zoo`.split("\n");
17096
17451
  found;
17097
17452
  /** Aliases defined earlier in this command: name -> value (null = dynamic). */
17098
17453
  aliases = /* @__PURE__ */ new Map();
17454
+ /** node9Firewall#385: variables assigned a tree-top word (`scope="$PWD"`,
17455
+ * `root=$(git rev-parse --show-toplevel)`, `DIR=$(pwd)`), so the
17456
+ * variable reads as the top where it is used. */
17457
+ treeTopVars = /* @__PURE__ */ new Set();
17458
+ /** node9Firewall#385 (c): a string a script builds with `+=` from
17459
+ * literals (`mode=--ha; mode+=rd`). The jail's table does not follow an
17460
+ * append (JAIL-14a); the git rules read the built word, since a flag
17461
+ * spelled in two halves is still that flag. name -> value, null once a
17462
+ * half is unknown. */
17463
+ appended = /* @__PURE__ */ new Map();
17099
17464
  payloads = /* @__PURE__ */ new Set();
17465
+ /** node9Firewall#385 (a): the text this command writes to each path
17466
+ * (`cat > /tmp/x.sh <<'SH' ... SH`, `echo '...' > f`, `>>` appends), so
17467
+ * a later statement that runs the path (`bash f`, `sh f`, `./f`) is
17468
+ * judged on what it runs. null once a write to the path is unknown. */
17469
+ written = /* @__PURE__ */ new Map();
17100
17470
  /** `for NAME in a b c`: the literal items, or null when one is dynamic. */
17101
17471
  loopVars = /* @__PURE__ */ new Map();
17102
17472
  /** Walk one command string. Returns false when it did not parse. */
@@ -17115,6 +17485,25 @@ zoo`.split("\n");
17115
17485
  const type = n.kind;
17116
17486
  if (type === "Stmt") {
17117
17487
  this.visitHeredocShell(n, depth);
17488
+ this.visitPipeline(n, depth);
17489
+ this.visitHookWrite(n);
17490
+ this.recordScriptWrite(n);
17491
+ this.visitWrittenScript(n, depth);
17492
+ return true;
17493
+ }
17494
+ if (type === "Assign" && n.Name?.Value && n.Value) {
17495
+ const name = n.Name.Value;
17496
+ if (n.Append) {
17497
+ const prev = this.appended.has(name) ? this.appended.get(name) : resolveWordLiteral(n.Value) === null ? null : void 0;
17498
+ const base22 = prev === void 0 ? recordedExpansion(name) : prev;
17499
+ const piece = resolveWordLiteral(n.Value);
17500
+ this.appended.set(name, typeof base22 === "string" && piece !== null ? base22 + piece : null);
17501
+ this.treeTopVars.delete(name);
17502
+ return true;
17503
+ }
17504
+ this.appended.delete(name);
17505
+ if (treeTopWord(n.Value, this.treeTopVars)) this.treeTopVars.add(name);
17506
+ else this.treeTopVars.delete(name);
17118
17507
  return true;
17119
17508
  }
17120
17509
  if (type === "ForClause") {
@@ -17143,13 +17532,13 @@ zoo`.split("\n");
17143
17532
  const param = plainParamName(rawArgs[loopArg]);
17144
17533
  for (const item of this.loopVars.get(param.name)) {
17145
17534
  const words2 = rawArgs.flatMap(
17146
- (a, i) => i === loopArg ? param.quoted ? [item] : splitFields(item) : resolveGitWords(a)
17535
+ (a, i) => i === loopArg ? param.quoted ? [item] : splitFields(item) : resolveGitWords(a, this.treeTopVars, this.appended)
17147
17536
  );
17148
17537
  this.visitWords(words2, new Map(prefixEnv), depth, false);
17149
17538
  }
17150
17539
  return true;
17151
17540
  }
17152
- const words = rawArgs.flatMap((a) => resolveGitWords(a));
17541
+ const words = rawArgs.flatMap((a) => resolveGitWords(a, this.treeTopVars, this.appended));
17153
17542
  this.visitWords(words, prefixEnv, depth, false);
17154
17543
  return true;
17155
17544
  }
@@ -17174,6 +17563,23 @@ zoo`.split("\n");
17174
17563
  if (f) this.found(f);
17175
17564
  return;
17176
17565
  }
17566
+ if (headWord === "rm" || headWord === "unlink") {
17567
+ if (words.slice(head + 1).some((w) => w !== null && isLiveHookFile(w)))
17568
+ this.found(hooksBypassed(shownOf(words)));
17569
+ return;
17570
+ }
17571
+ if (headWord === "mv") {
17572
+ const operands2 = words.slice(head + 1).filter((w) => w === null || !w.startsWith("-"));
17573
+ const src = operands2[0];
17574
+ if (typeof src === "string" && isLiveHookFile(src)) this.found(hooksBypassed(shownOf(words)));
17575
+ return;
17576
+ }
17577
+ if (headWord === "perl") {
17578
+ const code2 = words.slice(head + 1).find((w) => w !== null && /\brename\b/.test(w));
17579
+ const m = code2 ? /rename\s*\(?\s*q[qw]?\s*[(\[{<'"]([^)\]}>'"]+)/.exec(code2) : null;
17580
+ if (m && isLiveHookFile(m[1])) this.found(hooksBypassed(shownOf(words)));
17581
+ return;
17582
+ }
17177
17583
  if (depth < MAX_PAYLOAD_DEPTH2) {
17178
17584
  const payload = literalShellPayload(words, baseWord(words[0]));
17179
17585
  if (payload !== null) this.runPayload(payload, depth);
@@ -17184,6 +17590,127 @@ zoo`.split("\n");
17184
17590
  this.payloads.add(payload);
17185
17591
  this.run(payload, depth + 1);
17186
17592
  }
17593
+ /**
17594
+ * node9Firewall#385: a pipeline whose last stage reads its pathspec from
17595
+ * stdin (`git add --pathspec-from-file=-`) or runs git over the names it
17596
+ * is fed (`xargs -0 git add --`), with a producer that is one literal
17597
+ * (`git rev-parse --show-toplevel`, `printf '%s\0' .`): the literal is
17598
+ * the operand, and the call is judged with it.
17599
+ */
17600
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17601
+ visitPipeline(stmt, depth) {
17602
+ const cmd = stmt.Cmd;
17603
+ if (!cmd || cmd.kind !== "BinaryCmd" || !isPipeOp(cmd.Op)) return;
17604
+ const stages = [];
17605
+ let cur = cmd;
17606
+ while (cur?.kind === "BinaryCmd" && isPipeOp(cur.Op)) {
17607
+ stages.unshift(cur.Y);
17608
+ const left = cur.X;
17609
+ if (left?.Cmd?.kind === "BinaryCmd" && isPipeOp(left.Cmd.Op)) cur = left.Cmd;
17610
+ else {
17611
+ stages.unshift(left);
17612
+ break;
17613
+ }
17614
+ }
17615
+ if (stages.length < 2) return;
17616
+ const producer = stages[stages.length - 2]?.Cmd;
17617
+ const consumer = stages[stages.length - 1]?.Cmd;
17618
+ if (!producer || !consumer || producer.kind !== "CallExpr" || consumer.kind !== "CallExpr")
17619
+ return;
17620
+ const fed = producedLiteral(producer);
17621
+ if (fed === null) return;
17622
+ const words = (consumer.Args || []).flatMap(
17623
+ (a) => resolveGitWords(a, this.treeTopVars, this.appended)
17624
+ );
17625
+ const head = unwrapCommandHead(words);
17626
+ const viaXargs = words.slice(0, head).some((w) => baseWord(w) === "xargs");
17627
+ const fromFile = words.findIndex(
17628
+ (w) => w === "--pathspec-from-file=-" || w === "--pathspec-from-file"
17629
+ );
17630
+ if (!viaXargs && fromFile < 0) return;
17631
+ let fedWords;
17632
+ if (fromFile >= 0) {
17633
+ fedWords = words.filter(
17634
+ (w, k) => !(k === fromFile || w === "-" && words[k - 1] === "--pathspec-from-file")
17635
+ );
17636
+ fedWords.push(fed);
17637
+ } else fedWords = [...words, fed];
17638
+ this.visitWords(fedWords, /* @__PURE__ */ new Map(), depth, false);
17639
+ }
17640
+ /**
17641
+ * node9Firewall#385 (e): a hook file written with a body that checks
17642
+ * nothing (`cat > .githooks/pre-commit <<'SH' exit 0 SH`, `echo 'exit 0'
17643
+ * > .git/hooks/pre-push`, `: > .githooks/pre-commit`). A real body (a
17644
+ * test, a linter) is ordinary work on the hooks.
17645
+ */
17646
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17647
+ visitHookWrite(stmt) {
17648
+ const cmd = stmt.Cmd;
17649
+ if (!cmd || cmd.kind !== "CallExpr") return;
17650
+ const redirs = stmt.Redirs || [];
17651
+ const target = redirs.find((r) => r?.Word && !r.Hdoc && (r.N == null || r.N.Value === "1"));
17652
+ if (!target) return;
17653
+ const path210 = resolveWordLiteral(target.Word);
17654
+ if (path210 === null || !isLiveHookFile(path210)) return;
17655
+ const hdoc = redirs.find((r) => r?.Hdoc);
17656
+ const words = (cmd.Args || []).map((a) => resolveWordLiteral(a));
17657
+ const head = baseWord(words[0]);
17658
+ let body = null;
17659
+ if (hdoc) body = resolveWordLiteral(hdoc.Hdoc);
17660
+ else if (head === ":" || head === "true" || words.length === 0) body = "";
17661
+ else if (head === "echo" || head === "printf") {
17662
+ const rest = words.slice(1);
17663
+ body = rest.some((w) => w === null) ? null : rest.filter((w) => !/^-[neE]+$/.test(w)).join(" ");
17664
+ if (body !== null) body = body.replace(/\\n/g, "\n");
17665
+ }
17666
+ if (body !== null && hookBodyIsEmpty(body)) this.found(hooksBypassed(shownOf(words)));
17667
+ }
17668
+ /** A statement that writes a literal text to a path, by redirect. */
17669
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17670
+ recordScriptWrite(stmt) {
17671
+ const cmd = stmt.Cmd;
17672
+ if (!cmd || cmd.kind !== "CallExpr") return;
17673
+ const redirs = stmt.Redirs || [];
17674
+ const target = redirs.find((r) => r?.Word && !r.Hdoc && (r.N == null || r.N.Value === "1"));
17675
+ if (!target) return;
17676
+ const path210 = resolveWordLiteral(target.Word);
17677
+ if (path210 === null) return;
17678
+ const append = /^>>/.test(target.Op?.toString() ?? "") || redirOpIsAppend(target);
17679
+ const hdoc = redirs.find((r) => r?.Hdoc);
17680
+ const words = (cmd.Args || []).map((a) => resolveWordLiteral(a));
17681
+ const head = baseWord(words[0]);
17682
+ let text = null;
17683
+ if (hdoc && (head === "cat" || head === "tee")) text = resolveWordLiteral(hdoc.Hdoc);
17684
+ else if (head === "echo" || head === "printf") {
17685
+ const rest = words.slice(1);
17686
+ text = rest.some((w) => w === null) ? null : rest.filter((w) => !/^-[neE]+$/.test(w)).join(" ");
17687
+ if (text !== null && head === "printf") text = text.replace(/\\n/g, "\n");
17688
+ } else return;
17689
+ const prev = this.written.get(path210);
17690
+ if (text === null || append && prev === null) this.written.set(path210, null);
17691
+ else this.written.set(path210, append && typeof prev === "string" ? `${prev}
17692
+ ${text}` : text);
17693
+ }
17694
+ /** A statement that runs a path this command wrote: the text is the script. */
17695
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17696
+ visitWrittenScript(stmt, depth) {
17697
+ const cmd = stmt.Cmd;
17698
+ if (!cmd || cmd.kind !== "CallExpr" || this.written.size === 0 || depth >= MAX_PAYLOAD_DEPTH2)
17699
+ return;
17700
+ const words = (cmd.Args || []).map((a) => resolveWordLiteral(a));
17701
+ const head = unwrapCommandHead(words);
17702
+ const name = baseWord(words[head]);
17703
+ let ran = null;
17704
+ if (PAYLOAD_SHELLS.has(name) && name !== "eval") {
17705
+ const operand = words.slice(head + 1).find((w) => w === null || !w.startsWith("-"));
17706
+ if (typeof operand === "string") ran = operand;
17707
+ } else if (typeof words[head] === "string" && this.written.has(words[head])) {
17708
+ ran = words[head];
17709
+ }
17710
+ if (ran === null) return;
17711
+ const text = this.written.get(ran);
17712
+ if (typeof text === "string") this.runPayload(text, depth);
17713
+ }
17187
17714
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
17188
17715
  visitHeredocShell(stmt, depth) {
17189
17716
  const cmd = stmt.Cmd;
@@ -17335,6 +17862,8 @@ zoo`.split("\n");
17335
17862
  }
17336
17863
  };
17337
17864
  POSITIONAL2 = "\0positional";
17865
+ NO_VARS = /* @__PURE__ */ new Set();
17866
+ NO_APPENDS = /* @__PURE__ */ new Map();
17338
17867
  FORCE_PUSH = {
17339
17868
  checkId: "commands.git-destructive",
17340
17869
  ruleName: "review-force-push",
@@ -17393,6 +17922,8 @@ zoo`.split("\n");
17393
17922
  "--cleanup"
17394
17923
  ]);
17395
17924
  CLEAN_LONG_VALUE = /* @__PURE__ */ new Set(["--exclude"]);
17925
+ RESTORE_LONG_VALUE = /* @__PURE__ */ new Set(["--source", "--pathspec-from-file"]);
17926
+ CHECKOUT_LONG_VALUE = /* @__PURE__ */ new Set(["--pathspec-from-file", "--orphan", "--conflict"]);
17396
17927
  HOOKS_BYPASS = {
17397
17928
  checkId: "commands.git-hooks-bypass",
17398
17929
  ruleName: "review-git-hooks-bypass",
@@ -17418,6 +17949,7 @@ zoo`.split("\n");
17418
17949
  "--gpg-sign"
17419
17950
  ]);
17420
17951
  CHMOD_OPTION_RE = /^-[RvcfHLP]+$/;
17952
+ _appendOp = null;
17421
17953
  STAGE_ALL = {
17422
17954
  checkId: "commands.git-stage-all",
17423
17955
  ruleName: "review-git-stage-all",
@@ -17542,7 +18074,7 @@ zoo`.split("\n");
17542
18074
  this.visitStmt(n, depth);
17543
18075
  return true;
17544
18076
  }
17545
- if (type === "BinaryCmd" && isPipeOp(n.Op)) {
18077
+ if (type === "BinaryCmd" && isPipeOp2(n.Op)) {
17546
18078
  this.visitPipeline(n);
17547
18079
  return true;
17548
18080
  }
@@ -17643,7 +18175,7 @@ zoo`.split("\n");
17643
18175
  const stages = [];
17644
18176
  const flatten = (s) => {
17645
18177
  const c = s?.Cmd;
17646
- if (c && c.kind === "BinaryCmd" && isPipeOp(c.Op)) {
18178
+ if (c && c.kind === "BinaryCmd" && isPipeOp2(c.Op)) {
17647
18179
  flatten(c.X);
17648
18180
  flatten(c.Y);
17649
18181
  } else stages.push(s);
@@ -18830,7 +19362,7 @@ zoo`.split("\n");
18830
19362
  { view: "separators-stripped", decoder: "separators", stripped: true }
18831
19363
  ];
18832
19364
  LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
18833
- CANONICAL_EXTRACTOR_VERSION = "canonical-v36";
19365
+ CANONICAL_EXTRACTOR_VERSION = "canonical-v37";
18834
19366
  }
18835
19367
  });
18836
19368
 
@@ -20125,11 +20657,18 @@ function slug(s) {
20125
20657
  return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "rule";
20126
20658
  }
20127
20659
  function pathToRegexFragment(rawPath) {
20128
- const tail = rawPath.trim().replace(/^~[\\/]?/, "").replace(/^\$\{?HOME\}?[\\/]?/, "").replace(/^\/(?:home|Users)\/[^\\/]+[\\/]?/, "").replace(/^[A-Za-z]:[\\/]Users[\\/][^\\/]+[\\/]?/, "").replace(/^[\\/]+/, "").replace(/[\\/]+$/, "");
20660
+ const trimmed = rawPath.trim();
20661
+ const tail = trimmed.replace(/^~[\\/]?/, "").replace(/^\$\{?HOME\}?[\\/]?/, "").replace(/^\/(?:home|Users)\/[^\\/]+[\\/]?/, "").replace(/^[A-Za-z]:[\\/]Users[\\/][^\\/]+[\\/]?/, "").replace(/^[\\/]+/, "").replace(/[\\/]+$/, "");
20129
20662
  const segments2 = tail.split(/[\\/]+/).filter(Boolean).map(escapeRegex);
20130
20663
  if (segments2.length === 0) return "";
20664
+ if (isAbsoluteOutsideHome(trimmed)) {
20665
+ return `(^|[^A-Za-z0-9_.~\\/-])/${segments2.join(SEP)}(${B}|$)`;
20666
+ }
20131
20667
  return `(^|${B})${segments2.join(SEP)}(${B}|$)`;
20132
20668
  }
20669
+ function isAbsoluteOutsideHome(p) {
20670
+ return p.startsWith("/") && !/^\/(?:home|Users|root)(?:\/|$)/.test(p);
20671
+ }
20133
20672
  function pathMatchesFragment(candidate, rawPath) {
20134
20673
  const value = pathToRegexFragment(rawPath);
20135
20674
  if (!value || !candidate) return false;
@@ -21417,7 +21956,8 @@ function getConfig(cwd) {
21417
21956
  trustedHostsManaged: false,
21418
21957
  appPermissions: {},
21419
21958
  appLinks: {},
21420
- managedJailPaths: []
21959
+ managedJailPaths: [],
21960
+ jailPaths: []
21421
21961
  };
21422
21962
  const mergedEnvironments = { ...DEFAULT_CONFIG.environments };
21423
21963
  const rank = (v) => {
@@ -21812,6 +22352,7 @@ function getConfig(cwd) {
21812
22352
  mergedPolicy.smartRules.push({ ...r, name: `org:${r.name}` });
21813
22353
  }
21814
22354
  mergedPolicy.managedJailPaths.push({ path: path54, verdict });
22355
+ mergedPolicy.jailPaths.push(path54);
21815
22356
  }
21816
22357
  }
21817
22358
  if (Array.isArray(mc.trustedHosts)) {
@@ -21887,6 +22428,11 @@ function getConfig(cwd) {
21887
22428
  }
21888
22429
  }
21889
22430
  mergedPolicy.appliedShields = appliedShields.sort();
22431
+ if (appliedShields.includes(USER_JAIL_SHIELD)) {
22432
+ for (const { path: path54 } of readJailPaths()) {
22433
+ if (!mergedPolicy.jailPaths.includes(path54)) mergedPolicy.jailPaths.push(path54);
22434
+ }
22435
+ }
21890
22436
  const existingAdvisoryNames = new Set(mergedPolicy.smartRules.map((r) => r.name));
21891
22437
  const cc = mergedPolicy.commandChecks ?? {};
21892
22438
  const advisoryKnobKey = (name) => {
@@ -22154,6 +22700,7 @@ var init_config = __esm({
22154
22700
  init_shields();
22155
22701
  init_managed();
22156
22702
  init_build();
22703
+ init_jail();
22157
22704
  init_trusted_hosts();
22158
22705
  init_dist();
22159
22706
  init_dist();
@@ -22422,7 +22969,8 @@ var init_config = __esm({
22422
22969
  trustedHostsManaged: false,
22423
22970
  appPermissions: {},
22424
22971
  appLinks: {},
22425
- managedJailPaths: []
22972
+ managedJailPaths: [],
22973
+ jailPaths: []
22426
22974
  },
22427
22975
  environments: {}
22428
22976
  };