mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-08-22 11:28:55 +08:00
Harden tag mode tool permissions against prompt injection (#1002)
Two defenses for tag mode where an attacker with repo write access could craft a prompt injection payload in an issue/PR to gain RCE on the Actions runner: 1. git-push wrapper (H1 #3556799) The Bash(git\ push:*) rule permitted arbitrary flags and remotes, including combinations that execute shell commands locally. Replaced with scripts/git-push.sh which allowlists exactly 'origin <ref>' with no flags, validates the ref via check-ref-format. Same pattern as scripts/gh.sh. 2. acceptEdits instead of blanket Write/Edit (Asana 1213310082312048) Edit/MultiEdit/Write in allowedTools granted write access to the whole runner filesystem (~/.bashrc etc). Removed from allowedTools and set --permission-mode acceptEdits, which auto-accepts edits inside cwd ($GITHUB_WORKSPACE) and denies outside. Headless SDK has no prompt handler so 'ask' becomes deny. Also: - Noted that create-prompt/index.ts exports ALLOWED_TOOLS env var that nothing reads. The live path is modes/tag/index.ts. Mirrored the fix in both so the file the H1 report likely points to stays in sync. - Updated prompt text (3 callsites) to reference the wrapper. - Updated tests (4 prompt-content asserts, 7 tool-list asserts).
This commit is contained in:
+23
-18
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import { describe, test, expect } from "bun:test";
|
||||
import { describe, test, expect, beforeAll } from "bun:test";
|
||||
import {
|
||||
generatePrompt,
|
||||
getEventTypeAndContext,
|
||||
@@ -9,6 +9,10 @@ import {
|
||||
} from "../src/create-prompt";
|
||||
import type { PreparedContext } from "../src/create-prompt";
|
||||
|
||||
beforeAll(() => {
|
||||
process.env.GITHUB_ACTION_PATH = "/test/action/path";
|
||||
});
|
||||
|
||||
describe("generatePrompt", () => {
|
||||
const mockGitHubData = {
|
||||
contextData: {
|
||||
@@ -505,7 +509,7 @@ describe("generatePrompt", () => {
|
||||
const prompt = await generatePrompt(envVars, mockGitHubData, false, "tag");
|
||||
|
||||
// Should contain PR-specific instructions (git commands when not using signing)
|
||||
expect(prompt).toContain("git push");
|
||||
expect(prompt).toContain("scripts/git-push.sh origin");
|
||||
expect(prompt).toContain(
|
||||
"Always push to the existing branch when triggered on a PR",
|
||||
);
|
||||
@@ -643,7 +647,7 @@ describe("generatePrompt", () => {
|
||||
const prompt = await generatePrompt(envVars, mockGitHubData, false, "tag");
|
||||
|
||||
// Should contain open PR instructions (git commands when not using signing)
|
||||
expect(prompt).toContain("git push");
|
||||
expect(prompt).toContain("scripts/git-push.sh origin");
|
||||
expect(prompt).toContain(
|
||||
"Always push to the existing branch when triggered on a PR",
|
||||
);
|
||||
@@ -757,7 +761,7 @@ describe("generatePrompt", () => {
|
||||
expect(prompt).toContain("Use git commands via the Bash tool");
|
||||
expect(prompt).toContain("git add");
|
||||
expect(prompt).toContain("git commit");
|
||||
expect(prompt).toContain("git push");
|
||||
expect(prompt).toContain("scripts/git-push.sh origin");
|
||||
|
||||
// Should use the minimal comment tool
|
||||
expect(prompt).toContain("mcp__github_comment__update_claude_comment");
|
||||
@@ -886,17 +890,18 @@ describe("buildAllowedToolsString", () => {
|
||||
const result = buildAllowedToolsString();
|
||||
|
||||
// The base tools should be in the result
|
||||
expect(result).toContain("Edit");
|
||||
// Edit/MultiEdit/Write are NOT in allowedTools — acceptEdits permission mode handles them
|
||||
expect(result).not.toContain("Edit");
|
||||
expect(result).not.toContain("Write");
|
||||
expect(result).toContain("Glob");
|
||||
expect(result).toContain("Grep");
|
||||
expect(result).toContain("LS");
|
||||
expect(result).toContain("Read");
|
||||
expect(result).toContain("Write");
|
||||
|
||||
// Default is no commit signing, so should have specific Bash git commands
|
||||
expect(result).toContain("Bash(git add:*)");
|
||||
expect(result).toContain("Bash(git commit:*)");
|
||||
expect(result).toContain("Bash(git push:*)");
|
||||
expect(result).toContain("scripts/git-push.sh:*)");
|
||||
expect(result).toContain("mcp__github_comment__update_claude_comment");
|
||||
|
||||
// Should not have commit signing tools
|
||||
@@ -908,12 +913,12 @@ describe("buildAllowedToolsString", () => {
|
||||
const result = buildAllowedToolsString([], false, false);
|
||||
|
||||
// The base tools should be in the result
|
||||
expect(result).toContain("Edit");
|
||||
expect(result).not.toContain("Edit");
|
||||
expect(result).toContain("Glob");
|
||||
expect(result).toContain("Grep");
|
||||
expect(result).toContain("LS");
|
||||
expect(result).toContain("Read");
|
||||
expect(result).toContain("Write");
|
||||
expect(result).not.toContain("Write");
|
||||
|
||||
// Should have specific Bash git commands for non-signing mode
|
||||
expect(result).toContain("Bash(git add:*)");
|
||||
@@ -930,7 +935,7 @@ describe("buildAllowedToolsString", () => {
|
||||
const result = buildAllowedToolsString(customTools);
|
||||
|
||||
// Base tools should be present
|
||||
expect(result).toContain("Edit");
|
||||
expect(result).toContain("Read");
|
||||
expect(result).toContain("Glob");
|
||||
|
||||
// Custom tools should be appended
|
||||
@@ -950,7 +955,7 @@ describe("buildAllowedToolsString", () => {
|
||||
const result = buildAllowedToolsString([], true);
|
||||
|
||||
// Base tools should be present
|
||||
expect(result).toContain("Edit");
|
||||
expect(result).toContain("Read");
|
||||
expect(result).toContain("Glob");
|
||||
|
||||
// GitHub Actions tools should be included
|
||||
@@ -964,7 +969,7 @@ describe("buildAllowedToolsString", () => {
|
||||
const result = buildAllowedToolsString(customTools, true);
|
||||
|
||||
// Base tools should be present
|
||||
expect(result).toContain("Edit");
|
||||
expect(result).toContain("Read");
|
||||
|
||||
// Custom tools should be included
|
||||
expect(result).toContain("Tool1");
|
||||
@@ -980,12 +985,12 @@ describe("buildAllowedToolsString", () => {
|
||||
const result = buildAllowedToolsString([], false, true);
|
||||
|
||||
// Base tools should be present
|
||||
expect(result).toContain("Edit");
|
||||
expect(result).not.toContain("Edit");
|
||||
expect(result).toContain("Glob");
|
||||
expect(result).toContain("Grep");
|
||||
expect(result).toContain("LS");
|
||||
expect(result).toContain("Read");
|
||||
expect(result).toContain("Write");
|
||||
expect(result).not.toContain("Write");
|
||||
|
||||
// Commit signing tools should be included
|
||||
expect(result).toContain("mcp__github_file_ops__commit_files");
|
||||
@@ -1001,17 +1006,17 @@ describe("buildAllowedToolsString", () => {
|
||||
const result = buildAllowedToolsString([], false, false);
|
||||
|
||||
// Base tools should be present
|
||||
expect(result).toContain("Edit");
|
||||
expect(result).not.toContain("Edit");
|
||||
expect(result).toContain("Glob");
|
||||
expect(result).toContain("Grep");
|
||||
expect(result).toContain("LS");
|
||||
expect(result).toContain("Read");
|
||||
expect(result).toContain("Write");
|
||||
expect(result).not.toContain("Write");
|
||||
|
||||
// Specific Bash git commands should be included
|
||||
expect(result).toContain("Bash(git add:*)");
|
||||
expect(result).toContain("Bash(git commit:*)");
|
||||
expect(result).toContain("Bash(git push:*)");
|
||||
expect(result).toContain("scripts/git-push.sh:*)");
|
||||
expect(result).toContain("Bash(git status:*)");
|
||||
expect(result).toContain("Bash(git diff:*)");
|
||||
expect(result).toContain("Bash(git log:*)");
|
||||
@@ -1030,7 +1035,7 @@ describe("buildAllowedToolsString", () => {
|
||||
const result = buildAllowedToolsString(customTools, true, false);
|
||||
|
||||
// Base tools should be present
|
||||
expect(result).toContain("Edit");
|
||||
expect(result).toContain("Read");
|
||||
expect(result).toContain("Bash(git add:*)");
|
||||
|
||||
// Custom tools should be included
|
||||
|
||||
@@ -135,7 +135,7 @@ describe("pull_request_target event support", () => {
|
||||
const prompt = generatePrompt(envVars, mockGitHubData, false, "tag");
|
||||
|
||||
// Should include git commands for non-commit-signing mode
|
||||
expect(prompt).toContain("git push");
|
||||
expect(prompt).toContain("scripts/git-push.sh origin");
|
||||
expect(prompt).toContain(
|
||||
"Always push to the existing branch when triggered on a PR",
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user