mirror of
https://github.com/morluto/rea
synced 2026-10-28 06:46:47 -04:00
55dd21a5e2
Preserve XML and Interface Builder identity at their parsing boundary, retain usable Objective-C slots with precise coverage, and keep native trace failures distinct from absence. Add caller-selected Interface Builder summaries backed by complete retained Evidence, and preserve comparison limitations. Reviewed fixes: #1969, #2088, #2108, #2513, #2639, #2684, #2731, #2759, #2875, #2888, #2889, #2970, #2971, #3002. Validation: relevant native/domain focused lane passed 142 tests; earlier Interface Builder/MCP checks passed 24 tests. Real native UI and Ghidra producer coverage was not repeated.
139 lines
4.8 KiB
TypeScript
139 lines
4.8 KiB
TypeScript
import { mkdir, symlink, writeFile } from "node:fs/promises";
|
|
import { dirname, join } from "node:path";
|
|
|
|
import { createPackageWithOptions } from "@electron/asar";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import { createTestTempDirectory } from "../../../fixtures/temporaryDirectory.js";
|
|
import { createDirectAnalysis } from "../../../../src/composition/directAnalysis.js";
|
|
import { artifactInspectionResultSchema } from "../../../../src/domain/artifactInspection.js";
|
|
import { parseEvidence } from "../../../../src/domain/evidence.js";
|
|
|
|
const { runProviderAnalysis } = createDirectAnalysis({});
|
|
|
|
// mach_header_64 for an arm64 MH_EXECUTE image with no load commands.
|
|
const MACH_O_64 = Buffer.alloc(32);
|
|
MACH_O_64.writeUInt32LE(0xfeedfacf, 0);
|
|
MACH_O_64.writeUInt32LE(0x0100000c, 4);
|
|
MACH_O_64.writeUInt32LE(2, 12);
|
|
const INFO_PLIST = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<plist version="1.0"><dict><key>CFBundleExecutable</key><string>Fixture</string></dict></plist>
|
|
`;
|
|
|
|
const writeTree = async (
|
|
root: string,
|
|
files: Readonly<Record<string, string | Buffer>>,
|
|
): Promise<void> => {
|
|
for (const [path, contents] of Object.entries(files)) {
|
|
await mkdir(dirname(join(root, path)), { recursive: true });
|
|
await writeFile(join(root, path), contents);
|
|
}
|
|
};
|
|
|
|
const probeOperations = async (path: string): Promise<string[]> => {
|
|
const evidence = parseEvidence(
|
|
await runProviderAnalysis(path, "inspect_artifact", {}),
|
|
);
|
|
const inspection = artifactInspectionResultSchema.parse(
|
|
evidence.normalized_result,
|
|
);
|
|
expect(inspection.subject.root_format).toBe("directory");
|
|
expect(
|
|
inspection.unexplored_branches.map(({ next_probe }) => next_probe),
|
|
).toEqual(inspection.next_probes);
|
|
return inspection.next_probes.map(({ operation }) => operation);
|
|
};
|
|
|
|
describe("directory inspection probes", () => {
|
|
it("proposes native and plist probes for an application bundle without JavaScript", async () => {
|
|
const root = await createTestTempDirectory("rea-artifact-bundle-");
|
|
const bundle = join(root, "Fixture.app");
|
|
await writeTree(bundle, {
|
|
"Contents/MacOS/Fixture": MACH_O_64,
|
|
"Contents/Info.plist": INFO_PLIST,
|
|
});
|
|
|
|
expect(await probeOperations(bundle)).toEqual([
|
|
"open_binary",
|
|
"inspect_plist",
|
|
]);
|
|
});
|
|
|
|
it("keeps JavaScript analysis first for an Electron-style bundle", async () => {
|
|
const root = await createTestTempDirectory("rea-artifact-electron-");
|
|
const source = join(root, "source");
|
|
const bundle = join(root, "Fixture.app");
|
|
await writeTree(source, { "main.js": "console.log('ok');\n" });
|
|
await writeTree(bundle, {
|
|
"Contents/MacOS/Fixture": MACH_O_64,
|
|
"Contents/Info.plist": INFO_PLIST,
|
|
});
|
|
await createPackageWithOptions(
|
|
source,
|
|
join(bundle, "Contents/Resources/app.asar"),
|
|
{},
|
|
);
|
|
|
|
expect(await probeOperations(bundle)).toEqual([
|
|
"analyze_javascript_application",
|
|
"open_binary",
|
|
"inspect_plist",
|
|
]);
|
|
});
|
|
|
|
it.each(["cjs", "cts", "mjs", "mts", "js", "jsx", "ts", "tsx"])(
|
|
"proposes JavaScript analysis for .%s sources the analyzer reads",
|
|
async (extension) => {
|
|
const root = await createTestTempDirectory("rea-artifact-typescript-");
|
|
const bundle = join(root, "Fixture.app");
|
|
await writeTree(bundle, {
|
|
"Contents/MacOS/Fixture": MACH_O_64,
|
|
"Contents/Info.plist": INFO_PLIST,
|
|
[`Contents/Resources/app/main.${extension}`]:
|
|
"export const ok = true;\n",
|
|
});
|
|
|
|
expect(await probeOperations(bundle)).toEqual([
|
|
"analyze_javascript_application",
|
|
"open_binary",
|
|
"inspect_plist",
|
|
]);
|
|
},
|
|
);
|
|
|
|
it("preserves plist inspection for a readable Info.plist symlink", async () => {
|
|
const root = await createTestTempDirectory("rea-artifact-plist-link-");
|
|
const bundle = join(root, "Fixture.app");
|
|
await writeTree(bundle, { "Contents/MacOS/Fixture": MACH_O_64 });
|
|
await writeFile(join(root, "bundle-metadata"), INFO_PLIST);
|
|
await symlink(
|
|
join(root, "bundle-metadata"),
|
|
join(bundle, "Contents/Info.plist"),
|
|
);
|
|
expect(await probeOperations(bundle)).toEqual([
|
|
"open_binary",
|
|
"inspect_plist",
|
|
]);
|
|
});
|
|
|
|
it("does not propose JavaScript analysis for a symlink the analyzers never follow", async () => {
|
|
const root = await createTestTempDirectory("rea-artifact-symlink-");
|
|
const bundle = join(root, "Fixture.app");
|
|
await writeTree(bundle, {
|
|
"Contents/MacOS/Fixture": MACH_O_64,
|
|
"Contents/Info.plist": INFO_PLIST,
|
|
});
|
|
await writeTree(root, { "outside.js": "console.log('outside');\n" });
|
|
await mkdir(join(bundle, "Contents/Resources"));
|
|
await symlink(
|
|
join(root, "outside.js"),
|
|
join(bundle, "Contents/Resources/main.js"),
|
|
);
|
|
|
|
expect(await probeOperations(bundle)).toEqual([
|
|
"open_binary",
|
|
"inspect_plist",
|
|
]);
|
|
});
|
|
});
|