mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 11:13:21 +00:00
* fix(artifacts): classify ELF and thin Mach-O only when their headers decode Inventory classified a file as elf or mach-o on its magic number alone, while target resolution requires a decodable ELF fixed header and a walkable Mach-O load-command table. A magic-only stub or a Mach-O with corrupt load commands was inventoried as an executable that open_binary then refused. Both routes now share readNativeHeaderFormat; such files are classified unknown. * test(applications): give mobile application fixtures decodable native headers Inventory now classifies ELF and thin Mach-O only when their headers decode, so the 8-byte magic stubs in the Android and Apple application fixtures no longer count as native code. Use the shared minimal header fixtures instead.
146 lines
5.8 KiB
TypeScript
146 lines
5.8 KiB
TypeScript
import { projectCliAnalysisResult } from "../../../src/cli/projectCliAnalysisResult.js";
|
|
import { writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import {
|
|
TextReader,
|
|
Uint8ArrayReader,
|
|
Uint8ArrayWriter,
|
|
ZipWriter,
|
|
} from "@zip.js/zip.js";
|
|
import { expect } from "vitest";
|
|
import { createDirectAnalysis } from "../../../src/composition/directAnalysis.js";
|
|
import { parseEvidence } from "../../../src/domain/evidence.js";
|
|
import { connectLocalToolsMcp } from "../../fixtures/localToolsMcp.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import {
|
|
minimalElfImage,
|
|
minimalMachoImage,
|
|
} from "../../fixtures/nativeHeaders.js";
|
|
import { cliTest } from "../../support/cli/cliFixture.js";
|
|
|
|
const android = {
|
|
extension: "apk",
|
|
operation: "project_android_application_graph",
|
|
command: "project-android-application-graph",
|
|
scripts: ["classes.dex", "extra.dex"],
|
|
nativeBytes: minimalElfImage(),
|
|
libraries: {
|
|
"lib/arm64-v8a/libcommunity.so": "jni-library-convention",
|
|
"lib/arm64-v8a/libpreact.so": "jni-library-convention",
|
|
"lib/arm64-v8a/libreactivity.so": "jni-library-convention",
|
|
"lib/arm64-v8a/libhermesfake.so": "jni-library-convention",
|
|
"lib/arm64-v8a/libflutter.so.backup.so": "jni-library-convention",
|
|
},
|
|
positives: {
|
|
"lib/arm64-v8a/libreactnativejni.so": "react-native-convention",
|
|
"lib/arm64-v8a/libhermes.so": "react-native-convention",
|
|
"lib/arm64-v8a/libflutter.so": "flutter-convention",
|
|
"lib/arm64-v8a/libunity.so": "unity-convention",
|
|
},
|
|
};
|
|
const apple = {
|
|
extension: "ipa",
|
|
operation: "project_apple_application_graph",
|
|
command: "project-apple-application-graph",
|
|
scripts: ["Payload/Fixture.app/main.js", "Payload/Fixture.app/extra.js"],
|
|
nativeBytes: minimalMachoImage(),
|
|
libraries: {
|
|
"Payload/Fixture.app/Frameworks/App.framework/App":
|
|
"javascript-and-native-content",
|
|
"Payload/Fixture.app/Frameworks/MyApp.framework/MyApp":
|
|
"javascript-and-native-content",
|
|
"Payload/Fixture.app/Frameworks/Community.framework/Community":
|
|
"javascript-and-native-content",
|
|
"Payload/Fixture.app/Frameworks/Preact.framework/Preact":
|
|
"javascript-and-native-content",
|
|
"Payload/Fixture.app/Frameworks/Reactivity.framework/Reactivity":
|
|
"javascript-and-native-content",
|
|
"Payload/Fixture.app/Frameworks/HermesExtra.framework/HermesExtra":
|
|
"javascript-and-native-content",
|
|
"Payload/Fixture.app/Frameworks/Flutter.framework.backup/Flutter":
|
|
"javascript-and-native-content",
|
|
"Payload/Fixture.app/Frameworks/plain.dylib":
|
|
"javascript-and-native-content",
|
|
},
|
|
positives: {
|
|
"Payload/Fixture.app/Frameworks/React.framework/React":
|
|
"react-native-convention",
|
|
"Payload/Fixture.app/Frameworks/hermes.framework/hermes":
|
|
"react-native-convention",
|
|
"Payload/Fixture.app/Frameworks/Flutter.framework/Flutter":
|
|
"flutter-convention",
|
|
"Payload/Fixture.app/Frameworks/UnityFramework.framework/UnityFramework":
|
|
"unity-convention",
|
|
},
|
|
};
|
|
|
|
cliTest(
|
|
"bounds mobile conventions and preserves inference provenance through compiled CLI and real MCP",
|
|
async ({ cli }) => {
|
|
const root = await createTestTempDirectory("rea-mobile-conventions-");
|
|
const { call } = await connectLocalToolsMcp();
|
|
const { runProviderAnalysis } = createDirectAnalysis({});
|
|
for (const fixture of [android, apple]) {
|
|
for (const positive of [false, true]) {
|
|
const libraries = positive ? fixture.positives : fixture.libraries;
|
|
const path = join(
|
|
root,
|
|
`${positive ? "positive" : "negative"}.${fixture.extension}`,
|
|
);
|
|
const writer = new ZipWriter(new Uint8ArrayWriter());
|
|
for (const script of fixture.scripts)
|
|
await writer.add(script, new TextReader("export const value = 1;"));
|
|
for (const name of Object.keys(libraries))
|
|
await writer.add(name, new Uint8ArrayReader(fixture.nativeBytes));
|
|
await writeFile(path, await writer.close());
|
|
const inventory = parseEvidence(
|
|
await runProviderAnalysis(path, "inventory_artifact", {}).then(
|
|
projectCliAnalysisResult,
|
|
),
|
|
);
|
|
const input = { inventory_evidence: [inventory] };
|
|
const result = await cli.run({
|
|
arguments: [fixture.command, JSON.stringify(input), "--json"],
|
|
});
|
|
expect(result.exitCode, result.stderr).toBe(0);
|
|
const evidence = parseEvidence(result.json);
|
|
const response = await call(fixture.operation, input);
|
|
expect(response.isError).not.toBe(true);
|
|
const mcp = parseEvidence(response.structuredContent);
|
|
expect(mcp).toEqual(evidence);
|
|
expect(evidence.normalized_result).toMatchObject({
|
|
runtime_families: positive
|
|
? fixture.extension === "apk"
|
|
? ["dalvik-art", "flutter", "native", "react-native", "unity"]
|
|
: ["flutter", "javascript", "native", "react-native", "unity"]
|
|
: fixture.extension === "apk"
|
|
? ["dalvik-art", "native"]
|
|
: ["javascript", "native"],
|
|
coverage: {
|
|
status: "complete-within-inventory",
|
|
inventory_complete: true,
|
|
},
|
|
limitations: expect.arrayContaining([
|
|
expect.stringMatching(
|
|
/bridge basis.*inferred.*native path.*every managed component/iu,
|
|
),
|
|
]),
|
|
bridge_candidates: expect.arrayContaining(
|
|
Object.entries(libraries).flatMap(([native_path, basis]) =>
|
|
fixture.scripts.map((script) =>
|
|
expect.objectContaining({
|
|
native_path,
|
|
basis,
|
|
...(fixture.extension === "apk"
|
|
? { managed_path: script }
|
|
: { source_path: script }),
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
});
|
|
}
|
|
}
|
|
},
|
|
);
|