fix(prd-7): M3 security review — self-trigger guard, actor permissions, trigger bypass, platform validation
- Add self-mention/bot loop prevention in actor.ts (blocks actor matching bot_name) - Switch Forgejo permission check to actor-level via getCollaboratorPermissionLevel with fallback to token-level repos.get() when collaborator API returns 403 - Close empty-body PR review comment trigger bypass (now returns false instead of true) - Add platform detection cross-validation (reject PLATFORM=forgejo on github.com) - Document URL builder defense-in-depth rationale in comment-logic.ts - Update/add tests across 6 test files (actor, permissions, trigger, platform, mcp, m7) Progress: 50% complete (3/6 milestones) — next: M4 test coverage review Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
cefc229199
commit
6b0302d226
12 files changed
+270
-68
No files matched your search
+37
-2
@@ -121,6 +121,8 @@ describe("checkHumanActor", () => {
|
||||
savedPlatform = process.env.PLATFORM;
|
||||
savedServerUrl = process.env.GITHUB_SERVER_URL;
|
||||
process.env.PLATFORM = "forgejo";
|
||||
// Ensure cross-validation doesn't reject PLATFORM=forgejo
|
||||
process.env.GITHUB_SERVER_URL = "https://git.example.com";
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -132,7 +134,7 @@ describe("checkHumanActor", () => {
|
||||
_resetPlatformCache();
|
||||
});
|
||||
|
||||
test("should skip actor check entirely on Forgejo", async () => {
|
||||
test("should skip GitHub API actor check on Forgejo for regular users", async () => {
|
||||
let apiCalled = false;
|
||||
const mockOctokit = {
|
||||
users: {
|
||||
@@ -151,7 +153,7 @@ describe("checkHumanActor", () => {
|
||||
expect(apiCalled).toBe(false);
|
||||
});
|
||||
|
||||
test("should skip actor check for bot-like names on Forgejo", async () => {
|
||||
test("should skip actor check for bot-like names that don't match bot_name", async () => {
|
||||
let apiCalled = false;
|
||||
const mockOctokit = {
|
||||
users: {
|
||||
@@ -170,5 +172,38 @@ describe("checkHumanActor", () => {
|
||||
).resolves.toBeUndefined();
|
||||
expect(apiCalled).toBe(false);
|
||||
});
|
||||
|
||||
test("should block self-trigger when actor matches bot_name (with [bot] suffix)", async () => {
|
||||
const mockOctokit = createMockOctokit("User");
|
||||
const context = createMockContext();
|
||||
// Default botName is "claude[bot]" from CLAUDE_BOT_LOGIN
|
||||
context.actor = "claude[bot]";
|
||||
|
||||
await expect(checkHumanActor(mockOctokit, context)).rejects.toThrow(
|
||||
"Blocking self-trigger on Forgejo",
|
||||
);
|
||||
});
|
||||
|
||||
test("should block self-trigger when actor matches bot_name without [bot] suffix", async () => {
|
||||
const mockOctokit = createMockOctokit("User");
|
||||
const context = createMockContext();
|
||||
context.inputs.botName = "claude[bot]";
|
||||
context.actor = "claude";
|
||||
|
||||
await expect(checkHumanActor(mockOctokit, context)).rejects.toThrow(
|
||||
"Blocking self-trigger on Forgejo",
|
||||
);
|
||||
});
|
||||
|
||||
test("should block self-trigger with custom bot_name", async () => {
|
||||
const mockOctokit = createMockOctokit("User");
|
||||
const context = createMockContext();
|
||||
context.inputs.botName = "my-assistant";
|
||||
context.actor = "my-assistant";
|
||||
|
||||
await expect(checkHumanActor(mockOctokit, context)).rejects.toThrow(
|
||||
"Blocking self-trigger on Forgejo",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user