fix(native): report plist symlink loops and overlong paths as selection failures (#6371)

Co-authored-by: Abelo9996 <Abelo9996@users.noreply.github.com>
This commit is contained in:
Abel YagubyanandAbelo9996 authored and GitHub committed 2026-10-23 23:41:29 +09:00
1 parent eab61d02a1
commit f290b27299
2 files changed
+65 -1

No files matched your search

+8
View File
@@ -260,6 +260,14 @@ export const nativePlistFailure = (
cause,
capturedOutput,
});
if (code === "ELOOP" || code === "ENAMETOOLONG")
return unreadablePlist(
selection,
code === "ELOOP"
? "symbolic-link traversal looped or exceeded the host limit (ELOOP)"
: "the path exceeds the host path or component length limit (ENAMETOOLONG)",
{ cause, capturedOutput },
);
if (code === "EACCES" || code === "EPERM")
return new BinaryTargetError(
path,
@@ -1,4 +1,4 @@
import { chmod, mkdir, writeFile } from "node:fs/promises";
import { chmod, mkdir, symlink, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect, it, onTestFinished } from "vitest";
import { NativeMacOSProvider } from "../../../../src/native/NativeMacOSProvider.js";
@@ -89,6 +89,62 @@ it.each(["missing", "directory"] as const)(
},
);
it.skipIf(process.platform === "win32")(
"reports selected and default plist path-resolution failures as selection failures",
async () => {
const root = await createTestTempDirectory("rea-plist-origin-resolve-");
const loop = join(root, "Loop.plist");
await symlink("Loop.plist", loop);
const long = join(root, `${"x".repeat(300)}.plist`);
const program = join(root, "program");
await writeFile(program, "fixture program");
await mkdir(join(root, "Contents"));
await symlink("Info.plist", join(root, "Contents/Info.plist"));
const failure = async (
target: Parameters<NativeMacOSProvider["createClient"]>[0],
) => {
const client = new NativeMacOSProvider(
{},
new NativeFixtureRunner(),
"darwin",
).createClient(target);
onTestFinished(async () => {
await client.close();
});
const result = await client.execute("inspect_plist", {});
if (result.ok) throw new Error("Expected plist refusal");
return projectAnalysisError(result.error);
};
for (const [path, code] of [
[loop, "ELOOP"],
[long, "ENAMETOOLONG"],
] as const)
expect(
await failure({
kind: "artifact",
format: "plist",
path,
sha256: "0".repeat(64),
}),
).toMatchObject({
code: "invalid_request",
details: {
issues: [
{
path: ["path"],
reason: "invalid_value",
message: expect.stringContaining(`(${code})`),
},
],
},
});
const fallback = await failure(nativeMachoTarget(program));
expect(fallback).toMatchObject({ code: "capability_unavailable" });
expect(fallback.message).toContain("no readable default plist");
expect(fallback.message).toContain("(ELOOP)");
},
);
it.skipIf(process.getuid?.() === 0 || process.platform === "win32")(
"keeps permission failure distinct from selection and format",
async () => {