fix(codex): compact home permission paths

This commit is contained in:
Peter Steinberger
2026-04-25 02:21:45 +01:00
parent 0d3a5c3101
commit f86f8400f5
2 changed files with 14 additions and 20 deletions

View File

@@ -554,9 +554,9 @@ describe("Codex app-server approval bridge", () => {
const [, , requestPayload] = mockCallGatewayTool.mock.calls[0] ?? [];
const description = (requestPayload as { description: string }).description;
expect(description).toContain("Network allowHosts: example.com, *.internal");
expect(description).toContain("File system roots: /; writePaths: /home/simone");
expect(description).toContain("File system roots: /; writePaths: ~");
expect(description).toContain(
"High-risk targets: wildcard hosts, private-network wildcards, filesystem root, home directory",
"High-risk targets: wildcard hosts, private-network wildcards, filesystem root",
);
expect(requestPayload).toEqual(
expect.objectContaining({
@@ -565,7 +565,7 @@ describe("Codex app-server approval bridge", () => {
);
});
it("keeps permission detail bounded with truncated target samples", async () => {
it("keeps permission detail bounded with truncated and compacted target samples", async () => {
const params = createParams();
mockCallGatewayTool
.mockResolvedValueOnce({ id: "plugin:approval-4", status: "accepted" })
@@ -608,13 +608,14 @@ describe("Codex app-server approval bridge", () => {
expect(description.length).toBeLessThanOrEqual(700);
expect(description).toContain("example.com");
expect(description).not.toContain("secret-token");
expect(description).not.toContain("simone");
expect(description).toContain("*.internal");
expect(description).toContain("/workspace/project");
expect(description).toContain("High-risk targets:");
expect(description).toContain("readPaths: /Users/simone/.ssh/id_rsa");
expect(description).toContain("readPaths: ~/.ssh/id_rsa, /etc/hosts");
});
it("preserves Windows home paths in permission descriptions", async () => {
it("compacts Windows home paths in permission descriptions", async () => {
const params = createParams();
mockCallGatewayTool
.mockResolvedValueOnce({ id: "plugin:approval-windows-home", status: "accepted" })
@@ -640,10 +641,8 @@ describe("Codex app-server approval bridge", () => {
const [, , requestPayload] = mockCallGatewayTool.mock.calls[0] ?? [];
const description = (requestPayload as { description: string }).description;
expect(description).toContain(
"File system roots: C:/Users/alice; readPaths: C:\\Users\\alice\\.ssh\\id_rsa, c:/users/bob/project",
);
expect(description).toContain("High-risk targets: home directory");
expect(description).toContain("File system roots: ~; readPaths: ~\\.ssh\\id_rsa, ~/project");
expect(description).not.toContain("High-risk targets");
});
it("strips terminal and invisible controls from permission descriptions", async () => {

View File

@@ -431,7 +431,12 @@ function sanitizePermissionHostValue(value: string): string {
}
function sanitizePermissionPathValue(value: string): string {
return truncate(sanitizePermissionScalar(value), PERMISSION_VALUE_MAX_LENGTH);
const normalized = sanitizePermissionScalar(value);
const homeCompacted = normalized
.replace(/^\/home\/(?!\.{1,2}(?=\/|$))[^/]+(?=\/|$)/, "~")
.replace(/^\/Users\/(?!\.{1,2}(?=\/|$))[^/]+(?=\/|$)/, "~")
.replace(/^[A-Za-z]:[\\/]Users[\\/](?!\.{1,2}(?=[\\/]|$))[^\\/]+(?=[\\/]|$)/i, "~");
return truncate(homeCompacted, PERMISSION_VALUE_MAX_LENGTH);
}
function sanitizePermissionScalar(value: string): string {
@@ -456,16 +461,6 @@ function permissionPathRisks(value: string): string[] {
if (normalized === "/" || normalized === "\\" || /^[A-Za-z]:[\\/]*$/.test(normalized)) {
risks.push("filesystem root");
}
if (
normalized === "~" ||
normalized === "~/" ||
normalized === "~\\" ||
/^\/home\/[^/]+\/?$/.test(normalized) ||
/^\/Users\/[^/]+\/?$/.test(normalized) ||
/^[A-Za-z]:[\\/]Users[\\/][^\\/]+[\\/]?$/i.test(normalized)
) {
risks.push("home directory");
}
return risks;
}