mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:10:44 +00:00
fix(exec): detect exec carrier risks
This commit is contained in:
@@ -49,6 +49,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Exec approvals: treat POSIX `exec` as a command carrier for inline eval, shell-wrapper, and eval/source detection, so approval explanations and command-risk checks do not miss payloads hidden behind `exec`. Thanks @vincentkoc.
|
||||
- Diagnostics: handle missing session-tail files in cron recovery context without tripping extension test typecheck. Thanks @vincentkoc.
|
||||
- QA/Slack: update the Slack dispatch preview fallback test SDK mock for structured progress draft helpers, so the rich progress draft regression suite covers the new imports instead of failing before assertions run. Thanks @vincentkoc.
|
||||
- Plugins/loader: keep bundled plugin package `test-api.js` aliases behind private QA mode, so source transforms do not expose test-only public surfaces during normal plugin loading. Thanks @vincentkoc.
|
||||
|
||||
@@ -57,6 +57,10 @@ describe("command-analysis risks", () => {
|
||||
expect(detectInlineEvalArgv(["env", "-P", "/usr/bin", "python3", "-c", "print(1)"])?.flag).toBe(
|
||||
"-c",
|
||||
);
|
||||
expect(detectInlineEvalArgv(["exec", "python3", "-c", "print(1)"])?.flag).toBe("-c");
|
||||
expect(detectInlineEvalArgv(["exec", "-a", "py", "python3", "-c", "print(1)"])?.flag).toBe(
|
||||
"-c",
|
||||
);
|
||||
expect(detectInlineEvalArgv(["command", "node", "--eval", "1"])?.flag).toBe("--eval");
|
||||
expect(detectInlineEvalArgv(["env", "-S", 'python3 -c "print(1)"'])?.flag).toBe("-c");
|
||||
expect(detectInlineEvalArgv(["python3", "script.py"])).toBeNull();
|
||||
@@ -109,6 +113,12 @@ describe("command-analysis risks", () => {
|
||||
(argv, startIndex) => argv[startIndex] === "-lc",
|
||||
),
|
||||
).toBe("sudo");
|
||||
expect(
|
||||
detectShellWrapperThroughCarrierArgv(
|
||||
["exec", "bash", "-lc", "id"],
|
||||
(argv, startIndex) => argv[startIndex] === "-lc",
|
||||
),
|
||||
).toBe("exec");
|
||||
expect(
|
||||
detectShellWrapperThroughCarrierArgv(
|
||||
["sudo", "echo", "bash", "-lc", "id"],
|
||||
@@ -125,6 +135,13 @@ describe("command-analysis risks", () => {
|
||||
kind: "source",
|
||||
command: "source",
|
||||
});
|
||||
expect(detectCarriedShellBuiltinArgv(["exec", "eval", "echo hi"])).toEqual({
|
||||
kind: "eval",
|
||||
});
|
||||
expect(detectCarriedShellBuiltinArgv(["exec", "source", "./env.sh"])).toEqual({
|
||||
kind: "source",
|
||||
command: "source",
|
||||
});
|
||||
expect(detectCarriedShellBuiltinArgv(["command", "echo", "eval"])).toBeNull();
|
||||
});
|
||||
|
||||
|
||||
@@ -27,6 +27,13 @@ function commandArgvKey(argv: readonly string[]): string {
|
||||
return argv.join("\0");
|
||||
}
|
||||
|
||||
function isCommandCarrierExecutable(executable: string, options?: { includeExec?: boolean }) {
|
||||
return (
|
||||
COMMAND_CARRIER_EXECUTABLES.has(executable) ||
|
||||
Boolean(options?.includeExec && executable === "exec")
|
||||
);
|
||||
}
|
||||
|
||||
export function buildCommandPayloadCandidates(
|
||||
argv: string[],
|
||||
seenArgv = new Set<string>(),
|
||||
@@ -87,10 +94,10 @@ function detectCarrierInlineEvalArgvInternal(
|
||||
}
|
||||
|
||||
const executable = normalizeExecutableToken(executableArgv[0] ?? "");
|
||||
if (!COMMAND_CARRIER_EXECUTABLES.has(executable)) {
|
||||
if (!isCommandCarrierExecutable(executable, { includeExec: true })) {
|
||||
return null;
|
||||
}
|
||||
const carriedArgv = resolveCarrierCommandArgv(executableArgv);
|
||||
const carriedArgv = resolveCarrierCommandArgv(executableArgv, 0, { includeExec: true });
|
||||
if (!carriedArgv) {
|
||||
return null;
|
||||
}
|
||||
@@ -179,10 +186,10 @@ export function detectShellWrapperThroughCarrierArgv(
|
||||
shellCommandFlag: (argv: string[], startIndex: number) => unknown,
|
||||
): string | null {
|
||||
const executable = normalizeExecutableToken(argv[0] ?? "");
|
||||
if (!COMMAND_CARRIER_EXECUTABLES.has(executable)) {
|
||||
if (!isCommandCarrierExecutable(executable, { includeExec: true })) {
|
||||
return null;
|
||||
}
|
||||
const carriedArgv = resolveCarrierCommandArgv(argv);
|
||||
const carriedArgv = resolveCarrierCommandArgv(argv, 0, { includeExec: true });
|
||||
if (!carriedArgv) {
|
||||
return null;
|
||||
}
|
||||
@@ -194,10 +201,10 @@ export function detectShellWrapperThroughCarrierArgv(
|
||||
|
||||
export function detectCarriedShellBuiltinArgv(argv: string[]): CarriedShellBuiltinHit | null {
|
||||
const executable = normalizeExecutableToken(argv[0] ?? "");
|
||||
if (!COMMAND_CARRIER_EXECUTABLES.has(executable)) {
|
||||
if (!isCommandCarrierExecutable(executable, { includeExec: true })) {
|
||||
return null;
|
||||
}
|
||||
const carriedArgv = resolveCarrierCommandArgv(argv);
|
||||
const carriedArgv = resolveCarrierCommandArgv(argv, 0, { includeExec: true });
|
||||
if (!carriedArgv) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -607,6 +607,7 @@ describe("command explainer tree-sitter runtime", () => {
|
||||
'env python -c "print(1)"',
|
||||
'sudo python -c "print(1)"',
|
||||
'command python -c "print(1)"',
|
||||
'exec python -c "print(1)"',
|
||||
]) {
|
||||
const explanation = await explainShellCommand(command);
|
||||
expect(explanation.risks).toContainEqual(
|
||||
@@ -644,6 +645,14 @@ describe("command explainer tree-sitter runtime", () => {
|
||||
expect.objectContaining({ kind: "shell-wrapper-through-carrier", command: "command" }),
|
||||
);
|
||||
|
||||
const execShell = await explainShellCommand("exec bash -lc 'id && whoami'");
|
||||
expect(execShell.risks).toContainEqual(
|
||||
expect.objectContaining({ kind: "shell-wrapper-through-carrier", command: "exec" }),
|
||||
);
|
||||
|
||||
const execEval = await explainShellCommand("exec eval 'echo hi'");
|
||||
expect(execEval.risks).toContainEqual(expect.objectContaining({ kind: "eval" }));
|
||||
|
||||
const sudoCombinedFlags = await explainShellCommand('sudo bash -euxc "id && whoami"');
|
||||
expect(sudoCombinedFlags.risks).toContainEqual(
|
||||
expect.objectContaining({ kind: "shell-wrapper-through-carrier", command: "sudo" }),
|
||||
|
||||
Reference in New Issue
Block a user