mirror of
https://github.com/harivansh-afk/clanker-agent.git
synced 2026-04-15 07:04:45 +00:00
fix: validate computer snapshot ids
- reject unsafe snapshot ids in the TypeScript wrapper before spawning the helper - reject unsafe snapshot ids in agent-computer before loading snapshot files - add regression coverage for wrapper and helper traversal attempts Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
parent
a4250bad30
commit
8a43732b7e
2 changed files with 57 additions and 0 deletions
|
|
@ -31,6 +31,7 @@ const computerActions = [
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
const computerObservationModes = ["hybrid", "ocr"] as const;
|
const computerObservationModes = ["hybrid", "ocr"] as const;
|
||||||
|
const computerSnapshotIdPattern = /^[A-Za-z0-9_-]+$/;
|
||||||
|
|
||||||
const DEFAULT_COMPUTER_COMMAND =
|
const DEFAULT_COMPUTER_COMMAND =
|
||||||
process.env.COMPANION_AGENT_COMPUTER_COMMAND || "agent-computer";
|
process.env.COMPANION_AGENT_COMPUTER_COMMAND || "agent-computer";
|
||||||
|
|
@ -285,6 +286,12 @@ function hasDragDestination(input: ComputerToolInput): boolean {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validateSnapshotId(snapshotId: string): void {
|
||||||
|
if (!computerSnapshotIdPattern.test(snapshotId)) {
|
||||||
|
throw new Error(`Invalid computer snapshotId: "${snapshotId}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function validateWaitInput(input: ComputerToolInput): void {
|
function validateWaitInput(input: ComputerToolInput): void {
|
||||||
const targetCount =
|
const targetCount =
|
||||||
(input.ref !== undefined ? 1 : 0) +
|
(input.ref !== undefined ? 1 : 0) +
|
||||||
|
|
@ -307,6 +314,10 @@ function validateWaitInput(input: ComputerToolInput): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateComputerInput(input: ComputerToolInput): void {
|
function validateComputerInput(input: ComputerToolInput): void {
|
||||||
|
if (input.snapshotId !== undefined) {
|
||||||
|
validateSnapshotId(input.snapshotId);
|
||||||
|
}
|
||||||
|
|
||||||
switch (input.action) {
|
switch (input.action) {
|
||||||
case "observe":
|
case "observe":
|
||||||
case "app_list":
|
case "app_list":
|
||||||
|
|
|
||||||
|
|
@ -159,6 +159,27 @@ describe("computer tool", () => {
|
||||||
expect(calls).toHaveLength(0);
|
expect(calls).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("rejects unsafe snapshot ids before spawning the helper", async () => {
|
||||||
|
const cwd = createTempDir("coding-agent-computer-snapshot-id-");
|
||||||
|
const stateDir = join(cwd, "computer-state");
|
||||||
|
const { calls, operations } = createMockComputerOperations();
|
||||||
|
|
||||||
|
const computerTool = createComputerTool(cwd, {
|
||||||
|
operations,
|
||||||
|
stateDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
computerTool.execute("computer-click-invalid-snapshot", {
|
||||||
|
action: "click",
|
||||||
|
snapshotId: "../../auth",
|
||||||
|
ref: "w1",
|
||||||
|
}),
|
||||||
|
).rejects.toThrow('Invalid computer snapshotId: "../../auth"');
|
||||||
|
|
||||||
|
expect(calls).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
it("accepts computer in --tools and exposes it in built-in tool wiring", () => {
|
it("accepts computer in --tools and exposes it in built-in tool wiring", () => {
|
||||||
const parsed = parseArgs(["--tools", "computer,read"]);
|
const parsed = parseArgs(["--tools", "computer,read"]);
|
||||||
expect(parsed.tools).toEqual(["computer", "read"]);
|
expect(parsed.tools).toEqual(["computer", "read"]);
|
||||||
|
|
@ -234,4 +255,29 @@ describe("computer tool", () => {
|
||||||
expect(result.stderr).toContain("app_not_found:");
|
expect(result.stderr).toContain("app_not_found:");
|
||||||
expect(existsSync(markerPath)).toBe(false);
|
expect(existsSync(markerPath)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("rejects snapshot path traversal inside the helper", () => {
|
||||||
|
const stateDir = createTempDir("coding-agent-computer-helper-snapshot-id-");
|
||||||
|
const result = spawnSync(
|
||||||
|
process.execPath,
|
||||||
|
[
|
||||||
|
"--no-warnings",
|
||||||
|
getAgentComputerScriptPath(),
|
||||||
|
"--state-dir",
|
||||||
|
stateDir,
|
||||||
|
"--input",
|
||||||
|
JSON.stringify({
|
||||||
|
action: "click",
|
||||||
|
snapshotId: "../../auth",
|
||||||
|
ref: "w1",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
{
|
||||||
|
encoding: "utf8",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.status).not.toBe(0);
|
||||||
|
expect(result.stderr).toContain("invalid_snapshot_id: ../../auth");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue