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
+1 -1
View File
@@ -74,7 +74,7 @@ const payloadSchema = z.object({
.optional()
.default("request")
.describe(
"request forwards approval prompts; auto approves only low-risk allowlisted tools; always approves every prompt not explicitly denied by OpenCode",
"request forwards approval prompts; auto approves only low-risk allowlisted tools and forwards other requests; always approves routine prompts but forwards sensitive paths and force removal for user approval",
),
});
+37 -7
View File
@@ -47,7 +47,7 @@ export const canAutoApprovePermission = (
}
if (normalized === "bash") {
return isConversationWorkspace(context.workspaceRoot);
return isConversationWorkspace(context.workspaceRoot) && !isSensitiveBashCommand(context);
}
if (normalized === "read" || normalized === "edit" || normalized === "write") {
@@ -70,12 +70,20 @@ export const resolvePermissionApproval = (
permission: string,
context: PermissionApprovalContext = {},
) => {
if (isDirectRecursiveForceRemove(permission, context)) {
const normalized = normalizePermission(permission);
if (
isSensitiveBashCommand(context, normalized) ||
(approvalMode === "always" &&
(normalized === "external_directory" ||
normalized === "read" ||
((normalized === "edit" || normalized === "write") &&
!isSafeConversationFileAccess(context))))
) {
return {
autoApprove: false,
autoReject: true,
title: "已拒绝递归强制删除",
detail: "当前安全策略禁止直接执行带 recursive 和 force 参数的 rm 命令。",
autoReject: false,
title: "等待权限确认",
detail: undefined,
} as const;
}
@@ -85,7 +93,7 @@ export const resolvePermissionApproval = (
autoReject: false,
title: "已按始终允许模式放行",
detail:
"当前会话处于始终允许模式,已放行本次权限请求;明确禁止的权限仍由 OpenCode 拒绝。",
"当前会话处于始终允许模式,已放行本次常规权限请求。",
} as const;
}
@@ -106,6 +114,21 @@ export const resolvePermissionApproval = (
} as const;
};
const isSensitiveBashCommand = (
context: PermissionApprovalContext,
normalizedPermission = "bash",
): boolean => {
if (normalizedPermission !== "bash") return false;
const command =
typeof context.metadata?.command === "string"
? context.metadata.command
: context.patterns?.join("\n");
return Boolean(
(command && containsProtectedPath(command, true)) ||
isDirectRecursiveForceRemove("bash", context),
);
};
const isDirectRecursiveForceRemove = (
permission: string,
context: PermissionApprovalContext,
@@ -412,9 +435,16 @@ const isSafeConversationFileAccess = (
relativeTarget !== ".." &&
!relativeTarget.startsWith(`..${sep}`) &&
!isAbsolute(relativeTarget) &&
!containsProtectedPath(relativeTarget, true)
!containsSensitiveFileSegment(relativeTarget)
);
} catch {
return false;
}
};
const containsSensitiveFileSegment = (value: string): boolean =>
value
.replaceAll("\\", "/")
.toLowerCase()
.split("/")
.some((segment) => segment.includes(".env") || segment === "data" || segment === "logs");