




















@@ -1,5 +1,6 @@
11import path from "node:path";
22import type { AgentToolResult } from "@mariozechner/pi-agent-core";
3+import { buildCommandPayloadCandidates } from "../infra/command-analysis/risks.js";
34import { analyzeShellCommand } from "../infra/exec-approvals-analysis.js";
45import {
56type ExecAsk,
@@ -16,7 +17,6 @@ import {
1617getShellPathFromLoginShell,
1718resolveShellEnvFallbackTimeoutMs,
1819} from "../infra/shell-env.js";
19-import { extractShellWrapperInlineCommand } from "../infra/shell-wrapper-resolution.js";
2020import { logInfo } from "../logger.js";
2121import { parseAgentSessionKey, resolveAgentIdFromSessionKey } from "../routing/session-key.js";
2222import { createLazyImportLoader } from "../shared/lazy-promise.js";
@@ -1140,207 +1140,17 @@ function parseOpenClawChannelsLoginShellCommand(raw: string): boolean {
11401140}
1141114111421142function rejectUnsafeControlShellCommand(command: string): void {
1143-const isEnvAssignmentToken = (token: string): boolean =>
1144-/^[A-Za-z_][A-Za-z0-9_]*=.*$/u.test(token);
1145-const commandStandaloneOptions = new Set(["-p", "-v", "-V"]);
1146-const envOptionsWithValues = new Set([
1147-"-C",
1148-"-S",
1149-"-u",
1150-"--argv0",
1151-"--block-signal",
1152-"--chdir",
1153-"--default-signal",
1154-"--ignore-signal",
1155-"--split-string",
1156-"--unset",
1157-]);
1158-const execOptionsWithValues = new Set(["-a"]);
1159-const execStandaloneOptions = new Set(["-c", "-l"]);
1160-const sudoOptionsWithValues = new Set([
1161-"-C",
1162-"-D",
1163-"-g",
1164-"-p",
1165-"-R",
1166-"-T",
1167-"-U",
1168-"-u",
1169-"--chdir",
1170-"--close-from",
1171-"--group",
1172-"--host",
1173-"--other-user",
1174-"--prompt",
1175-"--role",
1176-"--type",
1177-"--user",
1178-]);
1179-const sudoStandaloneOptions = new Set(["-A", "-E", "--askpass", "--preserve-env"]);
1180-const extractEnvSplitStringPayload = (argv: string[]): string[] => {
1181-const remaining = [...argv];
1182-while (remaining[0] && isEnvAssignmentToken(remaining[0])) {
1183-remaining.shift();
1184-}
1185-if (remaining[0] !== "env") {
1186-return [];
1187-}
1188-remaining.shift();
1189-const payloads: string[] = [];
1190-while (remaining.length > 0) {
1191-while (remaining[0] && isEnvAssignmentToken(remaining[0])) {
1192-remaining.shift();
1193-}
1194-const token: string | undefined = remaining[0];
1195-if (!token) {
1196-break;
1197-}
1198-if (token === "--") {
1199-remaining.shift();
1200-continue;
1201-}
1202-if (!token.startsWith("-") || token === "-") {
1203-break;
1204-}
1205-const option = remaining.shift()!;
1206-const normalized = option.split("=", 1)[0];
1207-if (normalized === "-S" || normalized === "--split-string") {
1208-const value = option.includes("=")
1209- ? option.slice(option.indexOf("=") + 1)
1210- : remaining.shift();
1211-if (value?.trim()) {
1212-payloads.push(value);
1213-}
1214-continue;
1215-}
1216-if (envOptionsWithValues.has(normalized) && !option.includes("=") && remaining[0]) {
1217-remaining.shift();
1218-}
1219-}
1220-return payloads;
1221-};
1222-const stripApprovalCommandPrefixes = (argv: string[]): string[] => {
1223-const remaining = [...argv];
1224-while (remaining.length > 0) {
1225-while (remaining[0] && isEnvAssignmentToken(remaining[0])) {
1226-remaining.shift();
1227-}
1228-1229-const token = remaining[0];
1230-if (!token) {
1231-break;
1232-}
1233-if (token === "--") {
1234-remaining.shift();
1235-continue;
1236-}
1237-if (token === "env") {
1238-remaining.shift();
1239-while (remaining.length > 0) {
1240-while (remaining[0] && isEnvAssignmentToken(remaining[0])) {
1241-remaining.shift();
1242-}
1243-const envToken = remaining[0];
1244-if (!envToken) {
1245-break;
1246-}
1247-if (envToken === "--") {
1248-remaining.shift();
1249-continue;
1250-}
1251-if (!envToken.startsWith("-") || envToken === "-") {
1252-break;
1253-}
1254-const option = remaining.shift()!;
1255-const normalized = option.split("=", 1)[0];
1256-if (envOptionsWithValues.has(normalized) && !option.includes("=") && remaining[0]) {
1257-remaining.shift();
1258-}
1259-}
1260-continue;
1261-}
1262-if (token === "command" || token === "builtin") {
1263-remaining.shift();
1264-while (remaining[0]?.startsWith("-")) {
1265-const option = remaining.shift()!;
1266-if (option === "--") {
1267-break;
1268-}
1269-if (!commandStandaloneOptions.has(option.split("=", 1)[0])) {
1270-continue;
1271-}
1272-}
1273-continue;
1274-}
1275-if (token === "exec") {
1276-remaining.shift();
1277-while (remaining[0]?.startsWith("-")) {
1278-const option = remaining.shift()!;
1279-if (option === "--") {
1280-break;
1281-}
1282-const normalized = option.split("=", 1)[0];
1283-if (execStandaloneOptions.has(normalized)) {
1284-continue;
1285-}
1286-if (execOptionsWithValues.has(normalized) && !option.includes("=") && remaining[0]) {
1287-remaining.shift();
1288-}
1289-}
1290-continue;
1291-}
1292-if (token === "sudo") {
1293-remaining.shift();
1294-while (remaining[0]?.startsWith("-")) {
1295-const option = remaining.shift()!;
1296-if (option === "--") {
1297-break;
1298-}
1299-const normalized = option.split("=", 1)[0];
1300-if (sudoStandaloneOptions.has(normalized)) {
1301-continue;
1302-}
1303-if (sudoOptionsWithValues.has(normalized) && !option.includes("=") && remaining[0]) {
1304-remaining.shift();
1305-}
1306-}
1307-continue;
1308-}
1309-break;
1310-}
1311-return remaining;
1312-};
1313-const buildCandidates = (argv: string[]): string[] => {
1314-const envSplitCandidates = extractEnvSplitStringPayload(argv).flatMap((payload) => {
1315-const innerArgv = splitShellArgs(payload);
1316-return innerArgv ? buildCandidates(innerArgv) : [payload];
1317-});
1318-const stripped = stripApprovalCommandPrefixes(argv);
1319-const shellWrapperPayload = extractShellWrapperInlineCommand(stripped);
1320-const shellWrapperCandidates = shellWrapperPayload
1321- ? (() => {
1322-const innerArgv = splitShellArgs(shellWrapperPayload);
1323-return innerArgv ? buildCandidates(innerArgv) : [shellWrapperPayload];
1324-})()
1325- : [];
1326-return [
1327- ...(stripped.length > 0 ? [stripped.join(" ")] : []),
1328- ...envSplitCandidates,
1329- ...shellWrapperCandidates,
1330-];
1331-};
1332-13331143const rawCommand = command.trim();
13341144const analysis = analyzeShellCommand({ command: rawCommand });
13351145const candidates = analysis.ok
1336- ? analysis.segments.flatMap((segment) => buildCandidates(segment.argv))
1146+ ? analysis.segments.flatMap((segment) => buildCommandPayloadCandidates(segment.argv))
13371147 : rawCommand
13381148.split(/\r?\n/)
13391149.map((line) => line.trim())
13401150.filter(Boolean)
13411151.flatMap((line) => {
13421152const argv = splitShellArgs(line);
1343-return argv ? buildCandidates(argv) : [line];
1153+return argv ? buildCommandPayloadCandidates(argv) : [line];
13441154});
13451155for (const candidate of candidates) {
13461156if (parseExecApprovalShellCommand(candidate)) {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。