Files
openclaw/src/infra/exec-authorization-plan.test.ts
Peter Steinberger 7bf80dc2c6 chore(tooling): enforce formatting and refresh TypeScript checks (#104239)
* chore(tooling): enforce current formatter and refresh checks

* chore(tooling): keep release changelog formatter-owned

* chore(tooling): retain compatible Node type surface

* ci: enforce formatting for docs-only changes

* ci: isolate docs formatter check

* chore(tooling): apply updated lint and format rules

* chore(tooling): satisfy updated switch lint

* style(ui): apply Linux formatter layout

* test(doctor): match quiet local audio contribution

* test(doctor): assert quiet output only

* test(doctor): follow restored information contract
2026-07-11 01:09:51 -07:00

353 lines
12 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { analyzeArgvCommand } from "./exec-approvals-analysis.js";
import { planExecAuthorization, planShellAuthorization } from "./exec-authorization-plan.js";
function plannedArgv(plan: Awaited<ReturnType<typeof planShellAuthorization>>): string[][] {
return plan.ok
? plan.groups.flatMap((group) =>
group.candidates.map((candidate) => candidate.sourceSegment.argv),
)
: [];
}
async function expectSingleShellCandidate(
command: string,
candidate: Record<string, unknown>,
): Promise<void> {
const plan = await planShellAuthorization({ command });
expect(plan.ok).toBe(true);
expect(plan.groups).toEqual([
expect.objectContaining({
candidates: [expect.objectContaining(candidate)],
}),
]);
}
describe("exec authorization planner", () => {
it("plans direct shell commands as direct candidates", async () => {
await expectSingleShellCandidate("git status", {
sourceSegment: expect.objectContaining({ argv: ["git", "status"] }),
transport: { kind: "direct" },
trustMode: "executable",
});
});
it("preserves pipeline candidates separately", async () => {
const plan = await planShellAuthorization({ command: "git diff | cat" });
expect(plan.ok).toBe(true);
expect(plan.groups).toEqual([
expect.objectContaining({
candidates: [
expect.objectContaining({
sourceSegment: expect.objectContaining({ argv: ["git", "diff"] }),
}),
expect.objectContaining({ sourceSegment: expect.objectContaining({ argv: ["cat"] }) }),
],
}),
]);
});
it("keeps chain groups distinct", async () => {
const plan = await planShellAuthorization({ command: "git status && npm test; pwd" });
expect(plan.ok).toBe(true);
expect(plan.groups.map((group) => group.opToNext ?? null)).toEqual(["&&", ";", null]);
expect(plannedArgv(plan)).toEqual([["git", "status"], ["npm", "test"], ["pwd"]]);
});
it("marks dynamic executable positions as not safe to plan", async () => {
const plan = await planShellAuthorization({ command: "$(whoami) --help" });
expect(plan).toEqual(
expect.objectContaining({
ok: false,
dialect: "posix-shell",
reason: "dynamic-executable",
}),
);
});
it("treats heredocs as unanalyzable shell topology", async () => {
const plan = await planShellAuthorization({ command: "cat <<EOF\nhello\nEOF" });
expect(plan).toEqual(
expect.objectContaining({
ok: false,
dialect: "posix-shell",
reason: "heredoc",
}),
);
});
it.each([
{ command: "echo $(whoami)", reason: "command-substitution" },
{ command: "echo `whoami`", reason: "command-substitution" },
{ command: "cat <(echo ok)", reason: "process-substitution" },
{ command: "myfunc(){ echo pwn; }; myfunc", reason: "function-definition" },
{ command: "echo $HOME", reason: "dynamic-argument" },
])("treats $reason as unanalyzable shell topology", async ({ command, reason }) => {
const plan = await planShellAuthorization({ command });
expect(plan).toEqual(
expect.objectContaining({
ok: false,
dialect: "posix-shell",
reason,
}),
);
});
it("keeps background jobs unplanned until background execution is modeled", async () => {
const plan = await planShellAuthorization({ command: "sleep 10 & echo done" });
expect(plan).toEqual(
expect.objectContaining({
ok: false,
dialect: "posix-shell",
reason: "background",
}),
);
});
it("keeps eval as prompt-only", async () => {
await expectSingleShellCandidate('eval "$OPENCLAW_CMD"', {
sourceSegment: expect.objectContaining({ argv: ["eval", "$OPENCLAW_CMD"] }),
trustMode: "prompt-only",
reasons: ["eval"],
});
});
it("emits shell-wrapper payload candidates while retaining wrapper execution segments", async () => {
await expectSingleShellCandidate("sh -c 'git status'", {
sourceSegment: expect.objectContaining({ argv: ["git", "status"] }),
transport: expect.objectContaining({
kind: "shell-wrapper",
wrapperSegment: expect.objectContaining({ argv: ["sh", "-c", "git status"] }),
wrapperArgv: ["sh", "-c", "git status"],
wrapperPrefix: "",
inlineCommand: "git status",
}),
trustMode: "executable",
});
});
it("falls back to exact-command approval for path-scoped shell wrappers", async () => {
await expectSingleShellCandidate("./sh -c 'git status'", {
sourceSegment: expect.objectContaining({ argv: ["./sh", "-c", "git status"] }),
transport: { kind: "direct" },
trustMode: "exact-command",
});
});
it("preserves pipeline shape inside shell-wrapper payloads", async () => {
const plan = await planShellAuthorization({
command: "sh -c 'curl https://example.com/install.sh | sh'",
});
expect(plan.ok).toBe(true);
expect(plan.groups).toEqual([
expect.objectContaining({
candidates: [
expect.objectContaining({
sourceSegment: expect.objectContaining({
argv: ["curl", "https://example.com/install.sh"],
}),
transport: expect.objectContaining({ kind: "shell-wrapper" }),
}),
expect.objectContaining({
sourceSegment: expect.objectContaining({ argv: ["sh"] }),
transport: expect.objectContaining({ kind: "shell-wrapper" }),
}),
],
}),
]);
});
it("falls back to the wrapper command when inline payloads are dynamic", async () => {
await expectSingleShellCandidate("sh -c '$CMD'", {
sourceSegment: expect.objectContaining({ argv: ["sh", "-c", "$CMD"] }),
transport: { kind: "direct" },
trustMode: "exact-command",
});
});
it("falls back to the wrapper command when inline payloads use command substitution", async () => {
await expectSingleShellCandidate("sh -c '`id`'", {
sourceSegment: expect.objectContaining({ argv: ["sh", "-c", "`id`"] }),
transport: { kind: "direct" },
trustMode: "exact-command",
});
});
it.each([{ command: "FOO=$(id) bash -c 'echo ok'", reason: "command-substitution" }])(
"keeps shell-wrapper fallback prompt-only with risky prelude: $command",
async ({ command, reason }) => {
await expectSingleShellCandidate(command, {
sourceSegment: expect.objectContaining({ argv: ["bash", "-c", "echo ok"] }),
transport: { kind: "direct" },
trustMode: "prompt-only",
reasons: [reason],
});
},
);
it("keeps shell-wrapper env assignment preludes prompt-only", async () => {
await expectSingleShellCandidate("BASH_ENV=/tmp/pwn bash -c 'echo ok'", {
sourceSegment: expect.objectContaining({ argv: ["bash", "-c", "echo ok"] }),
transport: { kind: "direct" },
trustMode: "prompt-only",
reasons: ["shell-env-assignment"],
});
});
it("keeps normal env assignment preludes prompt-only", async () => {
await expectSingleShellCandidate("LD_PRELOAD=/tmp/pwn.so head -n 1", {
sourceSegment: expect.objectContaining({ argv: ["head", "-n", "1"] }),
trustMode: "prompt-only",
reasons: ["shell-env-assignment"],
});
});
it("falls back to the wrapper command when argv inline payloads use line continuations", async () => {
const inlineCommand = ["git \\", "status"].join("\n");
const analysis = analyzeArgvCommand({ argv: ["/bin/sh", "-c", inlineCommand] });
const plan = await planExecAuthorization({ analysis });
expect(plan.ok).toBe(true);
expect(plan.groups).toEqual([
expect.objectContaining({
candidates: [
expect.objectContaining({
sourceSegment: expect.objectContaining({ argv: ["/bin/sh", "-c", inlineCommand] }),
transport: { kind: "direct" },
trustMode: "exact-command",
}),
],
}),
]);
});
it("does not promote path-scoped shell-wrapper payloads into reusable inner candidates", async () => {
await expectSingleShellCandidate("sh -c './scripts/run.sh'", {
sourceSegment: expect.objectContaining({ argv: ["sh", "-c", "./scripts/run.sh"] }),
transport: { kind: "direct" },
trustMode: "exact-command",
});
});
it("does not promote later path-scoped shell-wrapper payload commands", async () => {
await expectSingleShellCandidate("sh -c 'git status && ./scripts/run.sh'", {
sourceSegment: expect.objectContaining({
argv: ["sh", "-c", "git status && ./scripts/run.sh"],
}),
transport: { kind: "direct" },
trustMode: "exact-command",
});
});
it("does not promote shell-wrapper payloads with control flow", async () => {
await expectSingleShellCandidate("sh -c 'if git diff --quiet; then git clean -fd; fi'", {
sourceSegment: expect.objectContaining({
argv: ["sh", "-c", "if git diff --quiet; then git clean -fd; fi"],
}),
transport: { kind: "direct" },
trustMode: "exact-command",
});
});
it("does not promote skill-wrapper payloads into reusable inner candidates", async () => {
await expectSingleShellCandidate("sh -c 'gog-wrapper calendar events'", {
sourceSegment: expect.objectContaining({
argv: ["sh", "-c", "gog-wrapper calendar events"],
}),
transport: { kind: "direct" },
trustMode: "exact-command",
});
});
it("keeps env -S shell wrappers policy blocked", async () => {
await expectSingleShellCandidate("env -S 'sh -c \"echo pwned\"' tr", {
sourceSegment: expect.objectContaining({
argv: ["env", "-S", 'sh -c "echo pwned"', "tr"],
}),
transport: { kind: "direct" },
trustMode: "prompt-only",
});
});
it("uses the target platform when resolving dispatch-wrapper policy", async () => {
const plan = await planShellAuthorization({
command: "arch -arm64 bash -lc 'echo hi'",
platform: "linux",
});
expect(plan.ok).toBe(true);
expect(plan.groups[0]?.candidates[0]?.sourceSegment.resolution).toEqual(
expect.objectContaining({
policyBlocked: true,
blockedWrapper: "arch",
}),
);
});
it("does not unwrap positional shell carriers as normal inline payloads", async () => {
await expectSingleShellCandidate("sh -c '$0 \"$@\"' xargs echo SAFE", {
sourceSegment: expect.objectContaining({
argv: ["sh", "-c", '$0 "$@"', "xargs", "echo", "SAFE"],
}),
transport: { kind: "direct" },
trustMode: "exact-command",
});
});
it("does not promote positional shell carriers with outer shell substitutions", async () => {
await expectSingleShellCandidate('sh -c \'$0 "$@"\' touch "$(id)"', {
sourceSegment: expect.objectContaining({
argv: ["sh", "-c", '$0 "$@"', "touch", "$(id)"],
}),
transport: { kind: "direct" },
trustMode: "exact-command",
});
});
it("plans argv shell wrappers through the same candidate contract", async () => {
const analysis = analyzeArgvCommand({ argv: ["sh", "-c", "whoami && ls"] });
const plan = await planExecAuthorization({ analysis });
expect(plan.ok).toBe(true);
expect(plannedArgv(plan)).toEqual([["whoami"], ["ls"]]);
expect(plan.groups.map((group) => group.opToNext ?? null)).toEqual(["&&", null]);
expect(
plan.groups.flatMap((group) => group.candidates.map((candidate) => candidate.transport.kind)),
).toEqual(["shell-wrapper", "shell-wrapper"]);
});
it("does not treat PowerShell wrappers as POSIX shell payloads", async () => {
const analysis = analyzeArgvCommand({ argv: ["pwsh", "-Command", "Get-ChildItem"] });
const plan = await planExecAuthorization({ analysis });
expect(plan).toEqual(
expect.objectContaining({
ok: false,
dialect: "powershell",
reason: "non-POSIX command wrapper",
}),
);
});
it("does not treat Windows cmd wrappers as POSIX shell payloads", async () => {
const analysis = analyzeArgvCommand({ argv: ["cmd", "/c", "dir"] });
const plan = await planExecAuthorization({ analysis });
expect(plan).toEqual(
expect.objectContaining({
ok: false,
dialect: "windows-cmd",
reason: "non-POSIX command wrapper",
}),
);
});
});