Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.

Commit fb3d69e

Browse files
authored
fix(security): Run setup discovery agent in plan mode (#3802)
1 parent bf27356 commit fb3d69e

5 files changed

Lines changed: 60 additions & 2 deletions

File tree

‎packages/agent/src/adapters/claude/session/options.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,11 @@ export function buildSessionOptions(params: BuildOptionsParams): Options {
449449
...params.userProvidedOptions,
450450
betas: ["context-1m-2025-08-07"],
451451
systemPrompt: params.systemPrompt ?? buildSystemPrompt(),
452-
settingSources: ["user", "project", "local"],
452+
settingSources: params.userProvidedOptions?.settingSources ?? [
453+
"user",
454+
"project",
455+
"local",
456+
],
453457
stderr: (err) => params.logger.error(err),
454458
cwd: params.cwd,
455459
includePartialMessages: true,
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { beforeEach, describe, expect, it, vi } from "vitest";
2+
3+
const startMutate = vi.fn(async (_input: Record<string, unknown>) => {});
4+
5+
vi.mock("@posthog/di/container", () => ({
6+
resolveService: () => ({ agent: { start: { mutate: startMutate } } }),
7+
}));
8+
9+
vi.mock("../../shell/analytics", () => ({
10+
captureException: vi.fn(),
11+
track: vi.fn(),
12+
}));
13+
14+
import { SetupRunServiceImpl } from "./setupRunServiceImpl";
15+
16+
describe("SetupRunServiceImpl.startAgent", () => {
17+
beforeEach(() => {
18+
startMutate.mockClear();
19+
});
20+
21+
it("starts the discovery agent in plan mode, never bypassPermissions", async () => {
22+
const service = new SetupRunServiceImpl();
23+
24+
await service.startAgent({
25+
taskId: "task-1",
26+
taskRunId: "run-1",
27+
repoPath: "/repo",
28+
apiHost: "https://us.posthog.com",
29+
projectId: 1,
30+
jsonSchema: {},
31+
});
32+
33+
expect(startMutate).toHaveBeenCalledTimes(1);
34+
expect(startMutate.mock.calls[0][0]).toMatchObject({
35+
permissionMode: "plan",
36+
disallowedTools: ["EnterPlanMode", "ExitPlanMode", "AskUserQuestion"],
37+
settingSources: ["user"],
38+
});
39+
});
40+
});

‎packages/ui/src/features/setup/setupRunServiceImpl.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,11 @@ export class SetupRunServiceImpl implements ISetupRunService {
111111
repoPath: input.repoPath,
112112
apiHost: input.apiHost,
113113
projectId: input.projectId,
114-
permissionMode: "bypassPermissions",
114+
permissionMode: "plan",
115+
// Nothing answers a permission prompt on an auto-launched run.
116+
disallowedTools: ["EnterPlanMode", "ExitPlanMode", "AskUserQuestion"],
117+
// Never the repo's own .claude settings: they can carry hooks.
118+
settingSources: ["user"],
115119
jsonSchema: input.jsonSchema,
116120
});
117121
}

‎packages/workspace-server/src/services/agent/agent.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ function buildClaudeCodeOptions(args: {
248248
effort?: EffortLevel;
249249
plugins: { type: "local"; path: string }[];
250250
disallowedTools?: string[];
251+
settingSources?: ("user" | "project" | "local")[];
251252
}) {
252253
return {
253254
...(args.additionalDirectories?.length && {
@@ -257,6 +258,9 @@ function buildClaudeCodeOptions(args: {
257258
...(args.disallowedTools?.length && {
258259
disallowedTools: args.disallowedTools,
259260
}),
261+
...(args.settingSources?.length && {
262+
settingSources: args.settingSources,
263+
}),
260264
plugins: args.plugins,
261265
};
262266
}
@@ -278,6 +282,7 @@ interface SessionConfig {
278282
systemPromptOverride?: string;
279283
/** Tool names denied for this session (passed to the Claude SDK). */
280284
disallowedTools?: string[];
285+
settingSources?: ("user" | "project" | "local")[];
281286
/** Effort level for Claude sessions */
282287
effort?: EffortLevel;
283288
/** Model to use for the session (e.g. "claude-sonnet-4-6") */
@@ -779,6 +784,7 @@ If a repository IS genuinely required, attach one in this priority order:
779784
customInstructions,
780785
systemPromptOverride,
781786
disallowedTools,
787+
settingSources,
782788
effort,
783789
model,
784790
jsonSchema,
@@ -1010,6 +1016,7 @@ If a repository IS genuinely required, attach one in this priority order:
10101016
effort,
10111017
plugins,
10121018
disallowedTools,
1019+
settingSources,
10131020
});
10141021

10151022
let configOptions: SessionConfigOption[] | undefined;
@@ -2131,6 +2138,8 @@ For git operations while detached:
21312138
: undefined,
21322139
disallowedTools:
21332140
"disallowedTools" in params ? params.disallowedTools : undefined,
2141+
settingSources:
2142+
"settingSources" in params ? params.settingSources : undefined,
21342143
effort: "effort" in params ? params.effort : undefined,
21352144
model: "model" in params ? params.model : undefined,
21362145
jsonSchema: "jsonSchema" in params ? params.jsonSchema : undefined,

‎packages/workspace-server/src/services/agent/schemas.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export const startSessionInput = z.object({
7777
* Lets a sandboxed surface deny file/shell/network tools.
7878
*/
7979
disallowedTools: z.array(z.string()).optional(),
80+
settingSources: z.array(z.enum(["user", "project", "local"])).optional(),
8081
effort: effortLevelSchema.optional(),
8182
model: z.string().optional(),
8283
jsonSchema: z.record(z.string(), z.unknown()).nullish(),

0 commit comments

Comments
 (0)