Files
reverse-engineering-anything/tests/acceptance/applications/keyedArchiveScalarKinds.test.ts
T
morluto f2b7bbb312 fix(plist): retain known binary non-finite value identity
Make XML numeric uncertainty explicit at its producing boundary, separate
from decoded IEEE NaN and signed infinities. Shared projection preserves
known values using canonical numeric labels; unresolved XML numeric
values remain explicit unknowns. Remove the superseded known-value
wrapper and spelling alias, keeping numeric interpretation with its owner.

Cover public CLI/MCP binary archives, lookalike dictionaries, authored
32/64-bit IEEE payloads, signed zero, and XML parsing/overflow uncertainty.
Document canonical labels and unrepresented NaN payload/sign details.

Fixes #4919. Related: #4906, #4852.

Validation: baseline 2 failures/5 controls through public CLI/MCP; stable
final 439 passed across25files, check:fast, docs:check and discovery.
Native macOS plutil and existing codesign controls executed; no
Linux/Windows run or real Interface Builder engine qualification.
2026-10-18 07:55:51 +08:00

421 lines
14 KiB
TypeScript

import { createHash } from "node:crypto";
import { readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { build, buildBinary } from "plist";
import { expect } from "vitest";
import { keyedArchiveResultSchema } from "../../../src/domain/apple/keyedArchive.js";
import { parseEvidence } from "../../../src/domain/evidence.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { connectLocalToolsMcp } from "../../fixtures/localToolsMcp.js";
import { cliTest } from "../../support/cli/cliFixture.js";
cliTest.for(["xml", "binary"] as const)(
"distinguishes serialized scalar objects from lookalike dictionaries in a %s keyed archive",
async (format, { cli }) => {
const root = await createTestTempDirectory("rea-keyed-scalar-kinds-");
const archive = {
$archiver: "NSKeyedArchiver",
$version: 100000,
$top: { root: { CF$UID: 12 }, data: { CF$UID: 1 } },
$objects: [
"$null",
Buffer.from("deadbeef", "hex"),
new Date("2001-01-01T00:00:00.000Z"),
9007199254740992,
"hello",
42,
1.5,
Number.POSITIVE_INFINITY,
{ $plist_type: "data", base64: "3q2+7w==" },
{ $plist_type: "date", iso8601: "2001-01-01T00:00:00.000Z" },
{ $plist_type: "real", value: 0 },
[{ CF$UID: 1 }],
{ $class: { CF$UID: 13 }, payload: { CF$UID: 1 } },
{ $classname: "ScalarHolder", $classes: ["ScalarHolder", "NSObject"] },
],
};
const bytes = Buffer.from(
format === "binary" ? buildBinary(archive) : build(archive),
);
const path = join(root, "archive.plist");
await writeFile(path, bytes);
const digest = createHash("sha256").update(bytes).digest("hex");
const run = async (arguments_: readonly string[] = []) => {
const output = await cli.run({
arguments: ["inspect-keyed-archive", path, ...arguments_, "--json"],
environment: {
HOME: root,
USERPROFILE: root,
REA_LOG_LEVEL: "silent",
REA_ANALYSIS_PROVIDER: "auto",
},
});
expect(output.exitCode, output.stderr).toBe(0);
return keyedArchiveResultSchema.parse(
parseEvidence(output.json).normalized_result,
);
};
const result = await run();
const { call } = await connectLocalToolsMcp({
HOME: root,
USERPROFILE: root,
});
const opened = await call("open_binary", { path });
expect(opened.isError, JSON.stringify(opened)).not.toBe(true);
const mcp = await call("inspect_keyed_archive", {});
expect(mcp.isError, JSON.stringify(mcp)).not.toBe(true);
expect(
keyedArchiveResultSchema.parse(
parseEvidence(mcp.structuredContent).normalized_result,
),
).toEqual(result);
expect(result.archive_sha256).toBe(digest);
expect(result.archive_format).toBe(
format === "binary" ? "binary-plist" : "xml-plist",
);
expect(result.objects.map(({ kind }) => kind)).toEqual([
"nil",
"scalar",
"scalar",
"scalar",
"scalar",
"scalar",
"scalar",
"scalar",
"dictionary",
"dictionary",
"dictionary",
"array",
"archived-object",
"class-descriptor",
]);
for (const [scalar, dictionary] of [
[1, 8],
[2, 9],
] as const) {
expect(result.objects[scalar]?.value).toEqual(
result.objects[dictionary]?.value,
);
expect(result.objects[scalar]?.status).toBe("observed");
}
expect(result.objects[1]?.value).toEqual({
$plist_type: "data",
base64: "3q2+7w==",
});
expect(result.objects[2]?.value).toEqual({
$plist_type: "date",
iso8601: "2001-01-01T00:00:00.000Z",
});
// XML and binary decoding preserve the same non-finite value identity.
expect(result.objects[7]?.value).toEqual({
$plist_type: "real",
value: "+infinity",
});
expect(result.objects[10]?.value).toEqual({
$plist_type: "real",
value: 0,
});
expect(result.objects[12]).toMatchObject({
class_id: 13,
class_name: "ScalarHolder",
status: "unknown-class",
});
expect(result.references).toContainEqual(
expect.objectContaining({
source: 12,
path: ["payload"],
target: 1,
status: "resolved",
}),
);
expect(result.limitations).toContainEqual(
expect.stringContaining("1 non-finite real value(s)"),
);
const page = await run(["--root", "data", "--offset", "1", "--limit", "2"]);
expect(page.objects).toEqual(result.objects.slice(1, 3));
expect(page).toMatchObject({
archive_sha256: digest,
total_objects: 14,
offset: 1,
next_offset: 3,
truncated: true,
});
expect(Object.keys(page.roots)).toEqual(["data"]);
expect(page.references).toEqual([
expect.objectContaining({
source: null,
path: ["data"],
target: 1,
status: "resolved",
}),
]);
expect(await readFile(path)).toEqual(bytes);
},
);
cliTest.for(["xml", "binary"] as const)(
"keeps a negative-zero real distinct from positive zero in a %s keyed archive",
async (format, { cli }) => {
const root = await createTestTempDirectory("rea-keyed-signed-zero-");
const archive = {
$archiver: "NSKeyedArchiver",
$version: 100000,
$top: {
negative: { CF$UID: 1 },
positive: { CF$UID: 2 },
control: { CF$UID: 3 },
},
$objects: ["$null", 1.5, 0.25, 1.25],
};
// plist@5 writes -0 and 0 as integers, so encode placeholder reals and
// patch them to the zeros an archiver serializes.
let bytes: Buffer;
if (format === "xml") {
const xml = build(archive);
expect(xml).toContain("<real>1.5</real>");
expect(xml).toContain("<real>0.25</real>");
bytes = Buffer.from(
xml
.replace("<real>1.5</real>", "<real>-0.0</real>")
.replace("<real>0.25</real>", "<real>0.0</real>"),
);
} else {
bytes = Buffer.from(buildBinary(archive));
for (const [placeholder, zero] of [
[1.5, -0],
[0.25, 0],
] as const) {
const real = Buffer.alloc(9);
real[0] = 0x23;
real.writeDoubleBE(placeholder, 1);
const start = bytes.indexOf(real);
expect(start).toBeGreaterThanOrEqual(0);
bytes.writeDoubleBE(zero, start + 1);
}
}
const path = join(root, "archive.plist");
await writeFile(path, bytes);
const output = await cli.run({
arguments: ["inspect-keyed-archive", path, "--json"],
environment: {
HOME: root,
USERPROFILE: root,
REA_LOG_LEVEL: "silent",
REA_ANALYSIS_PROVIDER: "auto",
},
});
expect(output.exitCode, output.stderr).toBe(0);
const result = keyedArchiveResultSchema.parse(
parseEvidence(output.json).normalized_result,
);
const { call } = await connectLocalToolsMcp({
HOME: root,
USERPROFILE: root,
});
const opened = await call("open_binary", { path });
expect(opened.isError, JSON.stringify(opened)).not.toBe(true);
const mcp = await call("inspect_keyed_archive", {});
expect(mcp.isError, JSON.stringify(mcp)).not.toBe(true);
expect(
keyedArchiveResultSchema.parse(
parseEvidence(mcp.structuredContent).normalized_result,
),
).toEqual(result);
const [, negative, positive, control] = result.objects;
expect(negative).toMatchObject({
kind: "scalar",
status: "observed",
value: { $plist_type: "real", value: "-0" },
});
expect(positive).toMatchObject({ kind: "scalar", status: "observed" });
expect(Object.is(positive?.value, 0)).toBe(true);
expect(control?.value).toBe(1.25);
expect(JSON.stringify(result.objects.map(({ value }) => value))).toBe(
'["$null",{"$plist_type":"real","value":"-0"},0,1.25]',
);
expect(result.limitations).toContain(
'1 negative-zero real value(s) are reported as { "$plist_type": "real", "value": "-0" } because a JSON number cannot carry the sign of zero.',
);
expect(await readFile(path)).toEqual(bytes);
},
);
cliTest(
"rejects scalar top dictionaries through the public CLI and MCP",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-keyed-scalar-top-");
const { call } = await connectLocalToolsMcp({
HOME: root,
USERPROFILE: root,
});
for (const format of ["xml", "binary"]) {
const archive = {
$archiver: "NSKeyedArchiver",
$version: 100000,
$objects: ["$null"],
$top: new Uint8Array([0]),
};
const path = join(root, format + ".plist");
await writeFile(
path,
Buffer.from(format === "xml" ? build(archive) : buildBinary(archive)),
);
const output = await cli.run({
arguments: ["inspect-keyed-archive", path, "--json"],
environment: { HOME: root, USERPROFILE: root },
});
expect(output.exitCode).toBe(1);
expect(output.json).toMatchObject({
code: "artifact_operation_failed",
details: {
reason: "format",
detail: expect.stringContaining("Keyed archive has no $top roots"),
},
});
const opened = await call("open_binary", { path });
expect(opened.isError, JSON.stringify(opened)).not.toBe(true);
const reply = await call("inspect_keyed_archive", {});
expect(reply.isError).toBe(true);
expect(JSON.stringify(reply)).toContain(
"Keyed archive has no $top roots",
);
}
},
);
cliTest("preserves binary IEEE non-finite scalar identity", async ({ cli }) => {
const root = await createTestTempDirectory("rea-binary-nonfinite-");
const path = join(root, "archive.plist");
const bytes = Buffer.from(
buildBinary({
$archiver: "NSKeyedArchiver",
$version: 100000,
$top: {
positive: { CF$UID: 1 },
negative: { CF$UID: 2 },
nan: { CF$UID: 3 },
},
$objects: [
"$null",
Infinity,
-Infinity,
NaN,
{ $plist_type: "real", value: "nan" },
1.25,
],
}),
);
await writeFile(path, bytes);
const output = await cli.run({
arguments: ["inspect-keyed-archive", path, "--json"],
environment: { HOME: root, USERPROFILE: root, REA_LOG_LEVEL: "silent" },
});
expect(output.exitCode, output.stderr).toBe(0);
const result = keyedArchiveResultSchema.parse(
parseEvidence(output.json).normalized_result,
);
expect(
result.objects.slice(1).map(({ kind, value }) => ({ kind, value })),
).toEqual([
{ kind: "scalar", value: { $plist_type: "real", value: "+infinity" } },
{ kind: "scalar", value: { $plist_type: "real", value: "-infinity" } },
{ kind: "scalar", value: { $plist_type: "real", value: "nan" } },
{ kind: "dictionary", value: { $plist_type: "real", value: "nan" } },
{ kind: "scalar", value: 1.25 },
]);
expect(result.archive_sha256).toBe(
createHash("sha256").update(bytes).digest("hex"),
);
const { call } = await connectLocalToolsMcp({
HOME: root,
USERPROFILE: root,
});
const opened = await call("open_binary", { path });
expect(opened.isError).not.toBe(true);
const mcp = await call("inspect_keyed_archive", {});
expect(mcp.isError).not.toBe(true);
expect(
keyedArchiveResultSchema.parse(
parseEvidence(mcp.structuredContent).normalized_result,
),
).toEqual(result);
expect(await readFile(path)).toEqual(bytes);
});
cliTest(
"keeps CFPropertyList's non-finite real spellings in an XML keyed archive",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-keyed-non-finite-");
const archive = {
$archiver: "NSKeyedArchiver",
$version: 100000,
$top: {
up: { CF$UID: 1 },
down: { CF$UID: 2 },
missing: { CF$UID: 3 },
control: { CF$UID: 4 },
},
$objects: ["$null", 1.5, 0.25, 1.75, 1.25],
};
// plist@5 writes its own spellings for non-finite reals, so encode
// placeholder reals and patch them to the text CFPropertyList writes.
let xml = build(archive);
for (const [placeholder, spelling] of [
["1.5", "+infinity"],
["0.25", "-INFINITY"],
["1.75", "nan"],
] as const) {
expect(xml).toContain(`<real>${placeholder}</real>`);
xml = xml.replace(
`<real>${placeholder}</real>`,
`<real>${spelling}</real>`,
);
}
const bytes = Buffer.from(xml);
const path = join(root, "archive.plist");
await writeFile(path, bytes);
const output = await cli.run({
arguments: ["inspect-keyed-archive", path, "--json"],
environment: {
HOME: root,
USERPROFILE: root,
REA_LOG_LEVEL: "silent",
REA_ANALYSIS_PROVIDER: "auto",
},
});
expect(output.exitCode, output.stderr).toBe(0);
const result = keyedArchiveResultSchema.parse(
parseEvidence(output.json).normalized_result,
);
const { call } = await connectLocalToolsMcp({
HOME: root,
USERPROFILE: root,
});
const opened = await call("open_binary", { path });
expect(opened.isError, JSON.stringify(opened)).not.toBe(true);
const mcp = await call("inspect_keyed_archive", {});
expect(mcp.isError, JSON.stringify(mcp)).not.toBe(true);
expect(
keyedArchiveResultSchema.parse(
parseEvidence(mcp.structuredContent).normalized_result,
),
).toEqual(result);
for (const object of result.objects.slice(1)) {
expect(object).toMatchObject({ kind: "scalar", status: "observed" });
}
expect(JSON.stringify(result.objects.map(({ value }) => value))).toBe(
'["$null",{"$plist_type":"real","value":"+infinity"},{"$plist_type":"real","value":"-infinity"},{"$plist_type":"real","value":"nan"},1.25]',
);
expect(result.limitations).toContain(
'3 non-finite real value(s) are reported as { "$plist_type": "real", "value": "<spelling>" } using canonical numeric labels nan, +infinity or -infinity because a JSON number cannot carry them.',
);
expect(result.limitations).not.toContainEqual(
expect.stringContaining('"value": null'),
);
expect(await readFile(path)).toEqual(bytes);
},
);