Compare commits

..

1 Commits

Author SHA1 Message Date
Ashwin Bhat
3c5b0a6e9c tmp 2025-08-05 10:52:43 -07:00
13 changed files with 4 additions and 167 deletions

View File

@@ -10,7 +10,7 @@ Thank you for trying out the beta of our GitHub Action! This document outlines o
- **Support for workflow_dispatch and repository_dispatch events** - Dispatch Claude on events triggered via API from other workflows or from other services
- **Ability to disable commit signing** - Option to turn off GPG signing for environments where it's not required. This will enable Claude to use normal `git` bash commands for committing. This will likely become the default behavior once added.
- **Better code review behavior** - Support inline comments on specific lines, provide higher quality reviews with more actionable feedback
- ~**Support triggering @claude from bot users** - Allow automation and bot accounts to invoke Claude~
- **Support triggering @claude from bot users** - Allow automation and bot accounts to invoke Claude
- **Customizable base prompts** - Full control over Claude's initial context with template variables like `$PR_COMMENTS`, `$PR_FILES`, etc. Users can replace our default prompt entirely while still accessing key contextual data
---

View File

@@ -23,10 +23,6 @@ inputs:
description: "The prefix to use for Claude branches (defaults to 'claude/', use 'claude-' for dash format)"
required: false
default: "claude/"
allowed_bots:
description: "Comma-separated list of allowed bot usernames, or '*' to allow all bots. Empty string (default) allows no bots."
required: false
default: ""
# Mode configuration
mode:
@@ -160,7 +156,6 @@ runs:
OVERRIDE_PROMPT: ${{ inputs.override_prompt }}
MCP_CONFIG: ${{ inputs.mcp_config }}
OVERRIDE_GITHUB_TOKEN: ${{ inputs.github_token }}
ALLOWED_BOTS: ${{ inputs.allowed_bots }}
GITHUB_RUN_ID: ${{ github.run_id }}
USE_STICKY_COMMENT: ${{ inputs.use_sticky_comment }}
DEFAULT_WORKFLOW_TOKEN: ${{ github.token }}

View File

@@ -3,7 +3,7 @@
## Access Control
- **Repository Access**: The action can only be triggered by users with write access to the repository
- **Bot User Control**: By default, GitHub Apps and bots cannot trigger this action for security reasons. Use the `allowed_bots` parameter to enable specific bots or all bots
- **No Bot Triggers**: GitHub Apps and bots cannot trigger this action
- **Token Permissions**: The GitHub app receives only a short-lived token scoped specifically to the repository it's operating in
- **No Cross-Repository Access**: Each action invocation is limited to the repository where it was triggered
- **Limited Scope**: The token cannot access other repositories or perform actions beyond the configured permissions

View File

