fix(agent): request approval for protected path access
Generic Container CI/CD / test-build-publish (push) Successful in 2m4s
Agent CI/CD v2 / build-test-publish-and-deploy (push) Successful in 2m4s

This commit is contained in:
2026-09-25 15:44:46 +08:00
parent 0eab59c52e
commit 7f4918a1de
9 changed files with 214 additions and 78 deletions
+68 -15
View File
@@ -584,6 +584,28 @@ describe("streamPromptResponse", () => {
always: ["npm test"],
},
},
{
type: "permission.asked",
properties: {
id: "perm-auto-external",
sessionID: "runtime-session-1",
permission: "external_directory",
patterns: ["/app/opencode.json/*"],
metadata: { filepath: "/app/opencode.json" },
always: ["/app/opencode.json/*"],
},
},
{
type: "permission.asked",
properties: {
id: "perm-auto-env",
sessionID: "runtime-session-1",
permission: "read",
patterns: [".env"],
metadata: { filepath: ".env" },
always: [".env"],
},
},
{ type: "session.idle", properties: { sessionID: "runtime-session-1" } },
]),
prompt: async () => undefined,
@@ -606,9 +628,19 @@ describe("streamPromptResponse", () => {
request_id: "perm-auto-bash",
permission: "bash",
});
expect(events.filter((item) => item.event === "permission_request")[1]?.data).toMatchObject({
request_id: "perm-auto-external",
permission: "external_directory",
target: "/app/opencode.json",
});
expect(events.filter((item) => item.event === "permission_request")[2]?.data).toMatchObject({
request_id: "perm-auto-env",
permission: "read",
target: ".env",
});
});
it("approves every OpenCode ask once in always mode", async () => {
it("approves routine asks but forwards external paths in always mode", async () => {
const replies: Array<Record<string, unknown>> = [];
const runtime = {
subscribeEvents: async () =>
@@ -624,6 +656,28 @@ describe("streamPromptResponse", () => {
always: ["npm test"],
},
},
{
type: "permission.asked",
properties: {
id: "perm-always-external",
sessionID: "runtime-session-1",
permission: "external_directory",
patterns: ["/app/private/*"],
metadata: { filepath: "/app/private/config.json" },
always: ["/app/private/*"],
},
},
{
type: "permission.asked",
properties: {
id: "perm-always-env",
sessionID: "runtime-session-1",
permission: "read",
patterns: [".env"],
metadata: { filepath: ".env" },
always: [".env"],
},
},
{ type: "session.idle", properties: { sessionID: "runtime-session-1" } },
]),
prompt: async () => undefined,
@@ -650,10 +704,17 @@ describe("streamPromptResponse", () => {
reply: "once",
},
]);
expect(events.some((item) => item.event === "permission_request")).toBe(false);
expect(events.find((item) => item.event === "permission_request")?.data).toMatchObject({
request_id: "perm-always-external",
permission: "external_directory",
});
expect(events.filter((item) => item.event === "permission_request")[1]?.data).toMatchObject({
request_id: "perm-always-env",
permission: "read",
});
});
it("rejects recursive force removal even in always mode", async () => {
it("asks before recursive force removal even in always mode", async () => {
const replies: Array<Record<string, unknown>> = [];
const runtime = {
subscribeEvents: async () =>
@@ -687,19 +748,10 @@ describe("streamPromptResponse", () => {
write: (event, data) => events.push({ event, data }),
});
expect(replies).toEqual([
{
requestId: "perm-always-rm-rf",
sessionId: "runtime-session-1",
directory: "/tmp/conversation-workspace-1",
reply: "reject",
},
]);
expect(events.some((item) => item.event === "permission_request")).toBe(false);
expect(events.find((item) => item.event === "permission_response")?.data).toEqual({
session_id: "client-session-1",
expect(replies).toEqual([]);
expect(events.find((item) => item.event === "permission_request")?.data).toMatchObject({
request_id: "perm-always-rm-rf",
reply: "reject",
permission: "bash",
});
});
@@ -735,6 +787,7 @@ describe("streamPromptResponse", () => {
sessionId: "runtime-session-1",
clientSessionId: "client-session-1",
message: "read /tmp",
approvalMode: "auto",
write: (event, data) => events.push({ event, data }),
});