


























@@ -9,6 +9,7 @@ import {
99import { unwrapKnownDispatchWrapperInvocation } from "../dispatch-wrapper-resolution.js";
1010import type { ExecCommandSegment } from "../exec-approvals-analysis.js";
1111import { normalizeExecutableToken } from "../exec-wrapper-resolution.js";
12+import { POSIX_INLINE_COMMAND_FLAGS, resolveInlineCommandMatch } from "../shell-inline-command.js";
1213import {
1314extractShellWrapperInlineCommand,
1415isShellWrapperExecutable,
@@ -78,6 +79,132 @@ function uniqueCommandPayloadCandidates(candidates: string[]): string[] {
7879return [...new Set(candidates.filter((candidate) => candidate.trim().length > 0))];
7980}
808182+type ShellPositionalCarrierPlan = { kind: "all" } | { kind: "indexes"; indexes: number[] };
83+84+function normalizeShellPositionalToken(
85+token: string,
86+): { kind: "all" | "star" | "zero" } | { kind: "index"; index: number } | null {
87+const unquoted =
88+token.length >= 2 && token.startsWith('"') && token.endsWith('"') ? token.slice(1, -1) : token;
89+const match = unquoted.match(/^\$(?:([0-9@*])|\{([0-9@*])\})$/u);
90+const value = match?.[1] ?? match?.[2];
91+if (value === undefined) {
92+return null;
93+}
94+if (value === "@") {
95+return { kind: "all" };
96+}
97+if (value === "*") {
98+return { kind: "star" };
99+}
100+if (value === "0") {
101+return { kind: "zero" };
102+}
103+return { kind: "index", index: Number.parseInt(value, 10) };
104+}
105+106+function resolveShellPositionalCarrierPlan(command: string): ShellPositionalCarrierPlan | null {
107+const trimmed = command.trim();
108+if (trimmed.length === 0) {
109+return null;
110+}
111+112+const shellWhitespace = String.raw`[^\S\r\n]+`;
113+const positionalZero = String.raw`(?:\$(?:0|\{0\})|"\$(?:0|\{0\})")`;
114+const positionalArg = String.raw`(?:\$(?:[@*]|[1-9]|\{[@*1-9]\})|"\$(?:[@*]|[1-9]|\{[@*1-9]\})")`;
115+if (
116+!new RegExp(
117+`^(?:exec${shellWhitespace}(?:--${shellWhitespace})?)?${positionalZero}(?:${shellWhitespace}${positionalArg})*$`,
118+"u",
119+).test(trimmed)
120+) {
121+return null;
122+}
123+124+const tokens = trimmed.match(/"[^"]*"|\S+/gu) ?? [];
125+let index = 0;
126+if (tokens[index] === "exec") {
127+index += 1;
128+if (tokens[index] === "--") {
129+index += 1;
130+}
131+}
132+const zero = normalizeShellPositionalToken(tokens[index] ?? "");
133+if (zero?.kind !== "zero") {
134+return null;
135+}
136+index += 1;
137+138+const indexes = [0];
139+for (; index < tokens.length; index += 1) {
140+const positional = normalizeShellPositionalToken(tokens[index] ?? "");
141+if (positional === null || positional.kind === "zero" || positional.kind === "star") {
142+return null;
143+}
144+if (positional.kind === "all") {
145+return { kind: "all" };
146+}
147+if (positional.kind === "index") {
148+indexes.push(positional.index);
149+}
150+}
151+return { kind: "indexes", indexes };
152+}
153+154+function resolveShellPositionalCarrierArgv(params: {
155+executableArgv: string[];
156+valueTokenIndex: number;
157+plan: ShellPositionalCarrierPlan;
158+}): string[] {
159+const positionalArgv = params.executableArgv.slice(params.valueTokenIndex + 1);
160+const carriedArgv =
161+params.plan.kind === "all"
162+ ? positionalArgv
163+ : params.plan.indexes.map((index) => positionalArgv[index] ?? "");
164+return carriedArgv.map((token) => token.trim()).filter((token) => token.length > 0);
165+}
166+167+function detectShellPositionalCarrierInlineEvalArgvInternal(
168+argv: string[],
169+seenArgv: Set<string>,
170+): InterpreterInlineEvalHit | null {
171+const executableArgv = stripLeadingEnvAssignments(argv);
172+const executable = normalizeExecutableToken(executableArgv[0] ?? "");
173+if (!isShellWrapperExecutable(executable)) {
174+return null;
175+}
176+if (!["ash", "bash", "dash", "fish", "ksh", "sh", "zsh"].includes(executable)) {
177+return null;
178+}
179+const key = commandArgvKey(executableArgv);
180+if (seenArgv.has(key)) {
181+return null;
182+}
183+seenArgv.add(key);
184+185+const inlineMatch = resolveInlineCommandMatch(executableArgv, POSIX_INLINE_COMMAND_FLAGS, {
186+allowCombinedC: true,
187+});
188+if (inlineMatch.valueTokenIndex === null || !inlineMatch.command) {
189+return null;
190+}
191+const carrierPlan = resolveShellPositionalCarrierPlan(inlineMatch.command);
192+if (!carrierPlan) {
193+return null;
194+}
195+196+const carriedArgv = resolveShellPositionalCarrierArgv({
197+ executableArgv,
198+valueTokenIndex: inlineMatch.valueTokenIndex,
199+plan: carrierPlan,
200+});
201+if (carriedArgv.length === 0) {
202+return null;
203+}
204+205+return detectInlineEvalArgvInternal(carriedArgv, seenArgv);
206+}
207+81208function detectCarrierInlineEvalArgvInternal(
82209argv: string[],
83210seenArgv: Set<string>,
@@ -102,10 +229,7 @@ function detectCarrierInlineEvalArgvInternal(
102229if (!carriedArgv) {
103230return null;
104231}
105-return (
106-detectInterpreterInlineEvalArgv(carriedArgv) ??
107-detectCarrierInlineEvalArgvInternal(carriedArgv, seenArgv)
108-);
232+return detectInlineEvalArgvInternal(carriedArgv, seenArgv);
109233}
110234111235export function detectCarrierInlineEvalArgv(argv: string[]): InterpreterInlineEvalHit | null {
@@ -120,7 +244,9 @@ function detectInlineEvalArgvInternal(
120244return null;
121245}
122246return (
123-detectInterpreterInlineEvalArgv(argv) ?? detectCarrierInlineEvalArgvInternal(argv, seenArgv)
247+detectInterpreterInlineEvalArgv(argv) ??
248+detectShellPositionalCarrierInlineEvalArgvInternal(argv, seenArgv) ??
249+detectCarrierInlineEvalArgvInternal(argv, seenArgv)
124250);
125251}
126252此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。