@node9/proxy 2.22.1 → 2.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2352 -1826
- package/dist/cli.mjs +2279 -1753
- package/dist/dashboard.mjs +468 -88
- package/dist/index.js +293 -24
- package/dist/index.mjs +293 -24
- package/dist/scan-ink.mjs +12 -0
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1595,6 +1595,7 @@ function normalizeCommandForPolicyImpl(command) {
|
|
|
1595
1595
|
const source = command.slice(s, e);
|
|
1596
1596
|
if (resolved === source) continue;
|
|
1597
1597
|
if (resolved === "" || /\s/.test(resolved)) continue;
|
|
1598
|
+
if (/^[;&|()<>]+$/.test(resolved)) continue;
|
|
1598
1599
|
rewrites.push([s, e, resolved]);
|
|
1599
1600
|
const quoteOnly = source.replace(/['"]/g, "");
|
|
1600
1601
|
if (quoteOnly !== source) quoteOnlyRewrites.push([s, e, quoteOnly]);
|
|
@@ -2611,6 +2612,60 @@ var NET_BINARIES = /* @__PURE__ */ new Set([
|
|
|
2611
2612
|
"rsync"
|
|
2612
2613
|
]);
|
|
2613
2614
|
var VALUE_FLAGS = {
|
|
2615
|
+
// rsync 3.2.7, its own --help: every flag whose operand could be mistaken for
|
|
2616
|
+
// a host. `-e ssh` is the one that matters most (`ssh` is not the destination).
|
|
2617
|
+
rsync: /* @__PURE__ */ new Set([
|
|
2618
|
+
"-e",
|
|
2619
|
+
"--rsh",
|
|
2620
|
+
"-f",
|
|
2621
|
+
"--filter",
|
|
2622
|
+
"-T",
|
|
2623
|
+
"--temp-dir",
|
|
2624
|
+
"-B",
|
|
2625
|
+
"--block-size",
|
|
2626
|
+
"-M",
|
|
2627
|
+
"--remote-option",
|
|
2628
|
+
"--exclude",
|
|
2629
|
+
"--exclude-from",
|
|
2630
|
+
"--include",
|
|
2631
|
+
"--include-from",
|
|
2632
|
+
"--files-from",
|
|
2633
|
+
"--compare-dest",
|
|
2634
|
+
"--copy-dest",
|
|
2635
|
+
"--link-dest",
|
|
2636
|
+
"--partial-dir",
|
|
2637
|
+
"--log-file",
|
|
2638
|
+
"--password-file",
|
|
2639
|
+
"--bwlimit",
|
|
2640
|
+
"--timeout",
|
|
2641
|
+
"--contimeout",
|
|
2642
|
+
"--port",
|
|
2643
|
+
"--sockopts",
|
|
2644
|
+
"--address",
|
|
2645
|
+
"--chmod",
|
|
2646
|
+
"--chown",
|
|
2647
|
+
"--max-size",
|
|
2648
|
+
"--min-size",
|
|
2649
|
+
"--modify-window",
|
|
2650
|
+
"--out-format",
|
|
2651
|
+
"--log-file-format",
|
|
2652
|
+
"--backup-dir",
|
|
2653
|
+
"--suffix",
|
|
2654
|
+
"--iconv",
|
|
2655
|
+
"--max-delete",
|
|
2656
|
+
"--checksum-choice",
|
|
2657
|
+
"--info",
|
|
2658
|
+
"--debug",
|
|
2659
|
+
"--stderr",
|
|
2660
|
+
"--outbuf",
|
|
2661
|
+
"--skip-compress",
|
|
2662
|
+
"--usermap",
|
|
2663
|
+
"--groupmap",
|
|
2664
|
+
"--mkpath",
|
|
2665
|
+
"--write-batch",
|
|
2666
|
+
"--read-batch",
|
|
2667
|
+
"--only-write-batch"
|
|
2668
|
+
]),
|
|
2614
2669
|
curl: /* @__PURE__ */ new Set([
|
|
2615
2670
|
"-d",
|
|
2616
2671
|
"--data",
|
|
@@ -2694,23 +2749,163 @@ var VALUE_FLAGS = {
|
|
|
2694
2749
|
]),
|
|
2695
2750
|
nc: /* @__PURE__ */ new Set(["-p", "-s", "-w", "-X", "-x", "-e", "-g", "-G", "-i", "-O", "-T", "-q", "-m"])
|
|
2696
2751
|
};
|
|
2752
|
+
var HOME_VARIABLES = /* @__PURE__ */ new Set(["HOME", "USERPROFILE"]);
|
|
2753
|
+
var assignmentTable = null;
|
|
2754
|
+
var currentStmtOffset = Number.MAX_SAFE_INTEGER;
|
|
2755
|
+
var PAYLOAD_BUDGET = 256;
|
|
2756
|
+
var payloadBudget = 0;
|
|
2757
|
+
var seenPayloads = null;
|
|
2758
|
+
function payloadKey(payload) {
|
|
2759
|
+
if (!assignmentTable || assignmentTable.size === 0) return payload;
|
|
2760
|
+
const bindings = [];
|
|
2761
|
+
for (const [name, rec] of assignmentTable) {
|
|
2762
|
+
if (rec.value !== null) bindings.push(`${name}=${rec.value}`);
|
|
2763
|
+
}
|
|
2764
|
+
return `${payload}\0${bindings.sort().join("")}`;
|
|
2765
|
+
}
|
|
2766
|
+
function claimPayload(payload) {
|
|
2767
|
+
if (!seenPayloads) return "ok";
|
|
2768
|
+
const key = payloadKey(payload);
|
|
2769
|
+
if (seenPayloads.has(key)) return "seen";
|
|
2770
|
+
if (payloadBudget <= 0) return "exhausted";
|
|
2771
|
+
payloadBudget--;
|
|
2772
|
+
seenPayloads.add(key);
|
|
2773
|
+
return "ok";
|
|
2774
|
+
}
|
|
2775
|
+
var UNANALYSABLE_NESTING = {
|
|
2776
|
+
ruleName: "review-unanalysable-nesting",
|
|
2777
|
+
verdict: "review",
|
|
2778
|
+
reason: "This command nests more wrapped shell payloads than the policy engine will unwrap, so some of what it runs was not read.",
|
|
2779
|
+
path: ""
|
|
2780
|
+
};
|
|
2781
|
+
var ASSIGNMENT_HEADS = /* @__PURE__ */ new Set(["export", "declare", "local", "readonly", "typeset"]);
|
|
2782
|
+
function recordTopLevelAssignments(f) {
|
|
2783
|
+
const stmts = Array.isArray(f?.Stmts) ? f.Stmts : [];
|
|
2784
|
+
for (const stmt of stmts) recordTopLevelStmt(stmt);
|
|
2785
|
+
}
|
|
2786
|
+
function probeBinOp(src) {
|
|
2787
|
+
try {
|
|
2788
|
+
const cmd = syntax.NewParser().Parse(src, "probe")?.Stmts?.[0]?.Cmd;
|
|
2789
|
+
if (cmd && syntax.NodeType(cmd) === "BinaryCmd") return cmd.Op;
|
|
2790
|
+
} catch {
|
|
2791
|
+
}
|
|
2792
|
+
return null;
|
|
2793
|
+
}
|
|
2794
|
+
var AND_OP = probeBinOp("a && b");
|
|
2795
|
+
var OR_OP = probeBinOp("a || b");
|
|
2796
|
+
var AND_OR_OPS = new Set([AND_OP, OR_OP].filter((o) => o !== null));
|
|
2797
|
+
function recordTopLevelStmt(stmt) {
|
|
2798
|
+
if (!stmt || !stmt.Cmd) return false;
|
|
2799
|
+
const t = syntax.NodeType(stmt.Cmd);
|
|
2800
|
+
if (t === "BinaryCmd") {
|
|
2801
|
+
if (!AND_OR_OPS.has(stmt.Cmd.Op)) return false;
|
|
2802
|
+
if (!recordTopLevelStmt(stmt.Cmd.X)) return false;
|
|
2803
|
+
if (stmt.Cmd.Op === AND_OP) return recordTopLevelStmt(stmt.Cmd.Y);
|
|
2804
|
+
return true;
|
|
2805
|
+
}
|
|
2806
|
+
if (t !== "CallExpr" && t !== "DeclClause") return false;
|
|
2807
|
+
let at = 0;
|
|
2808
|
+
try {
|
|
2809
|
+
at = stmt.Pos().Offset();
|
|
2810
|
+
} catch {
|
|
2811
|
+
at = 0;
|
|
2812
|
+
}
|
|
2813
|
+
recordAssignments(stmt.Cmd, at);
|
|
2814
|
+
if (stmt.Negated) return false;
|
|
2815
|
+
if (t === "DeclClause") return ASSIGNMENT_HEADS.has(stmt.Cmd.Variant?.Value ?? "");
|
|
2816
|
+
return (stmt.Cmd.Args || []).length === 0 && (stmt.Cmd.Assigns || []).length > 0;
|
|
2817
|
+
}
|
|
2818
|
+
function recordAssignments(n, at) {
|
|
2819
|
+
if (!assignmentTable) return;
|
|
2820
|
+
const t = syntax.NodeType(n);
|
|
2821
|
+
let assigns = [];
|
|
2822
|
+
if (t === "CallExpr") {
|
|
2823
|
+
if ((n.Args || []).length > 0) return;
|
|
2824
|
+
assigns = n.Assigns || [];
|
|
2825
|
+
} else if (t === "DeclClause") {
|
|
2826
|
+
if (!ASSIGNMENT_HEADS.has(n.Variant?.Value ?? "")) return;
|
|
2827
|
+
assigns = (n.Args || []).filter((a) => syntax.NodeType(a) === "Assign");
|
|
2828
|
+
} else return;
|
|
2829
|
+
for (const a of assigns) {
|
|
2830
|
+
const name = a?.Name?.Value;
|
|
2831
|
+
if (!name || !a.Value || a.Append) continue;
|
|
2832
|
+
assignmentTable.set(name, { value: resolveWordLiteral(a.Value), at });
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
function resolveTrivialSubst(part) {
|
|
2836
|
+
if (syntax.NodeType(part) !== "CmdSubst") return void 0;
|
|
2837
|
+
const stmts = part.Stmts || [];
|
|
2838
|
+
if (stmts.length !== 1) return void 0;
|
|
2839
|
+
const st = stmts[0];
|
|
2840
|
+
if ((st.Redirs || []).length > 0 || st.Negated || st.Background) return void 0;
|
|
2841
|
+
const cmd = st.Cmd;
|
|
2842
|
+
if (!cmd || syntax.NodeType(cmd) !== "CallExpr") return void 0;
|
|
2843
|
+
if ((cmd.Assigns || []).length > 0) return void 0;
|
|
2844
|
+
const words = (cmd.Args || []).map((a) => resolveWordLiteral(a));
|
|
2845
|
+
if (words.length === 0 || words.some((w) => w === null)) return void 0;
|
|
2846
|
+
const head = baseWord(words[0]);
|
|
2847
|
+
const rest = words.slice(1);
|
|
2848
|
+
if (head === "echo") {
|
|
2849
|
+
let i = 0;
|
|
2850
|
+
while (i < rest.length && /^-[neE]+$/.test(rest[i])) i++;
|
|
2851
|
+
return rest.slice(i).join(" ");
|
|
2852
|
+
}
|
|
2853
|
+
if (head === "printf") {
|
|
2854
|
+
if (rest.length !== 2) return void 0;
|
|
2855
|
+
if (!/^%s(\\n)?$/.test(rest[0])) return void 0;
|
|
2856
|
+
return rest[1];
|
|
2857
|
+
}
|
|
2858
|
+
return void 0;
|
|
2859
|
+
}
|
|
2860
|
+
function recordedExpansion(name) {
|
|
2861
|
+
if (!assignmentTable || !name) return void 0;
|
|
2862
|
+
const rec = assignmentTable.get(name);
|
|
2863
|
+
if (rec === void 0 || rec.at >= currentStmtOffset) return void 0;
|
|
2864
|
+
return rec.value;
|
|
2865
|
+
}
|
|
2866
|
+
function isPlainParam(p) {
|
|
2867
|
+
if (syntax.NodeType(p) !== "ParamExp") return false;
|
|
2868
|
+
return !(p.Excl || p.Length || p.Width || p.Index || p.Slice || p.Repl || p.Exp);
|
|
2869
|
+
}
|
|
2870
|
+
function expandPlainParam(p) {
|
|
2871
|
+
if (!assignmentTable) return void 0;
|
|
2872
|
+
if (!isPlainParam(p)) return void 0;
|
|
2873
|
+
const recorded = recordedExpansion(p.Param?.Value);
|
|
2874
|
+
if (recorded !== void 0) return recorded;
|
|
2875
|
+
return HOME_VARIABLES.has(p.Param?.Value) ? "~" : void 0;
|
|
2876
|
+
}
|
|
2697
2877
|
function resolveWordLiteral(w) {
|
|
2698
2878
|
const parts = w?.Parts || [];
|
|
2699
2879
|
let s = "";
|
|
2700
2880
|
for (const p of parts) {
|
|
2701
|
-
const
|
|
2702
|
-
if (
|
|
2703
|
-
|
|
2704
|
-
else if (t === "DblQuoted") {
|
|
2705
|
-
const inner = p.Parts || [];
|
|
2706
|
-
if (!inner.every((ip) => syntax.NodeType(ip) === "Lit")) return null;
|
|
2707
|
-
s += inner.map((ip) => ip.Value ?? "").join("");
|
|
2708
|
-
} else {
|
|
2709
|
-
return null;
|
|
2710
|
-
}
|
|
2881
|
+
const piece = resolvePart(p, false);
|
|
2882
|
+
if (piece === void 0 || piece === null) return null;
|
|
2883
|
+
s += piece;
|
|
2711
2884
|
}
|
|
2712
2885
|
return s;
|
|
2713
2886
|
}
|
|
2887
|
+
function resolvePart(p, inQuotes) {
|
|
2888
|
+
const t = syntax.NodeType(p);
|
|
2889
|
+
if (t === "Lit") {
|
|
2890
|
+
const raw = p.Value ?? "";
|
|
2891
|
+
if (!inQuotes) return raw.replace(/\\(.)/g, "$1");
|
|
2892
|
+
return assignmentTable ? raw.replace(/\\([$`"\\])/g, "$1") : raw;
|
|
2893
|
+
}
|
|
2894
|
+
if (t === "SglQuoted") return p.Value ?? "";
|
|
2895
|
+
if (t === "ParamExp") return expandPlainParam(p);
|
|
2896
|
+
if (t === "CmdSubst") return assignmentTable ? resolveTrivialSubst(p) : void 0;
|
|
2897
|
+
if (t === "DblQuoted" && !inQuotes) {
|
|
2898
|
+
const inner = p.Parts || [];
|
|
2899
|
+
let out = "";
|
|
2900
|
+
for (const ip of inner) {
|
|
2901
|
+
const piece = resolvePart(ip, true);
|
|
2902
|
+
if (piece === void 0 || piece === null) return piece;
|
|
2903
|
+
out += piece;
|
|
2904
|
+
}
|
|
2905
|
+
return out;
|
|
2906
|
+
}
|
|
2907
|
+
return void 0;
|
|
2908
|
+
}
|
|
2714
2909
|
function parseDestHost(token) {
|
|
2715
2910
|
if (!token) return null;
|
|
2716
2911
|
let t = token.trim();
|
|
@@ -2765,6 +2960,7 @@ function destTokensForBinary(binary, args) {
|
|
|
2765
2960
|
case "ssh":
|
|
2766
2961
|
return positionals.slice(0, 1);
|
|
2767
2962
|
case "scp":
|
|
2963
|
+
case "rsync":
|
|
2768
2964
|
return positionals.filter((p) => p.includes(":") || p.includes("@"));
|
|
2769
2965
|
case "nc":
|
|
2770
2966
|
case "ncat":
|
|
@@ -2965,17 +3161,35 @@ function analyzeFsOperationImpl(command, depth = 0) {
|
|
|
2965
3161
|
const f = parseShared(command);
|
|
2966
3162
|
if (f === PARSE_FAIL) return null;
|
|
2967
3163
|
let result = null;
|
|
3164
|
+
const outerTable = assignmentTable;
|
|
3165
|
+
const outerOffset = currentStmtOffset;
|
|
3166
|
+
const outerSeen = seenPayloads;
|
|
3167
|
+
const outerBudget = payloadBudget;
|
|
3168
|
+
if (depth === 0) {
|
|
3169
|
+
seenPayloads = /* @__PURE__ */ new Set();
|
|
3170
|
+
payloadBudget = PAYLOAD_BUDGET;
|
|
3171
|
+
}
|
|
3172
|
+
assignmentTable = new Map(
|
|
3173
|
+
[...outerTable ?? []].map(([k, r]) => [k, { value: r.value, at: -1 }])
|
|
3174
|
+
);
|
|
3175
|
+
currentStmtOffset = Number.MAX_SAFE_INTEGER;
|
|
2968
3176
|
try {
|
|
3177
|
+
recordTopLevelAssignments(f);
|
|
2969
3178
|
syntax.Walk(f, (node) => {
|
|
2970
3179
|
if (!node || result?.verdict === "block") return false;
|
|
2971
3180
|
const n = node;
|
|
2972
3181
|
const nodeType = syntax.NodeType(n);
|
|
2973
3182
|
if (nodeType === "Stmt") {
|
|
3183
|
+
try {
|
|
3184
|
+
currentStmtOffset = n.Pos().Offset();
|
|
3185
|
+
} catch {
|
|
3186
|
+
currentStmtOffset = Number.MAX_SAFE_INTEGER;
|
|
3187
|
+
}
|
|
2974
3188
|
result = stricter(result, jailedRedirectRead(n));
|
|
2975
3189
|
return result?.verdict !== "block";
|
|
2976
3190
|
}
|
|
2977
3191
|
if (nodeType !== "CallExpr") return true;
|
|
2978
|
-
const { name, flags, paths, words
|
|
3192
|
+
const { name, flags, paths, words } = extractLiteralArgs(n);
|
|
2979
3193
|
if (!name) return true;
|
|
2980
3194
|
if (name === "rm") {
|
|
2981
3195
|
const flagStr = flags.join("").toLowerCase();
|
|
@@ -3004,9 +3218,30 @@ function analyzeFsOperationImpl(command, depth = 0) {
|
|
|
3004
3218
|
}
|
|
3005
3219
|
}
|
|
3006
3220
|
}
|
|
3007
|
-
if (depth <
|
|
3221
|
+
if (depth < 24 && name === "find") {
|
|
3222
|
+
for (const action of findActions(words, 0)) {
|
|
3223
|
+
const h = unwrapCommandHead(action);
|
|
3224
|
+
const inner = literalShellPayload(action.slice(h), baseWord(action[h]));
|
|
3225
|
+
if (inner === null) continue;
|
|
3226
|
+
const claim = claimPayload(inner);
|
|
3227
|
+
if (claim === "exhausted") {
|
|
3228
|
+
result = stricter(result, UNANALYSABLE_NESTING);
|
|
3229
|
+
continue;
|
|
3230
|
+
}
|
|
3231
|
+
if (claim === "seen") continue;
|
|
3232
|
+
const v = analyzeFsOperationImpl(inner, depth + 1);
|
|
3233
|
+
result = stricter(result, v);
|
|
3234
|
+
if (result?.verdict === "block") return false;
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
if (depth < 24) {
|
|
3008
3238
|
const payload = literalShellPayload(words, name);
|
|
3009
|
-
|
|
3239
|
+
const claim = payload === null ? "seen" : claimPayload(payload);
|
|
3240
|
+
if (claim === "exhausted") {
|
|
3241
|
+
result = stricter(result, UNANALYSABLE_NESTING);
|
|
3242
|
+
return true;
|
|
3243
|
+
}
|
|
3244
|
+
if (payload !== null && claim === "ok") {
|
|
3010
3245
|
const inner = analyzeFsOperationImpl(payload, depth + 1);
|
|
3011
3246
|
if (inner) {
|
|
3012
3247
|
result = inner;
|
|
@@ -3015,7 +3250,7 @@ function analyzeFsOperationImpl(command, depth = 0) {
|
|
|
3015
3250
|
return true;
|
|
3016
3251
|
}
|
|
3017
3252
|
}
|
|
3018
|
-
const readPaths = FS_READ_TOOLS.has(name) ?
|
|
3253
|
+
const readPaths = FS_READ_TOOLS.has(name) ? readerPaths(words, 0) : wrappedReadPaths(words, name);
|
|
3019
3254
|
if (readPaths) {
|
|
3020
3255
|
for (const p of readPaths) {
|
|
3021
3256
|
result = stricter(result, matchSensitivePath2(p));
|
|
@@ -3030,6 +3265,11 @@ function analyzeFsOperationImpl(command, depth = 0) {
|
|
|
3030
3265
|
return result;
|
|
3031
3266
|
} catch {
|
|
3032
3267
|
return null;
|
|
3268
|
+
} finally {
|
|
3269
|
+
assignmentTable = outerTable;
|
|
3270
|
+
currentStmtOffset = outerOffset;
|
|
3271
|
+
seenPayloads = outerSeen;
|
|
3272
|
+
if (depth === 0) payloadBudget = outerBudget;
|
|
3033
3273
|
}
|
|
3034
3274
|
}
|
|
3035
3275
|
function stricter(a, b) {
|
|
@@ -3087,6 +3327,18 @@ function resolveCopyShape(words, h) {
|
|
|
3087
3327
|
return null;
|
|
3088
3328
|
}
|
|
3089
3329
|
var FIND_OPTIONS = /* @__PURE__ */ new Set(["-H", "-L", "-P"]);
|
|
3330
|
+
function findAction(words, k) {
|
|
3331
|
+
const end = words.findIndex((w, i) => i > k && (w === ";" || w === "+"));
|
|
3332
|
+
return words.slice(k + 1, end < 0 ? words.length : end);
|
|
3333
|
+
}
|
|
3334
|
+
function findActions(words, h) {
|
|
3335
|
+
const out = [];
|
|
3336
|
+
for (let i = h + 1; i < words.length; i++) {
|
|
3337
|
+
const w = words[i];
|
|
3338
|
+
if (w !== null && FIND_EXEC_FLAGS.has(w)) out.push(findAction(words, i));
|
|
3339
|
+
}
|
|
3340
|
+
return out;
|
|
3341
|
+
}
|
|
3090
3342
|
function findStartPoints(words, h) {
|
|
3091
3343
|
const k = words.findIndex((w, i) => i > h && w !== null && FIND_EXEC_FLAGS.has(w));
|
|
3092
3344
|
if (k < 0) return { k, starts: [] };
|
|
@@ -3102,8 +3354,13 @@ function copySourcePaths(words) {
|
|
|
3102
3354
|
if (fi >= 0) {
|
|
3103
3355
|
const { k, starts } = findStartPoints(words, fi);
|
|
3104
3356
|
if (k < 0) return [];
|
|
3105
|
-
const
|
|
3106
|
-
|
|
3357
|
+
const out = [];
|
|
3358
|
+
for (const action of findActions(words, fi)) {
|
|
3359
|
+
const h2 = unwrapCommandHead(action);
|
|
3360
|
+
if (!resolveCopyShape(action, h2)) continue;
|
|
3361
|
+
out.push(...starts, ...copySourcePaths(action));
|
|
3362
|
+
}
|
|
3363
|
+
return out;
|
|
3107
3364
|
}
|
|
3108
3365
|
if (!COPY_VERB_HEADS.has(baseWord(words[h]))) return [];
|
|
3109
3366
|
const r = resolveCopyShape(words, h);
|
|
@@ -3290,6 +3547,15 @@ function flagEffect(token, shape, known) {
|
|
|
3290
3547
|
}
|
|
3291
3548
|
return NONE;
|
|
3292
3549
|
}
|
|
3550
|
+
function readerPaths(words, h) {
|
|
3551
|
+
const head = baseWord(words[h]);
|
|
3552
|
+
const from = h + 1;
|
|
3553
|
+
const flags = words.slice(from).filter((w) => w !== null && w.startsWith("-"));
|
|
3554
|
+
return [
|
|
3555
|
+
...readTargets(head, positionedArgs(words, from), flags, words, from),
|
|
3556
|
+
...flagOperandFiles(head, words, from)
|
|
3557
|
+
];
|
|
3558
|
+
}
|
|
3293
3559
|
function readTargets(verb, args, flags, words = [], from = 1) {
|
|
3294
3560
|
const shape = PATTERN_VERBS[verb];
|
|
3295
3561
|
if (!shape) return args.map((a) => a.value);
|
|
@@ -3371,18 +3637,21 @@ function flagOperandFiles(verb, words, from) {
|
|
|
3371
3637
|
function wrappedReadPaths(words, name) {
|
|
3372
3638
|
if (name === "find") {
|
|
3373
3639
|
const { k, starts } = findStartPoints(words, 0);
|
|
3374
|
-
|
|
3640
|
+
if (k < 0) return null;
|
|
3641
|
+
const paths = [];
|
|
3642
|
+
let reads = false;
|
|
3643
|
+
for (const action of findActions(words, 0)) {
|
|
3644
|
+
const h2 = unwrapCommandHead(action);
|
|
3645
|
+
if (!isReaderWord(action[h2] ?? null)) continue;
|
|
3646
|
+
reads = true;
|
|
3647
|
+
paths.push(...readerPaths(action, h2));
|
|
3648
|
+
}
|
|
3649
|
+
return reads ? [...starts, ...paths] : paths;
|
|
3375
3650
|
}
|
|
3376
3651
|
if (!COMMAND_WRAPPERS.has(name) && !RUNNER_WRAPPERS.has(name)) return null;
|
|
3377
3652
|
const h = unwrapCommandHead(words);
|
|
3378
3653
|
if (h <= 0 || !isReaderWord(words[h] ?? null)) return null;
|
|
3379
|
-
|
|
3380
|
-
const rest = words.slice(h + 1);
|
|
3381
|
-
const restFlags = rest.filter((w) => w !== null && w.startsWith("-"));
|
|
3382
|
-
return [
|
|
3383
|
-
...readTargets(head, positionedArgs(words, h + 1), restFlags, words, h + 1),
|
|
3384
|
-
...flagOperandFiles(head, words, h + 1)
|
|
3385
|
-
];
|
|
3654
|
+
return readerPaths(words, h);
|
|
3386
3655
|
}
|
|
3387
3656
|
function literalShellPayload(words, name) {
|
|
3388
3657
|
const h = COMMAND_WRAPPERS.has(name) || RUNNER_WRAPPERS.has(name) ? unwrapCommandHead(words) : 0;
|
package/dist/scan-ink.mjs
CHANGED
|
@@ -1211,6 +1211,18 @@ var FS_OP_PRESCREEN_RE = new RegExp(
|
|
|
1211
1211
|
// Dogfound 2026-09-22; pinned by verb-case-coverage.spec.ts.
|
|
1212
1212
|
"i"
|
|
1213
1213
|
);
|
|
1214
|
+
var currentStmtOffset = Number.MAX_SAFE_INTEGER;
|
|
1215
|
+
function probeBinOp(src) {
|
|
1216
|
+
try {
|
|
1217
|
+
const cmd = syntax.NewParser().Parse(src, "probe")?.Stmts?.[0]?.Cmd;
|
|
1218
|
+
if (cmd && syntax.NodeType(cmd) === "BinaryCmd") return cmd.Op;
|
|
1219
|
+
} catch {
|
|
1220
|
+
}
|
|
1221
|
+
return null;
|
|
1222
|
+
}
|
|
1223
|
+
var AND_OP = probeBinOp("a && b");
|
|
1224
|
+
var OR_OP = probeBinOp("a || b");
|
|
1225
|
+
var AND_OR_OPS = new Set([AND_OP, OR_OP].filter((o) => o !== null));
|
|
1214
1226
|
function deriveRedirOp(sample) {
|
|
1215
1227
|
try {
|
|
1216
1228
|
const f = sharedParser.Parse(sample, "cmd");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@node9/proxy",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.23.1",
|
|
4
4
|
"description": "IAM for your AI agents. Set what Claude Code, Codex, Gemini, Cursor and any MCP server are allowed to do, review risky actions before they run, and keep every action on the record.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|