@@ -42,8 +42,6 @@ jobs:
# Optional: grant additional permissions (requires corresponding GitHub token permissions)
# additional_permissions: |
# actions: read
# Optional: allow bot users to trigger the action
# allowed_bots: "dependabot[bot],renovate[bot]"
```
## Inputs
@@ -78,7 +76,6 @@ jobs:
| `additional_permissions` | Additional permissions to enable. Currently supports 'actions: read' for viewing workflow results | No | "" |
| `experimental_allowed_domains` | Restrict network access to these domains only (newline-separated). | No | "" |
| `use_commit_signing` | Enable commit signing using GitHub's commit signature verification. When false, Claude uses standard git commands | No | `false` |
| `allowed_bots` | Comma-separated list of allowed bot usernames, or '\*' to allow all bots. Empty string (default) allows no bots | No | "" |
\*Required when using direct Anthropic API (default and when not using Bedrock or Vertex)

View File

@@ -77,7 +77,6 @@ type BaseContext = {
useStickyComment: boolean;
additionalPermissions: Map<string, string>;
useCommitSigning: boolean;
allowedBots: string;
};
};
@@ -137,7 +136,6 @@ export function parseGitHubContext(): GitHubContext {
process.env.ADDITIONAL_PERMISSIONS ?? "",
),
useCommitSigning: process.env.USE_COMMIT_SIGNING === "true",
allowedBots: process.env.ALLOWED_BOTS ?? "",
},
};

View File

@@ -33,7 +33,7 @@ export async function configureGitAuth(
if (user) {
const botName = user.login;
const botId = user.id;
console.log(`Setting git user as ${botName}...`);
console.log(`Setting git user as ${botName} (id: ${botId})...`);
await $`git config user.name "${botName}"`;
await $`git config user.email "${botId}+${botName}@${noreplyDomain}"`;
console.log(`✓ Set git user as ${botName}`);

View File

@@ -21,42 +21,9 @@ export async function checkHumanActor(
console.log(`Actor type: ${actorType}`);
// Check bot permissions if actor is not a User
if (actorType !== "User") {
const allowedBots = githubContext.inputs.allowedBots;
// Check if all bots are allowed
if (allowedBots.trim() === "*") {
console.log(
`All bots are allowed, skipping human actor check for: ${githubContext.actor}`,
);
return;
}
// Parse allowed bots list
const allowedBotsList = allowedBots
.split(",")
.map((bot) =>
bot
.trim()
.toLowerCase()
.replace(/\[bot\]$/, ""),
)
.filter((bot) => bot.length > 0);
const botName = githubContext.actor.toLowerCase().replace(/\[bot\]$/, "");
// Check if specific bot is allowed
if (allowedBotsList.includes(botName)) {
console.log(
`Bot ${botName} is in allowed list, skipping human actor check`,
);
return;
}
// Bot not allowed
throw new Error(
`Workflow initiated by non-human actor: ${botName} (type: ${actorType}). Add bot to allowed_bots list or use '*' to allow all bots.`,
`Workflow initiated by non-human actor: ${githubContext.actor} (type: ${actorType}).`,
);
}

View File

@@ -17,12 +17,6 @@ export async function checkWritePermissions(
try {
core.info(`Checking permissions for actor: ${actor}`);
// Check if the actor is a GitHub App (bot user)
if (actor.endsWith("[bot]")) {
core.info(`Actor is a GitHub App: ${actor}`);
return true;
}
// Check permissions directly using the permission endpoint
const response = await octokit.repos.getCollaboratorPermissionLevel({
owner: repository.owner,

View File

@@ -1,96 +0,0 @@
#!/usr/bin/env bun
import { describe, test, expect } from "bun:test";
import { checkHumanActor } from "../src/github/validation/actor";
import type { Octokit } from "@octokit/rest";
import { createMockContext } from "./mockContext";
function createMockOctokit(userType: string): Octokit {
return {
users: {
getByUsername: async () => ({
data: {
type: userType,
},
}),
},
} as unknown as Octokit;
}
describe("checkHumanActor", () => {
test("should pass for human actor", async () => {
const mockOctokit = createMockOctokit("User");
const context = createMockContext();
context.actor = "human-user";
await expect(
checkHumanActor(mockOctokit, context),
).resolves.toBeUndefined();
});
test("should throw error for bot actor when not allowed", async () => {
const mockOctokit = createMockOctokit("Bot");
const context = createMockContext();
context.actor = "test-bot[bot]";
context.inputs.allowedBots = "";
await expect(checkHumanActor(mockOctokit, context)).rejects.toThrow(
"Workflow initiated by non-human actor: test-bot (type: Bot). Add bot to allowed_bots list or use '*' to allow all bots.",
);
});
test("should pass for bot actor when all bots allowed", async () => {
const mockOctokit = createMockOctokit("Bot");
const context = createMockContext();
context.actor = "test-bot[bot]";
context.inputs.allowedBots = "*";
await expect(
checkHumanActor(mockOctokit, context),
).resolves.toBeUndefined();
});
test("should pass for specific bot when in allowed list", async () => {
const mockOctokit = createMockOctokit("Bot");
const context = createMockContext();
context.actor = "dependabot[bot]";
context.inputs.allowedBots = "dependabot[bot],renovate[bot]";
await expect(
checkHumanActor(mockOctokit, context),
).resolves.toBeUndefined();
});
test("should pass for specific bot when in allowed list (without [bot])", async () => {
const mockOctokit = createMockOctokit("Bot");
const context = createMockContext();
context.actor = "dependabot[bot]";
context.inputs.allowedBots = "dependabot,renovate";
await expect(
checkHumanActor(mockOctokit, context),
).resolves.toBeUndefined();
});
test("should throw error for bot not in allowed list", async () => {
const mockOctokit = createMockOctokit("Bot");
const context = createMockContext();
context.actor = "other-bot[bot]";
context.inputs.allowedBots = "dependabot[bot],renovate[bot]";
await expect(checkHumanActor(mockOctokit, context)).rejects.toThrow(
"Workflow initiated by non-human actor: other-bot (type: Bot). Add bot to allowed_bots list or use '*' to allow all bots.",
);
});
test("should throw error for bot not in allowed list (without [bot])", async () => {
const mockOctokit = createMockOctokit("Bot");
const context = createMockContext();
context.actor = "other-bot[bot]";
context.inputs.allowedBots = "dependabot,renovate";
await expect(checkHumanActor(mockOctokit, context)).rejects.toThrow(
"Workflow initiated by non-human actor: other-bot (type: Bot). Add bot to allowed_bots list or use '*' to allow all bots.",
);
});
});

View File

@@ -37,7 +37,6 @@ describe("prepareMcpConfig", () => {
useStickyComment: false,
additionalPermissions: new Map(),
useCommitSigning: false,
allowedBots: "",
},
};

View File

@@ -28,7 +28,6 @@ const defaultInputs = {
useStickyComment: false,
additionalPermissions: new Map<string, string>(),
useCommitSigning: false,
allowedBots: "",
};
const defaultRepository = {

View File

@@ -73,7 +73,6 @@ describe("checkWritePermissions", () => {
useStickyComment: false,
additionalPermissions: new Map(),
useCommitSigning: false,
allowedBots: "",
},
});
@@ -127,16 +126,6 @@ describe("checkWritePermissions", () => {
);
});
test("should return true for bot user", async () => {
const mockOctokit = createMockOctokit("none");
const context = createContext();
context.actor = "test-bot[bot]";
const result = await checkWritePermissions(mockOctokit, context);
expect(result).toBe(true);
});
test("should throw error when permission check fails", async () => {
const error = new Error("API error");
const mockOctokit = {

View File

@@ -41,7 +41,6 @@ describe("checkContainsTrigger", () => {
useStickyComment: false,
additionalPermissions: new Map(),
useCommitSigning: false,
allowedBots: "",
},
});
expect(checkContainsTrigger(context)).toBe(true);
@@ -75,7 +74,6 @@ describe("checkContainsTrigger", () => {
useStickyComment: false,
additionalPermissions: new Map(),
useCommitSigning: false,
allowedBots: "",
},
});
expect(checkContainsTrigger(context)).toBe(false);
@@ -293,7 +291,6 @@ describe("checkContainsTrigger", () => {
useStickyComment: false,
additionalPermissions: new Map(),
useCommitSigning: false,
allowedBots: "",
},
});
expect(checkContainsTrigger(context)).toBe(true);
@@ -328,7 +325,6 @@ describe("checkContainsTrigger", () => {
useStickyComment: false,
additionalPermissions: new Map(),
useCommitSigning: false,
allowedBots: "",
},
});
expect(checkContainsTrigger(context)).toBe(true);
@@ -363,7 +359,6 @@ describe("checkContainsTrigger", () => {
useStickyComment: false,
additionalPermissions: new Map(),
useCommitSigning: false,
allowedBots: "",
},
});
expect(checkContainsTrigger(context)).toBe(false);