@node9/proxy 2.22.0 → 2.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1283 -949
- package/dist/cli.mjs +1254 -920
- package/dist/dashboard.mjs +458 -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.js
CHANGED
|
@@ -1625,6 +1625,7 @@ function normalizeCommandForPolicyImpl(command) {
|
|
|
1625
1625
|
const source = command.slice(s, e);
|
|
1626
1626
|
if (resolved === source) continue;
|
|
1627
1627
|
if (resolved === "" || /\s/.test(resolved)) continue;
|
|
1628
|
+
if (/^[;&|()<>]+$/.test(resolved)) continue;
|
|
1628
1629
|
rewrites.push([s, e, resolved]);
|
|
1629
1630
|
const quoteOnly = source.replace(/['"]/g, "");
|
|
1630
1631
|
if (quoteOnly !== source) quoteOnlyRewrites.push([s, e, quoteOnly]);
|
|
@@ -2641,6 +2642,60 @@ var NET_BINARIES = /* @__PURE__ */ new Set([
|
|
|
2641
2642
|
"rsync"
|
|
2642
2643
|
]);
|
|
2643
2644
|
var VALUE_FLAGS = {
|
|
2645
|
+
// rsync 3.2.7, its own --help: every flag whose operand could be mistaken for
|
|
2646
|
+
// a host. `-e ssh` is the one that matters most (`ssh` is not the destination).
|
|
2647
|
+
rsync: /* @__PURE__ */ new Set([
|
|
2648
|
+
"-e",
|
|
2649
|
+
"--rsh",
|
|
2650
|
+
"-f",
|
|
2651
|
+
"--filter",
|
|
2652
|
+
"-T",
|
|
2653
|
+
"--temp-dir",
|
|
2654
|
+
"-B",
|
|
2655
|
+
"--block-size",
|
|
2656
|
+
"-M",
|
|
2657
|
+
"--remote-option",
|
|
2658
|
+
"--exclude",
|
|
2659
|
+
"--exclude-from",
|
|
2660
|
+
"--include",
|
|
2661
|
+
"--include-from",
|
|
2662
|
+
"--files-from",
|
|
2663
|
+
"--compare-dest",
|
|
2664
|
+
"--copy-dest",
|
|
2665
|
+
"--link-dest",
|
|
2666
|
+
"--partial-dir",
|
|
2667
|
+
"--log-file",
|
|
2668
|
+
"--password-file",
|
|
2669
|
+
"--bwlimit",
|
|
2670
|
+
"--timeout",
|
|
2671
|
+
"--contimeout",
|
|
2672
|
+
"--port",
|
|
2673
|
+
"--sockopts",
|
|
2674
|
+
"--address",
|
|
2675
|
+
"--chmod",
|
|
2676
|
+
"--chown",
|
|
2677
|
+
"--max-size",
|
|
2678
|
+
"--min-size",
|
|
2679
|
+
"--modify-window",
|
|
2680
|
+
"--out-format",
|
|
2681
|
+
"--log-file-format",
|
|
2682
|
+
"--backup-dir",
|
|
2683
|
+
"--suffix",
|
|
2684
|
+
"--iconv",
|
|
2685
|
+
"--max-delete",
|
|
2686
|
+
"--checksum-choice",
|
|
2687
|
+
"--info",
|
|
2688
|
+
"--debug",
|
|
2689
|
+
"--stderr",
|
|
2690
|
+
"--outbuf",
|
|
2691
|
+
"--skip-compress",
|
|
2692
|
+
"--usermap",
|
|
2693
|
+
"--groupmap",
|
|
2694
|
+
"--mkpath",
|
|
2695
|
+
"--write-batch",
|
|
2696
|
+
"--read-batch",
|
|
2697
|
+
"--only-write-batch"
|
|
2698
|
+
]),
|
|
2644
2699
|
curl: /* @__PURE__ */ new Set([
|
|
2645
2700
|
"-d",
|
|
2646
2701
|
"--data",
|
|
@@ -2724,23 +2779,163 @@ var VALUE_FLAGS = {
|
|
|
2724
2779
|
]),
|
|
2725
2780
|
nc: /* @__PURE__ */ new Set(["-p", "-s", "-w", "-X", "-x", "-e", "-g", "-G", "-i", "-O", "-T", "-q", "-m"])
|
|
2726
2781
|
};
|
|
2782
|
+
var HOME_VARIABLES = /* @__PURE__ */ new Set(["HOME", "USERPROFILE"]);
|
|
2783
|
+
var assignmentTable = null;
|
|
2784
|
+
var currentStmtOffset = Number.MAX_SAFE_INTEGER;
|
|
2785
|
+
var PAYLOAD_BUDGET = 256;
|
|
2786
|
+
var payloadBudget = 0;
|
|
2787
|
+
var seenPayloads = null;
|
|
2788
|
+
function payloadKey(payload) {
|
|
2789
|
+
if (!assignmentTable || assignmentTable.size === 0) return payload;
|
|
2790
|
+
const bindings = [];
|
|
2791
|
+
for (const [name, rec] of assignmentTable) {
|
|
2792
|
+
if (rec.value !== null) bindings.push(`${name}=${rec.value}`);
|
|
2793
|
+
}
|
|
2794
|
+
return `${payload}\0${bindings.sort().join("")}`;
|
|
2795
|
+
}
|
|
2796
|
+
function claimPayload(payload) {
|
|
2797
|
+
if (!seenPayloads) return "ok";
|
|
2798
|
+
const key = payloadKey(payload);
|
|
2799
|
+
if (seenPayloads.has(key)) return "seen";
|
|
2800
|
+
if (payloadBudget <= 0) return "exhausted";
|
|
2801
|
+
payloadBudget--;
|
|
2802
|
+
seenPayloads.add(key);
|
|
2803
|
+
return "ok";
|
|
2804
|
+
}
|
|
2805
|
+
var UNANALYSABLE_NESTING = {
|
|
2806
|
+
ruleName: "review-unanalysable-nesting",
|
|
2807
|
+
verdict: "review",
|
|
2808
|
+
reason: "This command nests more wrapped shell payloads than the policy engine will unwrap, so some of what it runs was not read.",
|
|
2809
|
+
path: ""
|
|
2810
|
+
};
|
|
2811
|
+
var ASSIGNMENT_HEADS = /* @__PURE__ */ new Set(["export", "declare", "local", "readonly", "typeset"]);
|
|
2812
|
+
function recordTopLevelAssignments(f) {
|
|
2813
|
+
const stmts = Array.isArray(f?.Stmts) ? f.Stmts : [];
|
|
2814
|
+
for (const stmt of stmts) recordTopLevelStmt(stmt);
|
|
2815
|
+
}
|
|
2816
|
+
function probeBinOp(src) {
|
|
2817
|
+
try {
|
|
2818
|
+
const cmd = syntax.NewParser().Parse(src, "probe")?.Stmts?.[0]?.Cmd;
|
|
2819
|
+
if (cmd && syntax.NodeType(cmd) === "BinaryCmd") return cmd.Op;
|
|
2820
|
+
} catch {
|
|
2821
|
+
}
|
|
2822
|
+
return null;
|
|
2823
|
+
}
|
|
2824
|
+
var AND_OP = probeBinOp("a && b");
|
|
2825
|
+
var OR_OP = probeBinOp("a || b");
|
|
2826
|
+
var AND_OR_OPS = new Set([AND_OP, OR_OP].filter((o) => o !== null));
|
|
2827
|
+
function recordTopLevelStmt(stmt) {
|
|
2828
|
+
if (!stmt || !stmt.Cmd) return false;
|
|
2829
|
+
const t = syntax.NodeType(stmt.Cmd);
|
|
2830
|
+
if (t === "BinaryCmd") {
|
|
2831
|
+
if (!AND_OR_OPS.has(stmt.Cmd.Op)) return false;
|
|
2832
|
+
if (!recordTopLevelStmt(stmt.Cmd.X)) return false;
|
|
2833
|
+
if (stmt.Cmd.Op === AND_OP) return recordTopLevelStmt(stmt.Cmd.Y);
|
|
2834
|
+
return true;
|
|
2835
|
+
}
|
|
2836
|
+
if (t !== "CallExpr" && t !== "DeclClause") return false;
|
|
2837
|
+
let at = 0;
|
|
2838
|
+
try {
|
|
2839
|
+
at = stmt.Pos().Offset();
|
|
2840
|
+
} catch {
|
|
2841
|
+
at = 0;
|
|
2842
|
+
}
|
|
2843
|
+
recordAssignments(stmt.Cmd, at);
|
|
2844
|
+
if (stmt.Negated) return false;
|
|
2845
|
+
if (t === "DeclClause") return ASSIGNMENT_HEADS.has(stmt.Cmd.Variant?.Value ?? "");
|
|
2846
|
+
return (stmt.Cmd.Args || []).length === 0 && (stmt.Cmd.Assigns || []).length > 0;
|
|
2847
|
+
}
|
|
2848
|
+
function recordAssignments(n, at) {
|
|
2849
|
+
if (!assignmentTable) return;
|
|
2850
|
+
const t = syntax.NodeType(n);
|
|
2851
|
+
let assigns = [];
|
|
2852
|
+
if (t === "CallExpr") {
|
|
2853
|
+
if ((n.Args || []).length > 0) return;
|
|
2854
|
+
assigns = n.Assigns || [];
|
|
2855
|
+
} else if (t === "DeclClause") {
|
|
2856
|
+
if (!ASSIGNMENT_HEADS.has(n.Variant?.Value ?? "")) return;
|
|
2857
|
+
assigns = (n.Args || []).filter((a) => syntax.NodeType(a) === "Assign");
|
|
2858
|
+
} else return;
|
|
2859
|
+
for (const a of assigns) {
|
|
2860
|
+
const name = a?.Name?.Value;
|
|
2861
|
+
if (!name || !a.Value || a.Append) continue;
|
|
2862
|
+
assignmentTable.set(name, { value: resolveWordLiteral(a.Value), at });
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
function resolveTrivialSubst(part) {
|
|
2866
|
+
if (syntax.NodeType(part) !== "CmdSubst") return void 0;
|
|
2867
|
+
const stmts = part.Stmts || [];
|
|
2868
|
+
if (stmts.length !== 1) return void 0;
|
|
2869
|
+
const st = stmts[0];
|
|
2870
|
+
if ((st.Redirs || []).length > 0 || st.Negated || st.Background) return void 0;
|
|
2871
|
+
const cmd = st.Cmd;
|
|
2872
|
+
if (!cmd || syntax.NodeType(cmd) !== "CallExpr") return void 0;
|
|
2873
|
+
if ((cmd.Assigns || []).length > 0) return void 0;
|
|
2874
|
+
const words = (cmd.Args || []).map((a) => resolveWordLiteral(a));
|
|
2875
|
+
if (words.length === 0 || words.some((w) => w === null)) return void 0;
|
|
2876
|
+
const head = baseWord(words[0]);
|
|
2877
|
+
const rest = words.slice(1);
|
|
2878
|
+
if (head === "echo") {
|
|
2879
|
+
let i = 0;
|
|
2880
|
+
while (i < rest.length && /^-[neE]+$/.test(rest[i])) i++;
|
|
2881
|
+
return rest.slice(i).join(" ");
|
|
2882
|
+
}
|
|
2883
|
+
if (head === "printf") {
|
|
2884
|
+
if (rest.length !== 2) return void 0;
|
|
2885
|
+
if (!/^%s(\\n)?$/.test(rest[0])) return void 0;
|
|
2886
|
+
return rest[1];
|
|
2887
|
+
}
|
|
2888
|
+
return void 0;
|
|
2889
|
+
}
|
|
2890
|
+
function recordedExpansion(name) {
|
|
2891
|
+
if (!assignmentTable || !name) return void 0;
|
|
2892
|
+
const rec = assignmentTable.get(name);
|
|
2893
|
+
if (rec === void 0 || rec.at >= currentStmtOffset) return void 0;
|
|
2894
|
+
return rec.value;
|
|
2895
|
+
}
|
|
2896
|
+
function isPlainParam(p) {
|
|
2897
|
+
if (syntax.NodeType(p) !== "ParamExp") return false;
|
|
2898
|
+
return !(p.Excl || p.Length || p.Width || p.Index || p.Slice || p.Repl || p.Exp);
|
|
2899
|
+
}
|
|
2900
|
+
function expandPlainParam(p) {
|
|
2901
|
+
if (!assignmentTable) return void 0;
|
|
2902
|
+
if (!isPlainParam(p)) return void 0;
|
|
2903
|
+
const recorded = recordedExpansion(p.Param?.Value);
|
|
2904
|
+
if (recorded !== void 0) return recorded;
|
|
2905
|
+
return HOME_VARIABLES.has(p.Param?.Value) ? "~" : void 0;
|
|
2906
|
+
}
|
|
2727
2907
|
function resolveWordLiteral(w) {
|
|
2728
2908
|
const parts = w?.Parts || [];
|
|
2729
2909
|
let s = "";
|
|
2730
2910
|
for (const p of parts) {
|
|
2731
|
-
const
|
|
2732
|
-
if (
|
|
2733
|
-
|
|
2734
|
-
else if (t === "DblQuoted") {
|
|
2735
|
-
const inner = p.Parts || [];
|
|
2736
|
-
if (!inner.every((ip) => syntax.NodeType(ip) === "Lit")) return null;
|
|
2737
|
-
s += inner.map((ip) => ip.Value ?? "").join("");
|
|
2738
|
-
} else {
|
|
2739
|
-
return null;
|
|
2740
|
-
}
|
|
2911
|
+
const piece = resolvePart(p, false);
|
|
2912
|
+
if (piece === void 0 || piece === null) return null;
|
|
2913
|
+
s += piece;
|
|
2741
2914
|
}
|
|
2742
2915
|
return s;
|
|
2743
2916
|
}
|
|
2917
|
+
function resolvePart(p, inQuotes) {
|
|
2918
|
+
const t = syntax.NodeType(p);
|
|
2919
|
+
if (t === "Lit") {
|
|
2920
|
+
const raw = p.Value ?? "";
|
|
2921
|
+
if (!inQuotes) return raw.replace(/\\(.)/g, "$1");
|
|
2922
|
+
return assignmentTable ? raw.replace(/\\([$`"\\])/g, "$1") : raw;
|
|
2923
|
+
}
|
|
2924
|
+
if (t === "SglQuoted") return p.Value ?? "";
|
|
2925
|
+
if (t === "ParamExp") return expandPlainParam(p);
|
|
2926
|
+
if (t === "CmdSubst") return assignmentTable ? resolveTrivialSubst(p) : void 0;
|
|
2927
|
+
if (t === "DblQuoted" && !inQuotes) {
|
|
2928
|
+
const inner = p.Parts || [];
|
|
2929
|
+
let out = "";
|
|
2930
|
+
for (const ip of inner) {
|
|
2931
|
+
const piece = resolvePart(ip, true);
|
|
2932
|
+
if (piece === void 0 || piece === null) return piece;
|
|
2933
|
+
out += piece;
|
|
2934
|
+
}
|
|
2935
|
+
return out;
|
|
2936
|
+
}
|
|
2937
|
+
return void 0;
|
|
2938
|
+
}
|
|
2744
2939
|
function parseDestHost(token) {
|
|
2745
2940
|
if (!token) return null;
|
|
2746
2941
|
let t = token.trim();
|
|
@@ -2795,6 +2990,7 @@ function destTokensForBinary(binary, args) {
|
|
|
2795
2990
|
case "ssh":
|
|
2796
2991
|
return positionals.slice(0, 1);
|
|
2797
2992
|
case "scp":
|
|
2993
|
+
case "rsync":
|
|
2798
2994
|
return positionals.filter((p) => p.includes(":") || p.includes("@"));
|
|
2799
2995
|
case "nc":
|
|
2800
2996
|
case "ncat":
|
|
@@ -2995,17 +3191,35 @@ function analyzeFsOperationImpl(command, depth = 0) {
|
|
|
2995
3191
|
const f = parseShared(command);
|
|
2996
3192
|
if (f === PARSE_FAIL) return null;
|
|
2997
3193
|
let result = null;
|
|
3194
|
+
const outerTable = assignmentTable;
|
|
3195
|
+
const outerOffset = currentStmtOffset;
|
|
3196
|
+
const outerSeen = seenPayloads;
|
|
3197
|
+
const outerBudget = payloadBudget;
|
|
3198
|
+
if (depth === 0) {
|
|
3199
|
+
seenPayloads = /* @__PURE__ */ new Set();
|
|
3200
|
+
payloadBudget = PAYLOAD_BUDGET;
|
|
3201
|
+
}
|
|
3202
|
+
assignmentTable = new Map(
|
|
3203
|
+
[...outerTable ?? []].map(([k, r]) => [k, { value: r.value, at: -1 }])
|
|
3204
|
+
);
|
|
3205
|
+
currentStmtOffset = Number.MAX_SAFE_INTEGER;
|
|
2998
3206
|
try {
|
|
3207
|
+
recordTopLevelAssignments(f);
|
|
2999
3208
|
syntax.Walk(f, (node) => {
|
|
3000
3209
|
if (!node || result?.verdict === "block") return false;
|
|
3001
3210
|
const n = node;
|
|
3002
3211
|
const nodeType = syntax.NodeType(n);
|
|
3003
3212
|
if (nodeType === "Stmt") {
|
|
3213
|
+
try {
|
|
3214
|
+
currentStmtOffset = n.Pos().Offset();
|
|
3215
|
+
} catch {
|
|
3216
|
+
currentStmtOffset = Number.MAX_SAFE_INTEGER;
|
|
3217
|
+
}
|
|
3004
3218
|
result = stricter(result, jailedRedirectRead(n));
|
|
3005
3219
|
return result?.verdict !== "block";
|
|
3006
3220
|
}
|
|
3007
3221
|
if (nodeType !== "CallExpr") return true;
|
|
3008
|
-
const { name, flags, paths, words
|
|
3222
|
+
const { name, flags, paths, words } = extractLiteralArgs(n);
|
|
3009
3223
|
if (!name) return true;
|
|
3010
3224
|
if (name === "rm") {
|
|
3011
3225
|
const flagStr = flags.join("").toLowerCase();
|
|
@@ -3034,9 +3248,30 @@ function analyzeFsOperationImpl(command, depth = 0) {
|
|
|
3034
3248
|
}
|
|
3035
3249
|
}
|
|
3036
3250
|
}
|
|
3037
|
-
if (depth <
|
|
3251
|
+
if (depth < 24 && name === "find") {
|
|
3252
|
+
for (const action of findActions(words, 0)) {
|
|
3253
|
+
const h = unwrapCommandHead(action);
|
|
3254
|
+
const inner = literalShellPayload(action.slice(h), baseWord(action[h]));
|
|
3255
|
+
if (inner === null) continue;
|
|
3256
|
+
const claim = claimPayload(inner);
|
|
3257
|
+
if (claim === "exhausted") {
|
|
3258
|
+
result = stricter(result, UNANALYSABLE_NESTING);
|
|
3259
|
+
continue;
|
|
3260
|
+
}
|
|
3261
|
+
if (claim === "seen") continue;
|
|
3262
|
+
const v = analyzeFsOperationImpl(inner, depth + 1);
|
|
3263
|
+
result = stricter(result, v);
|
|
3264
|
+
if (result?.verdict === "block") return false;
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3267
|
+
if (depth < 24) {
|
|
3038
3268
|
const payload = literalShellPayload(words, name);
|
|
3039
|
-
|
|
3269
|
+
const claim = payload === null ? "seen" : claimPayload(payload);
|
|
3270
|
+
if (claim === "exhausted") {
|
|
3271
|
+
result = stricter(result, UNANALYSABLE_NESTING);
|
|
3272
|
+
return true;
|
|
3273
|
+
}
|
|
3274
|
+
if (payload !== null && claim === "ok") {
|
|
3040
3275
|
const inner = analyzeFsOperationImpl(payload, depth + 1);
|
|
3041
3276
|
if (inner) {
|
|
3042
3277
|
result = inner;
|
|
@@ -3045,7 +3280,7 @@ function analyzeFsOperationImpl(command, depth = 0) {
|
|
|
3045
3280
|
return true;
|
|
3046
3281
|
}
|
|
3047
3282
|
}
|
|
3048
|
-
const readPaths = FS_READ_TOOLS.has(name) ?
|
|
3283
|
+
const readPaths = FS_READ_TOOLS.has(name) ? readerPaths(words, 0) : wrappedReadPaths(words, name);
|
|
3049
3284
|
if (readPaths) {
|
|
3050
3285
|
for (const p of readPaths) {
|
|
3051
3286
|
result = stricter(result, matchSensitivePath2(p));
|
|
@@ -3060,6 +3295,11 @@ function analyzeFsOperationImpl(command, depth = 0) {
|
|
|
3060
3295
|
return result;
|
|
3061
3296
|
} catch {
|
|
3062
3297
|
return null;
|
|
3298
|
+
} finally {
|
|
3299
|
+
assignmentTable = outerTable;
|
|
3300
|
+
currentStmtOffset = outerOffset;
|
|
3301
|
+
seenPayloads = outerSeen;
|
|
3302
|
+
if (depth === 0) payloadBudget = outerBudget;
|
|
3063
3303
|
}
|
|
3064
3304
|
}
|
|
3065
3305
|
function stricter(a, b) {
|
|
@@ -3117,6 +3357,18 @@ function resolveCopyShape(words, h) {
|
|
|
3117
3357
|
return null;
|
|
3118
3358
|
}
|
|
3119
3359
|
var FIND_OPTIONS = /* @__PURE__ */ new Set(["-H", "-L", "-P"]);
|
|
3360
|
+
function findAction(words, k) {
|
|
3361
|
+
const end = words.findIndex((w, i) => i > k && (w === ";" || w === "+"));
|
|
3362
|
+
return words.slice(k + 1, end < 0 ? words.length : end);
|
|
3363
|
+
}
|
|
3364
|
+
function findActions(words, h) {
|
|
3365
|
+
const out = [];
|
|
3366
|
+
for (let i = h + 1; i < words.length; i++) {
|
|
3367
|
+
const w = words[i];
|
|
3368
|
+
if (w !== null && FIND_EXEC_FLAGS.has(w)) out.push(findAction(words, i));
|
|
3369
|
+
}
|
|
3370
|
+
return out;
|
|
3371
|
+
}
|
|
3120
3372
|
function findStartPoints(words, h) {
|
|
3121
3373
|
const k = words.findIndex((w, i) => i > h && w !== null && FIND_EXEC_FLAGS.has(w));
|
|
3122
3374
|
if (k < 0) return { k, starts: [] };
|
|
@@ -3132,8 +3384,13 @@ function copySourcePaths(words) {
|
|
|
3132
3384
|
if (fi >= 0) {
|
|
3133
3385
|
const { k, starts } = findStartPoints(words, fi);
|
|
3134
3386
|
if (k < 0) return [];
|
|
3135
|
-
const
|
|
3136
|
-
|
|
3387
|
+
const out = [];
|
|
3388
|
+
for (const action of findActions(words, fi)) {
|
|
3389
|
+
const h2 = unwrapCommandHead(action);
|
|
3390
|
+
if (!resolveCopyShape(action, h2)) continue;
|
|
3391
|
+
out.push(...starts, ...copySourcePaths(action));
|
|
3392
|
+
}
|
|
3393
|
+
return out;
|
|
3137
3394
|
}
|
|
3138
3395
|
if (!COPY_VERB_HEADS.has(baseWord(words[h]))) return [];
|
|
3139
3396
|
const r = resolveCopyShape(words, h);
|
|
@@ -3320,6 +3577,15 @@ function flagEffect(token, shape, known) {
|
|
|
3320
3577
|
}
|
|
3321
3578
|
return NONE;
|
|
3322
3579
|
}
|
|
3580
|
+
function readerPaths(words, h) {
|
|
3581
|
+
const head = baseWord(words[h]);
|
|
3582
|
+
const from = h + 1;
|
|
3583
|
+
const flags = words.slice(from).filter((w) => w !== null && w.startsWith("-"));
|
|
3584
|
+
return [
|
|
3585
|
+
...readTargets(head, positionedArgs(words, from), flags, words, from),
|
|
3586
|
+
...flagOperandFiles(head, words, from)
|
|
3587
|
+
];
|
|
3588
|
+
}
|
|
3323
3589
|
function readTargets(verb, args, flags, words = [], from = 1) {
|
|
3324
3590
|
const shape = PATTERN_VERBS[verb];
|
|
3325
3591
|
if (!shape) return args.map((a) => a.value);
|
|
@@ -3401,18 +3667,21 @@ function flagOperandFiles(verb, words, from) {
|
|
|
3401
3667
|
function wrappedReadPaths(words, name) {
|
|
3402
3668
|
if (name === "find") {
|
|
3403
3669
|
const { k, starts } = findStartPoints(words, 0);
|
|
3404
|
-
|
|
3670
|
+
if (k < 0) return null;
|
|
3671
|
+
const paths = [];
|
|
3672
|
+
let reads = false;
|
|
3673
|
+
for (const action of findActions(words, 0)) {
|
|
3674
|
+
const h2 = unwrapCommandHead(action);
|
|
3675
|
+
if (!isReaderWord(action[h2] ?? null)) continue;
|
|
3676
|
+
reads = true;
|
|
3677
|
+
paths.push(...readerPaths(action, h2));
|
|
3678
|
+
}
|
|
3679
|
+
return reads ? [...starts, ...paths] : paths;
|
|
3405
3680
|
}
|
|
3406
3681
|
if (!COMMAND_WRAPPERS.has(name) && !RUNNER_WRAPPERS.has(name)) return null;
|
|
3407
3682
|
const h = unwrapCommandHead(words);
|
|
3408
3683
|
if (h <= 0 || !isReaderWord(words[h] ?? null)) return null;
|
|
3409
|
-
|
|
3410
|
-
const rest = words.slice(h + 1);
|
|
3411
|
-
const restFlags = rest.filter((w) => w !== null && w.startsWith("-"));
|
|
3412
|
-
return [
|
|
3413
|
-
...readTargets(head, positionedArgs(words, h + 1), restFlags, words, h + 1),
|
|
3414
|
-
...flagOperandFiles(head, words, h + 1)
|
|
3415
|
-
];
|
|
3684
|
+
return readerPaths(words, h);
|
|
3416
3685
|
}
|
|
3417
3686
|
function literalShellPayload(words, name) {
|
|
3418
3687
|
const h = COMMAND_WRAPPERS.has(name) || RUNNER_WRAPPERS.has(name) ? unwrapCommandHead(words) : 0;
|