swadhinbiswas Harden AI callback auth and enforce suggestion path permissions · 2/18/2026
019051f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import { beforeEach, describe, expect, it, vi } from "vitest";
const {
getUserFromRequestMock,
canApplySuggestionsMock,
applySuggestionMock,
batchApplySuggestionsMock,
checkPathPermissionsMock,
fakeSchema,
} = vi.hoisted(() => ({
getUserFromRequestMock: vi.fn(async () => ({ userId: "user-1" })),
canApplySuggestionsMock: vi.fn(async () => true),
applySuggestionMock: vi.fn(async () => ({ success: true, commitSha: "abc123" })),
batchApplySuggestionsMock: vi.fn(async () => ({ success: true, applied: [], failed: [] })),
checkPathPermissionsMock: vi.fn(async () => ({ allowed: true, deniedPaths: [] })),
fakeSchema: {
users: { username: {}, id: {} },
repositories: { ownerId: {}, name: {}, id: {} },
pullRequests: { repositoryId: {}, number: {}, id: {} },
pullRequestComments: { pullRequestId: {}, id: {} },
} as any,
}));
let mockDb: any;
vi.mock("@/db", () => ({
getDatabase: () => mockDb,
schema: fakeSchema,
}));
vi.mock("@/lib/auth", () => ({
getUserFromRequest: getUserFromRequestMock,
}));
vi.mock("@/lib/suggestions", () => ({
canApplySuggestions: canApplySuggestionsMock,
applySuggestion: applySuggestionMock,
batchApplySuggestions: batchApplySuggestionsMock,
}));
vi.mock("@/lib/path-scoping", () => ({
checkPathPermissions: checkPathPermissionsMock,
}));
import { POST as applySuggestionsPost } from "@/pages/api/repos/[owner]/[repo]/pulls/[pullNumber]/suggestions/apply";
function makeDb() {
const owner = { id: "owner-1" };
const repository = { id: "repo-1" };
const pr = { id: "pr-1" };
const comments = [
{ id: "comment-1", path: "secure/config.yml" },
{ id: "comment-2", path: "src/app.ts" },
];
return {
query: {
users: {
findFirst: vi.fn(async () => owner),
},
repositories: {
findFirst: vi.fn(async () => repository),
},
pullRequests: {
findFirst: vi.fn(async () => pr),
},
pullRequestComments: {
findMany: vi.fn(async () => comments),
},
},
};
}
async function readJson(response: Response): Promise<any> {
return response.json();
}
describe("suggestions apply route", () => {
beforeEach(() => {
mockDb = makeDb();
getUserFromRequestMock.mockResolvedValue({ userId: "user-1" });
canApplySuggestionsMock.mockResolvedValue(true);
applySuggestionMock.mockResolvedValue({ success: true, commitSha: "abc123" });
batchApplySuggestionsMock.mockResolvedValue({ success: true, applied: ["comment-1"], failed: [] });
checkPathPermissionsMock.mockResolvedValue({ allowed: true, deniedPaths: [] });
});
it("blocks apply when path-scoped write permission is denied", async () => {
checkPathPermissionsMock.mockResolvedValue({
allowed: false,
deniedPaths: ["secure/config.yml"],
reason: "Insufficient permissions for paths: secure/config.yml",
});
const response = await applySuggestionsPost({
params: { owner: "owner-1", repo: "demo", pullNumber: "42" },
request: new Request("http://localhost/api/repos/owner-1/demo/pulls/42/suggestions/apply", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ commentIds: ["comment-1"] }),
}),
} as any);
const body = await readJson(response);
expect(response.status).toBe(403);
expect(body?.error?.code).toBe("FORBIDDEN");
expect(applySuggestionMock).not.toHaveBeenCalled();
});
it("applies suggestion when permission checks pass", async () => {
const response = await applySuggestionsPost({
params: { owner: "owner-1", repo: "demo", pullNumber: "42" },
request: new Request("http://localhost/api/repos/owner-1/demo/pulls/42/suggestions/apply", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ commentIds: ["comment-1"] }),
}),
} as any);
const body = await readJson(response);
expect(response.status).toBe(200);
expect(body?.success).toBe(true);
expect(checkPathPermissionsMock).toHaveBeenCalledWith(
"user-1",
"repo-1",
["secure/config.yml"],
"write"
);
expect(applySuggestionMock).toHaveBeenCalledWith("comment-1", "user-1");
});
});