@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.
package/dist/hook.js CHANGED
@@ -2050,9 +2050,15 @@ function detectDangerousShellExec(command) {
2050
2050
  if (f === PARSE_FAIL) return null;
2051
2051
  let result = null;
2052
2052
  const assigned = assignedNames(f);
2053
- walk(f, (node) => {
2053
+ const known = plainlyNamedPrograms(f);
2054
+ const calledByName = literallyCalledFunctions(f);
2055
+ const bodies = [];
2056
+ const inBody = (pos) => bodies.some(([a, b]) => pos >= a && pos < b);
2057
+ walkWithCalls(f, (node) => {
2054
2058
  if (!node || result === "block") return false;
2055
2059
  const n = node;
2060
+ if (n.kind === "FuncDecl" && n.Body && callDepth === 0 && calledByName.has(n.Name?.Value))
2061
+ bodies.push([n.Body.pos, n.Body.end]);
2056
2062
  if (n.kind === "BinaryCmd" && n.Y && !listOps().has(n.Op)) {
2057
2063
  const call = n.Y.Cmd;
2058
2064
  if (call && call.kind === "CallExpr") {
@@ -2076,7 +2082,8 @@ function detectDangerousShellExec(command) {
2076
2082
  );
2077
2083
  const head = unwrapCommandHead(headWords, false, true);
2078
2084
  const lookup = headWords[0]?.toLowerCase() === "command" && headWords.slice(1).some((w) => w === "-v" || w === "-V");
2079
- if (!lookup && (opaqueExecutable(args[0], assigned) || opaqueExecutable(args[head], assigned)))
2085
+ const unboundBody = callDepth === 0 && inBody(n.pos ?? -1);
2086
+ if (!lookup && !unboundBody && (opaqueExecutable(args[0], assigned, known) || opaqueExecutable(args[head], assigned, known)))
2080
2087
  result = "review";
2081
2088
  const firstParts = args[0].Parts || [];
2082
2089
  if (firstParts.length !== 1 || firstParts[0].kind !== "Lit") return true;
@@ -2136,11 +2143,105 @@ function assignedNames(f) {
2136
2143
  });
2137
2144
  return names;
2138
2145
  }
2139
- function executableParts(word, assigned, quoted = false) {
2146
+ function literallyCalledFunctions(f) {
2147
+ const declared = /* @__PURE__ */ new Set();
2148
+ const called = /* @__PURE__ */ new Set();
2149
+ walk(f, (node) => {
2150
+ if (!node) return false;
2151
+ if (node.kind === "FuncDecl" && node.Name?.Value) declared.add(node.Name.Value);
2152
+ if (node.kind === "CallExpr") {
2153
+ const head = resolveWordLiteral(node.Args?.[0]);
2154
+ if (head) called.add(head);
2155
+ }
2156
+ return true;
2157
+ });
2158
+ return new Set([...declared].filter((name) => called.has(name)));
2159
+ }
2160
+ function plainlyNamedPrograms(f) {
2161
+ const values = /* @__PURE__ */ new Map();
2162
+ const arrays = /* @__PURE__ */ new Set();
2163
+ const give = (name, value) => {
2164
+ if (value === null || !PLAIN_PROGRAM_NAME.test(value)) {
2165
+ values.set(name, null);
2166
+ return;
2167
+ }
2168
+ const cur = values.get(name);
2169
+ if (cur === null) return;
2170
+ if (cur === void 0) values.set(name, /* @__PURE__ */ new Set([value]));
2171
+ else cur.add(value);
2172
+ };
2173
+ const literalOnly = (w) => {
2174
+ const parts = w?.Parts ?? [];
2175
+ if (parts.length !== 1) return null;
2176
+ const p = parts[0];
2177
+ if (p.kind === "Lit") return p.Value ?? null;
2178
+ if (p.kind === "ParamExp" && expansionName(p) === "FUNCNAME") return "FUNCNAME";
2179
+ if (p.kind === "SglQuoted" && !p.Dollar) return p.Value ?? null;
2180
+ if (p.kind === "DblQuoted") {
2181
+ const inner = p.Parts ?? [];
2182
+ return inner.length === 1 && inner[0].kind === "Lit" ? inner[0].Value ?? null : null;
2183
+ }
2184
+ return null;
2185
+ };
2186
+ let ifsTouched = false;
2187
+ walk(f, (node) => {
2188
+ if (!node) return false;
2189
+ const kind = node.kind;
2190
+ if (kind === "FuncDecl" && node.Name?.Value) give(node.Name.Value, node.Name.Value);
2191
+ if (kind === "Assign" && node.Name?.Value) {
2192
+ const name = node.Name.Value;
2193
+ if (name === "IFS") ifsTouched = true;
2194
+ if (node.Append) values.set(name, null);
2195
+ else if (node.Array) {
2196
+ arrays.add(name);
2197
+ for (const el of node.Array.Elems ?? []) give(name, literalOnly(el.Value));
2198
+ } else if (node.Index) {
2199
+ arrays.add(name);
2200
+ give(name, node.Value ? literalOnly(node.Value) : null);
2201
+ } else give(name, node.Value ? literalOnly(node.Value) : null);
2202
+ }
2203
+ if (kind === "ForClause" && node.Loop?.Name?.Value) values.set(node.Loop.Name.Value, null);
2204
+ if (kind === "CallExpr") {
2205
+ const words = (node.Args ?? []).map((a) => resolveWordLiteral(a));
2206
+ if (VARIABLE_READERS.has(words[0] ?? "")) {
2207
+ for (const w of words.slice(1)) if (w && /^[A-Za-z_]\w*$/.test(w)) values.set(w, null);
2208
+ }
2209
+ }
2210
+ return true;
2211
+ });
2212
+ if (ifsTouched) return /* @__PURE__ */ new Set();
2213
+ const functions = /* @__PURE__ */ new Set();
2214
+ walk(f, (node) => {
2215
+ if (node && node.kind === "FuncDecl" && node.Name?.Value) functions.add(node.Name.Value);
2216
+ return true;
2217
+ });
2218
+ const callable = (v) => functions.has(v) || v === "FUNCNAME" || v === "true" || v === "false" || v === ":";
2219
+ for (const [name, vals] of values) {
2220
+ if (vals && ![...vals].every(callable)) values.set(name, null);
2221
+ }
2222
+ const known = /* @__PURE__ */ new Set();
2223
+ for (const [name, vals] of values)
2224
+ if (vals && (vals.size === 1 || arrays.has(name) && vals.size > 0)) known.add(name);
2225
+ return known;
2226
+ }
2227
+ function expansionName(part) {
2228
+ if (part.kind !== "ParamExp") return null;
2229
+ if (part.Excl || part.Length || part.Width || part.Slice || part.Repl || part.Exp) return null;
2230
+ return part.Param?.Value ?? null;
2231
+ }
2232
+ function executableParts(word, assigned, quoted = false, known = EMPTY_NAMES) {
2140
2233
  return (word?.Parts ?? []).flatMap((part) => {
2141
2234
  const kind = part.kind;
2142
- if (kind === "DblQuoted") return executableParts(part, assigned, true);
2235
+ if (kind === "DblQuoted") return executableParts(part, assigned, true, known);
2143
2236
  if (kind === "Lit") return [{ kind: "Lit", value: part.Value ?? "", quoted }];
2237
+ const name = expansionName(part);
2238
+ if (name !== null && (known.has(name) || name === "FUNCNAME"))
2239
+ return [{ kind: "Lit", value: name, quoted }];
2240
+ if (name !== null) {
2241
+ const bound = recordedExpansion(name === "@" || name === "*" ? "1" : name);
2242
+ if (typeof bound === "string" && PLAIN_PROGRAM_NAME.test(bound))
2243
+ return [{ kind: "Lit", value: bound, quoted }];
2244
+ }
2144
2245
  if (kind === "SglQuoted")
2145
2246
  return [{ kind: part.Dollar ? "Dynamic" : "Lit", value: part.Value ?? "", quoted: true }];
2146
2247
  if (kind === "ParamExp" && isDefaultExpansion(part) && !assigned.has(part.Param?.Value)) {
@@ -2148,14 +2249,14 @@ function executableParts(word, assigned, quoted = false) {
2148
2249
  if (fallback !== null) return [{ kind: "Lit", value: fallback, quoted }];
2149
2250
  }
2150
2251
  if (kind === "CmdSubst") {
2151
- const name = lookupSubstitution(part);
2152
- if (name !== null) return [{ kind: "Lit", value: name, quoted }];
2252
+ const name2 = lookupSubstitution(part);
2253
+ if (name2 !== null) return [{ kind: "Lit", value: name2, quoted }];
2153
2254
  }
2154
2255
  return [{ kind: "Dynamic", value: "", quoted }];
2155
2256
  });
2156
2257
  }
2157
- function opaqueExecutable(word, assigned) {
2158
- const parts = executableParts(word, assigned);
2258
+ function opaqueExecutable(word, assigned, known = EMPTY_NAMES) {
2259
+ const parts = executableParts(word, assigned, false, known);
2159
2260
  let lastSlash = -1;
2160
2261
  parts.forEach((p, i) => {
2161
2262
  if (p.kind === "Lit" && p.value.includes("/")) lastSlash = i;
@@ -2277,6 +2378,9 @@ function deriveBinaryOp(sample) {
2277
2378
  function isListOp(op) {
2278
2379
  return listOps().has(op);
2279
2380
  }
2381
+ function isPipeOp(op) {
2382
+ return pipeOps().has(op);
2383
+ }
2280
2384
  function listOps() {
2281
2385
  if (_listOps) return _listOps;
2282
2386
  _listOps = new Set(
@@ -2521,6 +2625,33 @@ function isProtectedCodePath(p) {
2521
2625
  if (matchSensitivePath(p) !== null) return true;
2522
2626
  return SYSTEM_AUTH_FILES.includes(p) || SYSTEM_AUTH_DIRS.some((d) => p === d || p.startsWith(d + "/"));
2523
2627
  }
2628
+ function isJailedCodePath(literal, jailPaths) {
2629
+ const value = trimTrailingSlashes(literal.replace(/\\/g, "/"));
2630
+ if (!value) return false;
2631
+ for (const raw of jailPaths) {
2632
+ const jail = trimTrailingSlashes(raw.trim().replace(/\\/g, "/"));
2633
+ if (!jail) continue;
2634
+ const home = homeTail(jail);
2635
+ if (home === null) {
2636
+ if (value === jail || value.startsWith(jail + "/")) return true;
2637
+ continue;
2638
+ }
2639
+ if (!home) continue;
2640
+ const tail = homeTail(value);
2641
+ if (tail !== null && (tail === home || tail.startsWith(home + "/"))) return true;
2642
+ }
2643
+ return false;
2644
+ }
2645
+ function trimTrailingSlashes(s) {
2646
+ let end = s.length;
2647
+ while (end > 0 && s.charCodeAt(end - 1) === 47) end--;
2648
+ return s.slice(0, end);
2649
+ }
2650
+ function homeTail(p) {
2651
+ const m = /^(?:~|\$\{?HOME\}?|\/home\/[^/]+|\/Users\/[^/]+|\/root)(?:\/(.*))?$/.exec(p);
2652
+ if (!m) return null;
2653
+ return m[1] ?? "";
2654
+ }
2524
2655
  function inlineExecDescription(r) {
2525
2656
  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.";
2526
2657
  return `${lead} Review the full script below before allowing it to execute.`;
@@ -2529,7 +2660,8 @@ function inlineExecReason(r) {
2529
2660
  const who = r.interp ? `This inline ${r.interp} code` : "This inline code";
2530
2661
  return r.evidence ? `${who} ${r.what} (${r.evidence})` : `${who} ${r.what}`;
2531
2662
  }
2532
- function inlineExecReview(command) {
2663
+ function inlineExecReview(command, jailPaths = []) {
2664
+ const isProtected = jailPaths.length === 0 ? isProtectedCodePath : (p) => isProtectedCodePath(p) || isJailedCodePath(p, jailPaths);
2533
2665
  const calls = inlineExecCalls(command);
2534
2666
  if (calls === PARSE_FAIL) {
2535
2667
  return INLINE_EXEC_FALLBACK.test(command) ? { outcome: "unreadable", interp: "", what: "could not be parsed" } : null;
@@ -2552,7 +2684,7 @@ function inlineExecReview(command) {
2552
2684
  what: `is ${call.interp} code, which node9 does not read`
2553
2685
  };
2554
2686
  } else {
2555
- const judged = judgeInlineCode(language, code, isProtectedCodePath);
2687
+ const judged = judgeInlineCode(language, code, isProtected);
2556
2688
  if (judged) finding = { ...judged, interp: call.interp };
2557
2689
  }
2558
2690
  if (finding?.outcome === "dangerous") return finding;
@@ -2733,6 +2865,11 @@ function recordTopLevelStmt(stmt) {
2733
2865
  if (t === "DeclClause") return ASSIGNMENT_HEADS.has(stmt.Cmd.Variant?.Value ?? "");
2734
2866
  return (stmt.Cmd.Args || []).length === 0 && (stmt.Cmd.Assigns || []).length > 0;
2735
2867
  }
2868
+ function arrayElements(value) {
2869
+ if (typeof value !== "string" || !value.startsWith(ARRAY_MARK)) return null;
2870
+ const body = value.slice(ARRAY_MARK.length);
2871
+ return body === "" ? [] : body.split(ARRAY_SEP);
2872
+ }
2736
2873
  function recordAssignments(n, at) {
2737
2874
  if (!assignmentTable) return;
2738
2875
  const t = n.kind;
@@ -2746,7 +2883,19 @@ function recordAssignments(n, at) {
2746
2883
  } else return;
2747
2884
  for (const a of assigns) {
2748
2885
  const name = a?.Name?.Value;
2749
- if (!name || !a.Value || a.Append) continue;
2886
+ if (!name) continue;
2887
+ if (a.Array) {
2888
+ const elems = (a.Array.Elems || []).map(
2889
+ (e) => e?.Index ? null : resolveWordLiteral(e?.Value)
2890
+ );
2891
+ const literal = elems.every((v) => v !== null);
2892
+ assignmentTable.set(name, {
2893
+ value: literal && !a.Append ? ARRAY_MARK + elems.join(ARRAY_SEP) : null,
2894
+ at
2895
+ });
2896
+ continue;
2897
+ }
2898
+ if (!a.Value || a.Append) continue;
2750
2899
  assignmentTable.set(name, { value: resolveWordLiteral(a.Value), at });
2751
2900
  }
2752
2901
  }
@@ -2757,7 +2906,33 @@ function resolveTrivialSubst(part) {
2757
2906
  const st = stmts[0];
2758
2907
  if ((st.Redirs || []).length > 0 || st.Negated || st.Background) return void 0;
2759
2908
  const cmd = st.Cmd;
2760
- if (!cmd || cmd.kind !== "CallExpr") return void 0;
2909
+ if (!cmd) return void 0;
2910
+ if (cmd.kind === "BinaryCmd" && pipeOps().has(cmd.Op)) {
2911
+ const left = cmd.X?.Cmd;
2912
+ const right = cmd.Y?.Cmd;
2913
+ if (!left || !right || left.kind !== "CallExpr" || right.kind !== "CallExpr") return void 0;
2914
+ if ((cmd.X.Redirs || []).length > 0 || (cmd.Y.Redirs || []).length > 0) return void 0;
2915
+ const input = trivialCallOutput(left);
2916
+ if (input === void 0) return void 0;
2917
+ const filter = (right.Args || []).map((a) => resolveWordLiteral(a));
2918
+ if (filter.some((w) => w === null)) return void 0;
2919
+ const f = baseWord(filter[0]);
2920
+ const flags = filter.slice(1);
2921
+ if (f === "base64" && flags.length === 1 && /^(-d|-D|--decode)$/.test(flags[0])) {
2922
+ try {
2923
+ const out = Buffer.from(input, "base64").toString("utf8");
2924
+ return /^[\x20-\x7e]*$/.test(out) ? out : void 0;
2925
+ } catch {
2926
+ return void 0;
2927
+ }
2928
+ }
2929
+ if (f === "rev" && flags.length === 0) return [...input].reverse().join("");
2930
+ return void 0;
2931
+ }
2932
+ if (cmd.kind !== "CallExpr") return void 0;
2933
+ return trivialCallOutput(cmd);
2934
+ }
2935
+ function trivialCallOutput(cmd) {
2761
2936
  if ((cmd.Assigns || []).length > 0) return void 0;
2762
2937
  const words = (cmd.Args || []).map((a) => resolveWordLiteral(a));
2763
2938
  if (words.length === 0 || words.some((w) => w === null)) return void 0;
@@ -2769,9 +2944,16 @@ function resolveTrivialSubst(part) {
2769
2944
  return rest.slice(i).join(" ");
2770
2945
  }
2771
2946
  if (head === "printf") {
2947
+ if (rest.length === 1 && /^(?:\\(?:[0-7]{1,3}|x[0-9A-Fa-f]{1,2}))+$/.test(rest[0])) {
2948
+ return rest[0].replace(
2949
+ /\\(?:x([0-9A-Fa-f]{1,2})|([0-7]{1,3}))/g,
2950
+ (_, hex, oct) => String.fromCharCode(hex ? parseInt(hex, 16) : parseInt(oct, 8))
2951
+ );
2952
+ }
2772
2953
  if (rest.length !== 2) return void 0;
2773
- if (!/^%s(\\n)?$/.test(rest[0])) return void 0;
2774
- return rest[1];
2954
+ const m = /^([^%]*)%s([^%]*?)(\\n)?$/.exec(rest[0]);
2955
+ if (!m) return void 0;
2956
+ return m[1] + rest[1] + m[2];
2775
2957
  }
2776
2958
  return void 0;
2777
2959
  }
@@ -2785,11 +2967,28 @@ function isPlainParam(p) {
2785
2967
  if (p.kind !== "ParamExp") return false;
2786
2968
  return !(p.Excl || p.Length || p.Width || p.Index || p.Slice || p.Repl || p.Exp);
2787
2969
  }
2970
+ function indexedParam(p) {
2971
+ if (p.kind !== "ParamExp" || !p.Index) return null;
2972
+ if (p.Excl || p.Length || p.Width || p.Slice || p.Repl || p.Exp) return null;
2973
+ const index = resolveWordLiteral(p.Index);
2974
+ const name = p.Param?.Value;
2975
+ return name && index !== null ? { name, index } : null;
2976
+ }
2788
2977
  function expandPlainParam(p) {
2789
2978
  if (!assignmentTable) return void 0;
2979
+ const indexed = indexedParam(p);
2980
+ if (indexed) {
2981
+ const elems = arrayElements(recordedExpansion(indexed.name));
2982
+ if (elems === null) return void 0;
2983
+ if (indexed.index === "@" || indexed.index === "*") return elems.join(" ");
2984
+ return /^\d+$/.test(indexed.index) ? elems[Number(indexed.index)] ?? null : null;
2985
+ }
2790
2986
  if (!isPlainParam(p)) return void 0;
2791
2987
  const recorded = recordedExpansion(p.Param?.Value);
2792
- if (recorded !== void 0) return recorded;
2988
+ if (recorded !== void 0) {
2989
+ const elems = arrayElements(recorded);
2990
+ return elems === null ? recorded : elems[0] ?? "";
2991
+ }
2793
2992
  return HOME_VARIABLES.has(p.Param?.Value) ? "~" : void 0;
2794
2993
  }
2795
2994
  function functionBodies(f) {
@@ -5862,7 +6061,59 @@ function dynamicOptionWord(word) {
5862
6061
  if (/^--[a-z-]+=/.test(prefix) || /^-[cC]/.test(prefix)) return `${prefix}$\u2026`;
5863
6062
  return null;
5864
6063
  }
5865
- function resolveGitWords(word) {
6064
+ function arrayExpansionWords(word) {
6065
+ let parts = word?.Parts || [];
6066
+ if (parts.length === 1 && parts[0].kind === "DblQuoted") parts = parts[0].Parts || [];
6067
+ if (parts.length !== 1 || parts[0].kind !== "ParamExp" || !parts[0].Index) return null;
6068
+ const index = resolveWordLiteral(parts[0].Index);
6069
+ if (index !== "@" && index !== "*") return null;
6070
+ const elems = arrayElements(recordedExpansion(parts[0].Param?.Value));
6071
+ return elems;
6072
+ }
6073
+ function producedLiteral(call) {
6074
+ if ((call.Assigns || []).length > 0) return null;
6075
+ const words = (call.Args || []).map((a) => resolveWordLiteral(a));
6076
+ if (words.length === 0 || words.some((w) => w === null)) return null;
6077
+ const head = baseWord(words[0]);
6078
+ const rest = words.slice(1);
6079
+ if (head === "git" && rest[0] === "rev-parse" && rest[1] === "--show-toplevel" && rest.length === 2)
6080
+ return ":/";
6081
+ if (head === "echo") {
6082
+ let i = 0;
6083
+ while (i < rest.length && /^-[neE]+$/.test(rest[i])) i++;
6084
+ return rest.length - i === 1 ? rest[i] : null;
6085
+ }
6086
+ if (head === "printf" && rest.length === 2 && /^%s(\\[0n])?$/.test(rest[0])) return rest[1];
6087
+ return null;
6088
+ }
6089
+ function treeTopWord(word, treeTopVars = NO_VARS) {
6090
+ let parts = word?.Parts || [];
6091
+ if (parts.length === 1 && parts[0].kind === "DblQuoted") parts = parts[0].Parts || [];
6092
+ if (parts.length !== 1) return false;
6093
+ const p = parts[0];
6094
+ if (p.kind === "ParamExp") {
6095
+ const name = plainParamName(word)?.name;
6096
+ return name === "PWD" || name !== void 0 && treeTopVars.has(name);
6097
+ }
6098
+ if (p.kind !== "CmdSubst") return false;
6099
+ const stmts = p.Stmts || [];
6100
+ if (stmts.length !== 1 || !stmts[0].Cmd || stmts[0].Cmd.kind !== "CallExpr") return false;
6101
+ const words = (stmts[0].Cmd.Args || []).map((a) => resolveWordLiteral(a));
6102
+ if (words.some((w) => w === null)) return false;
6103
+ const head = baseWord(words[0]);
6104
+ if (head === "pwd") return words.length === 1 || words[1] === "-P" || words[1] === "-L";
6105
+ return head === "git" && words[1] === "rev-parse" && words[2] === "--show-toplevel";
6106
+ }
6107
+ function resolveGitWords(word, treeTopVars = NO_VARS, appended = NO_APPENDS) {
6108
+ if (treeTopWord(word, treeTopVars)) return [":/"];
6109
+ const plain = plainParamName(word);
6110
+ if (plain && appended.has(plain.name)) {
6111
+ const v = appended.get(plain.name);
6112
+ if (v === null || v === void 0) return [null];
6113
+ return plain.quoted ? [v] : splitFields(v);
6114
+ }
6115
+ const elems = arrayExpansionWords(word);
6116
+ if (elems !== null) return elems;
5866
6117
  const value = resolveWordLiteral(word);
5867
6118
  if (value === null) {
5868
6119
  if (isPositionalParam(word)) return boundPositionals() ?? [POSITIONAL2];
@@ -5970,6 +6221,8 @@ function classify(inv) {
5970
6221
  if (o.flags.has("-n") || o.flags.has("--dry-run")) break;
5971
6222
  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("+"));
5972
6223
  if (forced) found(FORCE_PUSH);
6224
+ const deletes = o.flags.has("-d") || o.flags.has("--delete") || o.operands.some((r) => /^:[^:]/.test(r));
6225
+ if (deletes) found(DESTRUCTIVE);
5973
6226
  break;
5974
6227
  }
5975
6228
  case "reset": {
@@ -5997,11 +6250,72 @@ function classify(inv) {
5997
6250
  if (o.flags.has("-d") || o.flags.has("--delete")) found(DESTRUCTIVE);
5998
6251
  break;
5999
6252
  }
6253
+ // node9Firewall#385: history and work discarded through plumbing. A ref
6254
+ // deleted by hand, a stash dropped, the reflog expired or pruned: the
6255
+ // commits behind them are gone the way `branch -D` loses them.
6256
+ case "update-ref": {
6257
+ const o = parseOptions(inv.args, "m", /* @__PURE__ */ new Set());
6258
+ if (o.flags.has("-d") && (o.dynamic || o.operands.some(isHistoryRef))) found(DESTRUCTIVE);
6259
+ break;
6260
+ }
6261
+ case "reflog": {
6262
+ const o = parseOptions(inv.args, "", /* @__PURE__ */ new Set());
6263
+ if (o.operands[0] === "expire" || o.operands[0] === "delete") found(DESTRUCTIVE);
6264
+ break;
6265
+ }
6266
+ case "gc": {
6267
+ if (inv.config.get("gc.reflogexpire") === "now") found(DESTRUCTIVE);
6268
+ break;
6269
+ }
6270
+ case "stash": {
6271
+ const o = parseOptions(inv.args, "", /* @__PURE__ */ new Set());
6272
+ if (o.operands[0] === "clear" || o.operands[0] === "drop") found(DESTRUCTIVE);
6273
+ break;
6274
+ }
6275
+ case "worktree": {
6276
+ const o = parseOptions(inv.args, "", /* @__PURE__ */ new Set());
6277
+ if (o.operands[0] === "remove" && (o.flags.has("-f") || o.flags.has("--force")))
6278
+ found(DESTRUCTIVE);
6279
+ break;
6280
+ }
6281
+ // The working tree overwritten: `restore` from another ref, a forced
6282
+ // checkout, or `checkout -- .` (every local change dropped). A bare
6283
+ // `restore .` or `checkout .` stays an ordinary undo, as pinned.
6284
+ case "restore": {
6285
+ const o = parseOptions(inv.args, "s", RESTORE_LONG_VALUE);
6286
+ const src = restoreSource(inv.args);
6287
+ const whole = o.dynamic || o.operands.some(wholeTree);
6288
+ if (src !== null && src !== "HEAD" && whole) found(DESTRUCTIVE);
6289
+ break;
6290
+ }
6291
+ case "checkout": {
6292
+ const o = parseOptions(inv.args, "bB", CHECKOUT_LONG_VALUE);
6293
+ const forced = o.flags.has("-f") || o.flags.has("--force");
6294
+ const dropAll = inv.args.includes("--") && o.operands.some(wholeTree);
6295
+ if (forced || dropAll) found(DESTRUCTIVE);
6296
+ break;
6297
+ }
6000
6298
  default:
6001
6299
  break;
6002
6300
  }
6003
6301
  return out;
6004
6302
  }
6303
+ function isExcludePathspec(operand) {
6304
+ return /^:[!^]/.test(operand) || /^:\([^)]*\bexclude\b[^)]*\)/.test(operand);
6305
+ }
6306
+ function restoreSource(args) {
6307
+ for (let i = 0; i < args.length; i++) {
6308
+ const w = args[i];
6309
+ if (w === null) continue;
6310
+ if (w.startsWith("--source=")) return w.slice("--source=".length);
6311
+ if (w === "--source" || w === "-s") return args[i + 1] ?? null;
6312
+ if (/^-s./.test(w)) return w.slice(2);
6313
+ }
6314
+ return null;
6315
+ }
6316
+ function isHistoryRef(ref) {
6317
+ return /^refs\/(heads|stash)(\/|$)/.test(ref);
6318
+ }
6005
6319
  function killSwitchSet(name, value) {
6006
6320
  if (value === null) return false;
6007
6321
  switch (name) {
@@ -6059,6 +6373,7 @@ function hooksBypass(inv) {
6059
6373
  const o = parseOptions(inv.args, "mFCcut", COMMIT_LONG_VALUE);
6060
6374
  return o.flags.has("-n") || o.flags.has("--no-verify");
6061
6375
  }
6376
+ if (sub === "commit-tree") return true;
6062
6377
  if (sub !== void 0 && NO_VERIFY_SUBS.has(sub)) {
6063
6378
  return inv.args.some((a) => a === "--no-verify");
6064
6379
  }
@@ -6096,7 +6411,20 @@ function configWritesHooksPath(args) {
6096
6411
  return write2;
6097
6412
  }
6098
6413
  function isHookPath(word) {
6099
- return /(^|[\\/])(\.git[\\/]hooks|\.husky)([\\/]|$)/.test(word);
6414
+ return /(^|[\\/])(\.git[\\/]hooks|\.husky|\.githooks)([\\/]|$)/.test(word);
6415
+ }
6416
+ function isLiveHookFile(word) {
6417
+ if (!isHookPath(word)) return false;
6418
+ const m = /(^|[\\/])(\.git[\\/]hooks|\.husky|\.githooks)(?:[\\/](.*))?$/.exec(word);
6419
+ if (!m) return false;
6420
+ const rest = m[3] ?? "";
6421
+ if (rest === "" || rest === "_") return true;
6422
+ const leaf = rest.replace(/^_[\\/]/, "");
6423
+ return /^[a-z-]+$/.test(leaf);
6424
+ }
6425
+ function hookBodyIsEmpty(text) {
6426
+ const lines = text.split(/\r?\n/).map((l) => l.trim()).filter((l) => l !== "" && !l.startsWith("#"));
6427
+ return lines.every((l) => /^(exit(\s+0)?|true|:|set\s+-[a-zA-Z]+|set\s+-o\s+\w+)$/.test(l));
6100
6428
  }
6101
6429
  function modeRemovesExecute(mode) {
6102
6430
  if (/^[0-7]{3,4}$/.test(mode)) {
@@ -6105,6 +6433,22 @@ function modeRemovesExecute(mode) {
6105
6433
  }
6106
6434
  return mode.split(",").some((part) => /^[ugoa]*-[rwst]*x/.test(part));
6107
6435
  }
6436
+ function redirOpIsAppend(redir) {
6437
+ return redir?.Op === appendOp();
6438
+ }
6439
+ function appendOp() {
6440
+ if (_appendOp !== null) return _appendOp;
6441
+ try {
6442
+ const f = parseShared("echo x >> y");
6443
+ _appendOp = typeof f === "symbol" ? -1 : f.Stmts[0].Redirs[0].Op;
6444
+ } catch {
6445
+ _appendOp = -1;
6446
+ }
6447
+ return _appendOp;
6448
+ }
6449
+ function hooksBypassed(shown) {
6450
+ return { ...HOOKS_BYPASS, shown, reason: `${HOOKS_BYPASS.reason}: ${shown}` };
6451
+ }
6108
6452
  function chmodDisarmsHook(args, shown) {
6109
6453
  let i = 0;
6110
6454
  while (i < args.length) {
@@ -6147,6 +6491,7 @@ function stagesEverything(inv) {
6147
6491
  const sub = inv.sub?.toLowerCase();
6148
6492
  const prefix = chdirPrefix(inv);
6149
6493
  const judge = (operands2) => {
6494
+ if (operands2.length > 0 && operands2.every(isExcludePathspec)) return {};
6150
6495
  let conditional = false;
6151
6496
  for (const operand of operands2) {
6152
6497
  if (!wholeTree(operand)) continue;
@@ -6439,7 +6784,7 @@ function evaluateAgentFileWrite(args, accesses, context, settings, shell, hooks
6439
6784
  }
6440
6785
  return null;
6441
6786
  }
6442
- function isPipeOp(op) {
6787
+ function isPipeOp2(op) {
6443
6788
  return !isListOp(op);
6444
6789
  }
6445
6790
  function isDupInOp(op) {
@@ -8472,7 +8817,7 @@ async function evaluatePolicyCore(config, toolName, args, context = {}, hooks =
8472
8817
  if (v?.logged) noteLogged(v);
8473
8818
  else if (v) candidates.push(v);
8474
8819
  };
8475
- const inline = reviewsEveryInline(config.settings.mode) ? detectInlineExec(shellCommand) ? void 0 : null : inlineExecReview(shellCommand);
8820
+ const inline = reviewsEveryInline(config.settings.mode) ? detectInlineExec(shellCommand) ? void 0 : null : inlineExecReview(shellCommand, config.policy.jailPaths);
8476
8821
  if (inline !== null) {
8477
8822
  consider("commands.inline-exec", {
8478
8823
  decision: "review",
@@ -8815,7 +9160,7 @@ function* validCardRuns(text, base2, exemptFractions = true) {
8815
9160
  let m;
8816
9161
  while ((m = re.exec(text)) !== null) {
8817
9162
  const digits = m[0].replace(/\D/g, "");
8818
- const fraction = exemptFractions && isDecimalFraction(text, m.index, digits.length === m[0].length);
9163
+ const fraction = exemptFractions && (isDecimalFraction(text, m.index, digits.length === m[0].length) || isInteriorDotNumber(m[0]));
8819
9164
  if (!fraction && validateLuhn(digits)) {
8820
9165
  yield { index: m.index, length: m[0].length, digits };
8821
9166
  } else {
@@ -8842,6 +9187,12 @@ function replaceValidCards(text, replace, { exemptFractions = true } = {}) {
8842
9187
  }
8843
9188
  return out;
8844
9189
  }
9190
+ function isInteriorDotNumber(run) {
9191
+ const first = run.indexOf(".");
9192
+ if (first <= 0 || first === run.length - 1) return false;
9193
+ if (run.indexOf(".", first + 1) !== -1) return false;
9194
+ return !/[-\s]/.test(run);
9195
+ }
8845
9196
  function isDecimalFraction(text, index, unseparated) {
8846
9197
  if (!unseparated) return false;
8847
9198
  const sep = text[index - 1];
@@ -9568,7 +9919,7 @@ function matchCanaryArgs(args, values) {
9568
9919
  return null;
9569
9920
  }
9570
9921
  }
9571
- var import_url, import_picomatch, import_safe_regex2, import_picomatch2, import_module, import_picomatch3, import_safe_regex22, import_crypto, import_crypto2, import_crypto3, import_crypto4, getFilename, __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, 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, DLP_SCAN_LIMITS, 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, CONTROL_CHAR_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;
9922
+ var import_url, import_picomatch, import_safe_regex2, import_picomatch2, import_module, import_picomatch3, import_safe_regex22, import_crypto, import_crypto2, import_crypto3, import_crypto4, getFilename, __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, 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, DLP_SCAN_LIMITS, 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, CONTROL_CHAR_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;
9572
9923
  var init_dist = __esm({
9573
9924
  "packages/policy-engine/dist/index.mjs"() {
9574
9925
  "use strict";
@@ -12846,6 +13197,7 @@ var init_dist = __esm({
12846
13197
  "sqlite3"
12847
13198
  ]);
12848
13199
  defaultExpansionOps = null;
13200
+ EMPTY_NAMES = /* @__PURE__ */ new Set();
12849
13201
  VARIABLE_READERS = /* @__PURE__ */ new Set([
12850
13202
  "read",
12851
13203
  "mapfile",
@@ -12858,6 +13210,7 @@ var init_dist = __esm({
12858
13210
  "readonly",
12859
13211
  "printf"
12860
13212
  ]);
13213
+ PLAIN_PROGRAM_NAME = /^[A-Za-z_][\w.+-]*$/;
12861
13214
  FS_READ_TOOLS = /* @__PURE__ */ new Set([
12862
13215
  "cat",
12863
13216
  "less",
@@ -13752,6 +14105,8 @@ var init_dist = __esm({
13752
14105
  AND_OR_OPS: new Set([and, or].filter((o) => o !== null))
13753
14106
  };
13754
14107
  });
14108
+ ARRAY_MARK = "array";
14109
+ ARRAY_SEP = "";
13755
14110
  MAX_CALL_DEPTH2 = 4;
13756
14111
  MAX_CALL_WALKS2 = 256;
13757
14112
  callDepth = 0;
@@ -17089,7 +17444,22 @@ zoo`.split("\n");
17089
17444
  found;
17090
17445
  /** Aliases defined earlier in this command: name -> value (null = dynamic). */
17091
17446
  aliases = /* @__PURE__ */ new Map();
17447
+ /** node9Firewall#385: variables assigned a tree-top word (`scope="$PWD"`,
17448
+ * `root=$(git rev-parse --show-toplevel)`, `DIR=$(pwd)`), so the
17449
+ * variable reads as the top where it is used. */
17450
+ treeTopVars = /* @__PURE__ */ new Set();
17451
+ /** node9Firewall#385 (c): a string a script builds with `+=` from
17452
+ * literals (`mode=--ha; mode+=rd`). The jail's table does not follow an
17453
+ * append (JAIL-14a); the git rules read the built word, since a flag
17454
+ * spelled in two halves is still that flag. name -> value, null once a
17455
+ * half is unknown. */
17456
+ appended = /* @__PURE__ */ new Map();
17092
17457
  payloads = /* @__PURE__ */ new Set();
17458
+ /** node9Firewall#385 (a): the text this command writes to each path
17459
+ * (`cat > /tmp/x.sh <<'SH' ... SH`, `echo '...' > f`, `>>` appends), so
17460
+ * a later statement that runs the path (`bash f`, `sh f`, `./f`) is
17461
+ * judged on what it runs. null once a write to the path is unknown. */
17462
+ written = /* @__PURE__ */ new Map();
17093
17463
  /** `for NAME in a b c`: the literal items, or null when one is dynamic. */
17094
17464
  loopVars = /* @__PURE__ */ new Map();
17095
17465
  /** Walk one command string. Returns false when it did not parse. */
@@ -17108,6 +17478,25 @@ zoo`.split("\n");
17108
17478
  const type = n.kind;
17109
17479
  if (type === "Stmt") {
17110
17480
  this.visitHeredocShell(n, depth);
17481
+ this.visitPipeline(n, depth);
17482
+ this.visitHookWrite(n);
17483
+ this.recordScriptWrite(n);
17484
+ this.visitWrittenScript(n, depth);
17485
+ return true;
17486
+ }
17487
+ if (type === "Assign" && n.Name?.Value && n.Value) {
17488
+ const name = n.Name.Value;
17489
+ if (n.Append) {
17490
+ const prev = this.appended.has(name) ? this.appended.get(name) : resolveWordLiteral(n.Value) === null ? null : void 0;
17491
+ const base2 = prev === void 0 ? recordedExpansion(name) : prev;
17492
+ const piece = resolveWordLiteral(n.Value);
17493
+ this.appended.set(name, typeof base2 === "string" && piece !== null ? base2 + piece : null);
17494
+ this.treeTopVars.delete(name);
17495
+ return true;
17496
+ }
17497
+ this.appended.delete(name);
17498
+ if (treeTopWord(n.Value, this.treeTopVars)) this.treeTopVars.add(name);
17499
+ else this.treeTopVars.delete(name);
17111
17500
  return true;
17112
17501
  }
17113
17502
  if (type === "ForClause") {
@@ -17136,13 +17525,13 @@ zoo`.split("\n");
17136
17525
  const param = plainParamName(rawArgs[loopArg]);
17137
17526
  for (const item of this.loopVars.get(param.name)) {
17138
17527
  const words2 = rawArgs.flatMap(
17139
- (a, i) => i === loopArg ? param.quoted ? [item] : splitFields(item) : resolveGitWords(a)
17528
+ (a, i) => i === loopArg ? param.quoted ? [item] : splitFields(item) : resolveGitWords(a, this.treeTopVars, this.appended)
17140
17529
  );
17141
17530
  this.visitWords(words2, new Map(prefixEnv), depth, false);
17142
17531
  }
17143
17532
  return true;
17144
17533
  }
17145
- const words = rawArgs.flatMap((a) => resolveGitWords(a));
17534
+ const words = rawArgs.flatMap((a) => resolveGitWords(a, this.treeTopVars, this.appended));
17146
17535
  this.visitWords(words, prefixEnv, depth, false);
17147
17536
  return true;
17148
17537
  }
@@ -17167,6 +17556,23 @@ zoo`.split("\n");
17167
17556
  if (f) this.found(f);
17168
17557
  return;
17169
17558
  }
17559
+ if (headWord === "rm" || headWord === "unlink") {
17560
+ if (words.slice(head + 1).some((w) => w !== null && isLiveHookFile(w)))
17561
+ this.found(hooksBypassed(shownOf(words)));
17562
+ return;
17563
+ }
17564
+ if (headWord === "mv") {
17565
+ const operands2 = words.slice(head + 1).filter((w) => w === null || !w.startsWith("-"));
17566
+ const src = operands2[0];
17567
+ if (typeof src === "string" && isLiveHookFile(src)) this.found(hooksBypassed(shownOf(words)));
17568
+ return;
17569
+ }
17570
+ if (headWord === "perl") {
17571
+ const code = words.slice(head + 1).find((w) => w !== null && /\brename\b/.test(w));
17572
+ const m = code ? /rename\s*\(?\s*q[qw]?\s*[(\[{<'"]([^)\]}>'"]+)/.exec(code) : null;
17573
+ if (m && isLiveHookFile(m[1])) this.found(hooksBypassed(shownOf(words)));
17574
+ return;
17575
+ }
17170
17576
  if (depth < MAX_PAYLOAD_DEPTH2) {
17171
17577
  const payload = literalShellPayload(words, baseWord(words[0]));
17172
17578
  if (payload !== null) this.runPayload(payload, depth);
@@ -17177,6 +17583,127 @@ zoo`.split("\n");
17177
17583
  this.payloads.add(payload);
17178
17584
  this.run(payload, depth + 1);
17179
17585
  }
17586
+ /**
17587
+ * node9Firewall#385: a pipeline whose last stage reads its pathspec from
17588
+ * stdin (`git add --pathspec-from-file=-`) or runs git over the names it
17589
+ * is fed (`xargs -0 git add --`), with a producer that is one literal
17590
+ * (`git rev-parse --show-toplevel`, `printf '%s\0' .`): the literal is
17591
+ * the operand, and the call is judged with it.
17592
+ */
17593
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17594
+ visitPipeline(stmt, depth) {
17595
+ const cmd = stmt.Cmd;
17596
+ if (!cmd || cmd.kind !== "BinaryCmd" || !isPipeOp(cmd.Op)) return;
17597
+ const stages = [];
17598
+ let cur = cmd;
17599
+ while (cur?.kind === "BinaryCmd" && isPipeOp(cur.Op)) {
17600
+ stages.unshift(cur.Y);
17601
+ const left = cur.X;
17602
+ if (left?.Cmd?.kind === "BinaryCmd" && isPipeOp(left.Cmd.Op)) cur = left.Cmd;
17603
+ else {
17604
+ stages.unshift(left);
17605
+ break;
17606
+ }
17607
+ }
17608
+ if (stages.length < 2) return;
17609
+ const producer = stages[stages.length - 2]?.Cmd;
17610
+ const consumer = stages[stages.length - 1]?.Cmd;
17611
+ if (!producer || !consumer || producer.kind !== "CallExpr" || consumer.kind !== "CallExpr")
17612
+ return;
17613
+ const fed = producedLiteral(producer);
17614
+ if (fed === null) return;
17615
+ const words = (consumer.Args || []).flatMap(
17616
+ (a) => resolveGitWords(a, this.treeTopVars, this.appended)
17617
+ );
17618
+ const head = unwrapCommandHead(words);
17619
+ const viaXargs = words.slice(0, head).some((w) => baseWord(w) === "xargs");
17620
+ const fromFile = words.findIndex(
17621
+ (w) => w === "--pathspec-from-file=-" || w === "--pathspec-from-file"
17622
+ );
17623
+ if (!viaXargs && fromFile < 0) return;
17624
+ let fedWords;
17625
+ if (fromFile >= 0) {
17626
+ fedWords = words.filter(
17627
+ (w, k) => !(k === fromFile || w === "-" && words[k - 1] === "--pathspec-from-file")
17628
+ );
17629
+ fedWords.push(fed);
17630
+ } else fedWords = [...words, fed];
17631
+ this.visitWords(fedWords, /* @__PURE__ */ new Map(), depth, false);
17632
+ }
17633
+ /**
17634
+ * node9Firewall#385 (e): a hook file written with a body that checks
17635
+ * nothing (`cat > .githooks/pre-commit <<'SH' exit 0 SH`, `echo 'exit 0'
17636
+ * > .git/hooks/pre-push`, `: > .githooks/pre-commit`). A real body (a
17637
+ * test, a linter) is ordinary work on the hooks.
17638
+ */
17639
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17640
+ visitHookWrite(stmt) {
17641
+ const cmd = stmt.Cmd;
17642
+ if (!cmd || cmd.kind !== "CallExpr") return;
17643
+ const redirs = stmt.Redirs || [];
17644
+ const target = redirs.find((r) => r?.Word && !r.Hdoc && (r.N == null || r.N.Value === "1"));
17645
+ if (!target) return;
17646
+ const path210 = resolveWordLiteral(target.Word);
17647
+ if (path210 === null || !isLiveHookFile(path210)) return;
17648
+ const hdoc = redirs.find((r) => r?.Hdoc);
17649
+ const words = (cmd.Args || []).map((a) => resolveWordLiteral(a));
17650
+ const head = baseWord(words[0]);
17651
+ let body = null;
17652
+ if (hdoc) body = resolveWordLiteral(hdoc.Hdoc);
17653
+ else if (head === ":" || head === "true" || words.length === 0) body = "";
17654
+ else if (head === "echo" || head === "printf") {
17655
+ const rest = words.slice(1);
17656
+ body = rest.some((w) => w === null) ? null : rest.filter((w) => !/^-[neE]+$/.test(w)).join(" ");
17657
+ if (body !== null) body = body.replace(/\\n/g, "\n");
17658
+ }
17659
+ if (body !== null && hookBodyIsEmpty(body)) this.found(hooksBypassed(shownOf(words)));
17660
+ }
17661
+ /** A statement that writes a literal text to a path, by redirect. */
17662
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17663
+ recordScriptWrite(stmt) {
17664
+ const cmd = stmt.Cmd;
17665
+ if (!cmd || cmd.kind !== "CallExpr") return;
17666
+ const redirs = stmt.Redirs || [];
17667
+ const target = redirs.find((r) => r?.Word && !r.Hdoc && (r.N == null || r.N.Value === "1"));
17668
+ if (!target) return;
17669
+ const path210 = resolveWordLiteral(target.Word);
17670
+ if (path210 === null) return;
17671
+ const append = /^>>/.test(target.Op?.toString() ?? "") || redirOpIsAppend(target);
17672
+ const hdoc = redirs.find((r) => r?.Hdoc);
17673
+ const words = (cmd.Args || []).map((a) => resolveWordLiteral(a));
17674
+ const head = baseWord(words[0]);
17675
+ let text = null;
17676
+ if (hdoc && (head === "cat" || head === "tee")) text = resolveWordLiteral(hdoc.Hdoc);
17677
+ else if (head === "echo" || head === "printf") {
17678
+ const rest = words.slice(1);
17679
+ text = rest.some((w) => w === null) ? null : rest.filter((w) => !/^-[neE]+$/.test(w)).join(" ");
17680
+ if (text !== null && head === "printf") text = text.replace(/\\n/g, "\n");
17681
+ } else return;
17682
+ const prev = this.written.get(path210);
17683
+ if (text === null || append && prev === null) this.written.set(path210, null);
17684
+ else this.written.set(path210, append && typeof prev === "string" ? `${prev}
17685
+ ${text}` : text);
17686
+ }
17687
+ /** A statement that runs a path this command wrote: the text is the script. */
17688
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17689
+ visitWrittenScript(stmt, depth) {
17690
+ const cmd = stmt.Cmd;
17691
+ if (!cmd || cmd.kind !== "CallExpr" || this.written.size === 0 || depth >= MAX_PAYLOAD_DEPTH2)
17692
+ return;
17693
+ const words = (cmd.Args || []).map((a) => resolveWordLiteral(a));
17694
+ const head = unwrapCommandHead(words);
17695
+ const name = baseWord(words[head]);
17696
+ let ran = null;
17697
+ if (PAYLOAD_SHELLS.has(name) && name !== "eval") {
17698
+ const operand = words.slice(head + 1).find((w) => w === null || !w.startsWith("-"));
17699
+ if (typeof operand === "string") ran = operand;
17700
+ } else if (typeof words[head] === "string" && this.written.has(words[head])) {
17701
+ ran = words[head];
17702
+ }
17703
+ if (ran === null) return;
17704
+ const text = this.written.get(ran);
17705
+ if (typeof text === "string") this.runPayload(text, depth);
17706
+ }
17180
17707
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
17181
17708
  visitHeredocShell(stmt, depth) {
17182
17709
  const cmd = stmt.Cmd;
@@ -17328,6 +17855,8 @@ zoo`.split("\n");
17328
17855
  }
17329
17856
  };
17330
17857
  POSITIONAL2 = "\0positional";
17858
+ NO_VARS = /* @__PURE__ */ new Set();
17859
+ NO_APPENDS = /* @__PURE__ */ new Map();
17331
17860
  FORCE_PUSH = {
17332
17861
  checkId: "commands.git-destructive",
17333
17862
  ruleName: "review-force-push",
@@ -17386,6 +17915,8 @@ zoo`.split("\n");
17386
17915
  "--cleanup"
17387
17916
  ]);
17388
17917
  CLEAN_LONG_VALUE = /* @__PURE__ */ new Set(["--exclude"]);
17918
+ RESTORE_LONG_VALUE = /* @__PURE__ */ new Set(["--source", "--pathspec-from-file"]);
17919
+ CHECKOUT_LONG_VALUE = /* @__PURE__ */ new Set(["--pathspec-from-file", "--orphan", "--conflict"]);
17389
17920
  HOOKS_BYPASS = {
17390
17921
  checkId: "commands.git-hooks-bypass",
17391
17922
  ruleName: "review-git-hooks-bypass",
@@ -17411,6 +17942,7 @@ zoo`.split("\n");
17411
17942
  "--gpg-sign"
17412
17943
  ]);
17413
17944
  CHMOD_OPTION_RE = /^-[RvcfHLP]+$/;
17945
+ _appendOp = null;
17414
17946
  STAGE_ALL = {
17415
17947
  checkId: "commands.git-stage-all",
17416
17948
  ruleName: "review-git-stage-all",
@@ -17535,7 +18067,7 @@ zoo`.split("\n");
17535
18067
  this.visitStmt(n, depth);
17536
18068
  return true;
17537
18069
  }
17538
- if (type === "BinaryCmd" && isPipeOp(n.Op)) {
18070
+ if (type === "BinaryCmd" && isPipeOp2(n.Op)) {
17539
18071
  this.visitPipeline(n);
17540
18072
  return true;
17541
18073
  }
@@ -17636,7 +18168,7 @@ zoo`.split("\n");
17636
18168
  const stages = [];
17637
18169
  const flatten = (s) => {
17638
18170
  const c = s?.Cmd;
17639
- if (c && c.kind === "BinaryCmd" && isPipeOp(c.Op)) {
18171
+ if (c && c.kind === "BinaryCmd" && isPipeOp2(c.Op)) {
17640
18172
  flatten(c.X);
17641
18173
  flatten(c.Y);
17642
18174
  } else stages.push(s);
@@ -19265,11 +19797,18 @@ function slug(s) {
19265
19797
  var B = "[\\s/\\\\]";
19266
19798
  var SEP = "[/\\\\]";
19267
19799
  function pathToRegexFragment(rawPath) {
19268
- const tail = rawPath.trim().replace(/^~[\\/]?/, "").replace(/^\$\{?HOME\}?[\\/]?/, "").replace(/^\/(?:home|Users)\/[^\\/]+[\\/]?/, "").replace(/^[A-Za-z]:[\\/]Users[\\/][^\\/]+[\\/]?/, "").replace(/^[\\/]+/, "").replace(/[\\/]+$/, "");
19800
+ const trimmed = rawPath.trim();
19801
+ const tail = trimmed.replace(/^~[\\/]?/, "").replace(/^\$\{?HOME\}?[\\/]?/, "").replace(/^\/(?:home|Users)\/[^\\/]+[\\/]?/, "").replace(/^[A-Za-z]:[\\/]Users[\\/][^\\/]+[\\/]?/, "").replace(/^[\\/]+/, "").replace(/[\\/]+$/, "");
19269
19802
  const segments2 = tail.split(/[\\/]+/).filter(Boolean).map(escapeRegex);
19270
19803
  if (segments2.length === 0) return "";
19804
+ if (isAbsoluteOutsideHome(trimmed)) {
19805
+ return `(^|[^A-Za-z0-9_.~\\/-])/${segments2.join(SEP)}(${B}|$)`;
19806
+ }
19271
19807
  return `(^|${B})${segments2.join(SEP)}(${B}|$)`;
19272
19808
  }
19809
+ function isAbsoluteOutsideHome(p) {
19810
+ return p.startsWith("/") && !/^\/(?:home|Users|root)(?:\/|$)/.test(p);
19811
+ }
19273
19812
  function pathMatchesFragment(candidate, rawPath) {
19274
19813
  const value = pathToRegexFragment(rawPath);
19275
19814
  if (!value || !candidate) return false;
@@ -22303,7 +22842,8 @@ var DEFAULT_CONFIG = {
22303
22842
  trustedHostsManaged: false,
22304
22843
  appPermissions: {},
22305
22844
  appLinks: {},
22306
- managedJailPaths: []
22845
+ managedJailPaths: [],
22846
+ jailPaths: []
22307
22847
  },
22308
22848
  environments: {}
22309
22849
  };
@@ -22543,7 +23083,8 @@ function getConfig(cwd) {
22543
23083
  trustedHostsManaged: false,
22544
23084
  appPermissions: {},
22545
23085
  appLinks: {},
22546
- managedJailPaths: []
23086
+ managedJailPaths: [],
23087
+ jailPaths: []
22547
23088
  };
22548
23089
  const mergedEnvironments = { ...DEFAULT_CONFIG.environments };
22549
23090
  const rank = (v) => {
@@ -22938,6 +23479,7 @@ function getConfig(cwd) {
22938
23479
  mergedPolicy.smartRules.push({ ...r, name: `org:${r.name}` });
22939
23480
  }
22940
23481
  mergedPolicy.managedJailPaths.push({ path: path41, verdict });
23482
+ mergedPolicy.jailPaths.push(path41);
22941
23483
  }
22942
23484
  }
22943
23485
  if (Array.isArray(mc.trustedHosts)) {
@@ -23013,6 +23555,11 @@ function getConfig(cwd) {
23013
23555
  }
23014
23556
  }
23015
23557
  mergedPolicy.appliedShields = appliedShields.sort();
23558
+ if (appliedShields.includes(USER_JAIL_SHIELD)) {
23559
+ for (const { path: path41 } of readJailPaths()) {
23560
+ if (!mergedPolicy.jailPaths.includes(path41)) mergedPolicy.jailPaths.push(path41);
23561
+ }
23562
+ }
23016
23563
  const existingAdvisoryNames = new Set(mergedPolicy.smartRules.map((r) => r.name));
23017
23564
  const cc = mergedPolicy.commandChecks ?? {};
23018
23565
  const advisoryKnobKey = (name) => {