mirror of
https://github.com/morluto/rea
synced 2026-10-28 06:46:47 -04:00
5fb6edc34b
Validate the canonical programs directory before resolving its executable, preserving containment failures even when the external child is missing. Retain in-bundle directory and bundle aliases. Co-authored-by: gongfpp <22683417+gongfpp@users.noreply.github.com>
78 lines
2.7 KiB
TypeScript
78 lines
2.7 KiB
TypeScript
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import { resolveAppBundleExecutable } from "./AppBundleExecutable.js";
|
|
|
|
describe("app bundle Info.plist magic", () => {
|
|
it("does not decode a high-bit fold of bplist as a binary Info.plist", async () => {
|
|
const root = await mkdtemp(join(tmpdir(), "rea-bplist-fold-"));
|
|
try {
|
|
const bundle = join(root, "Fixture.app");
|
|
const plistPath = join(bundle, "Contents", "Info.plist");
|
|
await mkdir(join(bundle, "Contents"), { recursive: true });
|
|
const plist = Buffer.from("bplist00");
|
|
plist[0] = (plist[0] ?? 0) | 0x80;
|
|
await writeFile(plistPath, plist);
|
|
let decoded = false;
|
|
const result = await resolveAppBundleExecutable(bundle, {
|
|
readFile: async () => plist,
|
|
realpath: async (path) => path,
|
|
decodeBinaryPlist: async () => {
|
|
decoded = true;
|
|
return "Fixture";
|
|
},
|
|
});
|
|
expect(decoded).toBe(false);
|
|
expect(result.ok).toBe(false);
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("app bundle program directory containment", () => {
|
|
it("rejects an escaping programs directory before resolving its child", async () => {
|
|
const root = await mkdtemp(join(tmpdir(), "rea-app-programs-"));
|
|
try {
|
|
const bundle = join(root, "Fixture.app");
|
|
const programs = join(bundle, "Contents", "MacOS");
|
|
const executable = join(programs, "Missing");
|
|
const canonicalBundle = join(root, "Canonical.app");
|
|
const outsidePrograms = join(root, "outside");
|
|
const plist =
|
|
"<plist><dict><key>CFBundleExecutable</key><string>Missing</string></dict></plist>";
|
|
await mkdir(join(bundle, "Contents"), { recursive: true });
|
|
await writeFile(join(bundle, "Contents", "Info.plist"), plist);
|
|
let childResolved = false;
|
|
|
|
const result = await resolveAppBundleExecutable(bundle, {
|
|
readFile: async () => Buffer.from(plist),
|
|
realpath: async (path) => {
|
|
if (path === bundle) return canonicalBundle;
|
|
if (path === programs) return outsidePrograms;
|
|
if (path === executable) {
|
|
childResolved = true;
|
|
throw Object.assign(new Error("missing executable"), {
|
|
code: "ENOENT",
|
|
});
|
|
}
|
|
return path;
|
|
},
|
|
});
|
|
|
|
expect(result).toMatchObject({
|
|
ok: false,
|
|
error: {
|
|
reason: "app program directory leaves Contents/MacOS",
|
|
},
|
|
});
|
|
expect(childResolved).toBe(false);
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|