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
@@ -2,7 +2,8 @@ import {
|
||||
checkContainsTrigger,
|
||||
escapeRegExp,
|
||||
} from "../src/github/validation/trigger";
|
||||
import { describe, it, expect } from "bun:test";
|
||||
import { describe, it, expect, beforeEach, afterEach } from "bun:test";
|
||||
import { _resetPlatformCache } from "../src/platform/detector";
|
||||
import {
|
||||
createMockContext,
|
||||
mockIssueAssignedContext,
|
||||
@@ -451,6 +452,75 @@ describe("checkContainsTrigger", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Forgejo empty-body trigger bypass", () => {
|
||||
let savedPlatform: string | undefined;
|
||||
let savedServerUrl: string | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
_resetPlatformCache();
|
||||
savedPlatform = process.env.PLATFORM;
|
||||
savedServerUrl = process.env.GITHUB_SERVER_URL;
|
||||
process.env.PLATFORM = "forgejo";
|
||||
process.env.GITHUB_SERVER_URL = "https://git.example.com";
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (savedPlatform !== undefined) process.env.PLATFORM = savedPlatform;
|
||||
else delete process.env.PLATFORM;
|
||||
if (savedServerUrl !== undefined)
|
||||
process.env.GITHUB_SERVER_URL = savedServerUrl;
|
||||
else delete process.env.GITHUB_SERVER_URL;
|
||||
_resetPlatformCache();
|
||||
});
|
||||
|
||||
it("should NOT trigger when Forgejo PR review comment has no body", () => {
|
||||
// Previously this returned true unconditionally, bypassing trigger check
|
||||
const context = {
|
||||
...mockPullRequestReviewCommentContext,
|
||||
payload: {
|
||||
...mockPullRequestReviewCommentContext.payload,
|
||||
comment: {
|
||||
...(mockPullRequestReviewCommentContext.payload as any).comment,
|
||||
body: "",
|
||||
},
|
||||
},
|
||||
} as ParsedGitHubContext;
|
||||
expect(checkContainsTrigger(context)).toBe(false);
|
||||
});
|
||||
|
||||
it("should NOT trigger when Forgejo PR review comment body is missing", () => {
|
||||
const context = {
|
||||
...mockPullRequestReviewCommentContext,
|
||||
payload: {
|
||||
...mockPullRequestReviewCommentContext.payload,
|
||||
comment: {
|
||||
...(mockPullRequestReviewCommentContext.payload as any).comment,
|
||||
body: undefined,
|
||||
},
|
||||
},
|
||||
} as ParsedGitHubContext;
|
||||
expect(checkContainsTrigger(context)).toBe(false);
|
||||
});
|
||||
|
||||
it("should still trigger when Forgejo PR review comment contains trigger phrase", () => {
|
||||
const context = {
|
||||
...mockPullRequestReviewCommentContext,
|
||||
inputs: {
|
||||
...mockPullRequestReviewCommentContext.inputs,
|
||||
triggerPhrase: "@claude",
|
||||
},
|
||||
payload: {
|
||||
...mockPullRequestReviewCommentContext.payload,
|
||||
comment: {
|
||||
...(mockPullRequestReviewCommentContext.payload as any).comment,
|
||||
body: "@claude please review this line",
|
||||
},
|
||||
},
|
||||
} as ParsedGitHubContext;
|
||||
expect(checkContainsTrigger(context)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("escapeRegExp", () => {
|
||||
it("should escape special regex characters", () => {
|
||||
expect(escapeRegExp(".*+?^${}()|[]\\")).toBe(
|
||||
|
||||
Reference in new issue
Block a user