Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions src/agent/__tests__/hooks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,108 @@ describe("createDangerousCommandBlocker", () => {
expect(result).toHaveProperty("decision", "block");
});

test("blocks git push -f (short force flag)", async () => {
const hook = createDangerousCommandBlocker();
const callback = hook.hooks[0];

const result = await callback(
makeHookInput({
hook_event_name: "PreToolUse",
tool_name: "Bash",
tool_input: { command: "git push -f origin main" },
}),
undefined,
{ signal: new AbortController().signal },
);

expect(result).toHaveProperty("decision", "block");
});

test("blocks git push with +refspec force prefix", async () => {
const hook = createDangerousCommandBlocker();
const callback = hook.hooks[0];

const result = await callback(
makeHookInput({
hook_event_name: "PreToolUse",
tool_name: "Bash",
tool_input: { command: "git push origin +main:main" },
}),
undefined,
{ signal: new AbortController().signal },
);

expect(result).toHaveProperty("decision", "block");
});

test("blocks git push with quoted +refspec", async () => {
const hook = createDangerousCommandBlocker();
const callback = hook.hooks[0];

const result = await callback(
makeHookInput({
hook_event_name: "PreToolUse",
tool_name: "Bash",
tool_input: { command: 'git push origin "+HEAD:refs/heads/main"' },
}),
undefined,
{ signal: new AbortController().signal },
);

expect(result).toHaveProperty("decision", "block");
});

test("blocks git push --force-with-lease", async () => {
const hook = createDangerousCommandBlocker();
const callback = hook.hooks[0];

const result = await callback(
makeHookInput({
hook_event_name: "PreToolUse",
tool_name: "Bash",
tool_input: { command: "git push --force-with-lease origin main" },
}),
undefined,
{ signal: new AbortController().signal },
);

expect(result).toHaveProperty("decision", "block");
});

test("allows + inside a token (not a force refspec)", async () => {
const hook = createDangerousCommandBlocker();
const callback = hook.hooks[0];

const result = await callback(
makeHookInput({
hook_event_name: "PreToolUse",
tool_name: "Bash",
tool_input: { command: "git push origin tag+sign:main" },
}),
undefined,
{ signal: new AbortController().signal },
);

expect(result).toEqual({ continue: true });
});

test("allows -f outside a git push context", async () => {
const hook = createDangerousCommandBlocker();
const callback = hook.hooks[0];

const result = await callback(
makeHookInput({
hook_event_name: "PreToolUse",
tool_name: "Bash",
tool_input: { command: "ls -f /tmp" },
}),
undefined,
{ signal: new AbortController().signal },
);

expect(result).toEqual({ continue: true });
});

test("blocks docker system prune", async () => {
const hook = createDangerousCommandBlocker();
const callback = hook.hooks[0];
Expand Down
2 changes: 2 additions & 0 deletions src/agent/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const DANGEROUS_COMMANDS: { pattern: RegExp; label: string }[] = [
{ pattern: /docker\s+volume\s+prune/, label: "docker volume prune" },
{ pattern: /docker\s+system\s+prune/, label: "docker system prune" },
{ pattern: /git\s+push\s+.*--force/, label: "git push --force" },
{ pattern: /git\s+push\s+(?:\S+\s+)*-f(?=\s|$)/, label: "git push -f" },
{ pattern: /git\s+push\s+.*\s["']?\+\S+/, label: "git push +refspec" },
{ pattern: /git\s+reset\s+--hard/, label: "git reset --hard" },
{ pattern: /rm\s+-rf\s+\/(\s|$)/, label: "rm -rf /" },
{ pattern: /rm\s+-rf\s+\/home(\s|$)/, label: "rm -rf /home" },
Expand Down
Loading