Files
rea/tests/boundary/providers/native/nativeSignatureTargetBinding.test.ts
T
ShuaixinHuang f6e4e0296f fix(native): remove completed signing capture limitation (#5548)
* fix(native): remove completed signing capture limitation

* fix(cli): keep the WASM command timeout optional

* fix(cli): preserve WASM timeout help metadata

* test(pe): reuse compiled CLI startup for resource acceptance

* fix(cli): use upstream exact-optional timeout semantics
2026-10-22 03:50:31 +08:00

696 lines
24 KiB
TypeScript

import { execFile } from "node:child_process";
import { promisify } from "node:util";
import {
chmod,
copyFile,
lstat,
mkdir,
readFile,
rename,
rm,
symlink,
utimes,
writeFile,
} from "node:fs/promises";
import { dirname, join } from "node:path";
import { describe, expect, it, onTestFinished } from "vitest";
import { signatureInspectionFixture } from "../../../fixtures/nativeCommands.js";
import { resolvedAnalysisError } from "../../../../src/domain/analysisErrorBase.js";
import { NativeMacOSProvider } from "../../../../src/native/NativeMacOSProvider.js";
import { ArtifactResourceScope } from "../../../../src/artifacts/ArtifactResourceScope.js";
import {
withSignatureTarget,
verifySignatureTarget,
} from "../../../../src/native/SignatureTargetBinding.js";
import {
NativeCommandFailure,
XcrunCommandRunner,
type NativeCommandRunner,
} from "../../../../src/native/CommandRunner.js";
import { inspectSignatureSchema } from "../../../../src/domain/native/nativeInspection.js";
import { err, ok } from "../../../../src/domain/result.js";
import { projectAnalysisError } from "../../../../src/domain/analysisErrorProjection.js";
import {
NativeFixtureRunner,
nativeMachoTargetForFile,
} from "../../../fixtures/nativeCommands.js";
import { createTestTempDirectory } from "../../../fixtures/temporaryDirectory.js";
const CONTENT = "registered executable bytes";
const runFile = promisify(execFile);
const fixture = async () => {
const directory = await createTestTempDirectory("rea-signature-binding-");
const path = join(directory, "program");
await writeFile(path, CONTENT);
return {
directory,
path,
target: await nativeMachoTargetForFile(path),
};
};
class Captures extends NativeFixtureRunner {
calls = 0;
constructor(
private readonly onCapture?: (args: readonly string[]) => Promise<void>,
) {
super();
}
override async run(tool: string, args: readonly string[]) {
this.calls += 1;
await this.onCapture?.(args);
return super.run(tool, args);
}
}
describe("native signature target identity", () => {
it("inspects an unchanged registered executable", async () => {
const { target } = await fixture();
const result = await new NativeMacOSProvider({}, new Captures(), "darwin")
.createClient(target)
.execute("inspect_signature", {});
if (!result.ok) throw result.error;
expect(result.value.result).toMatchObject({
signed: true,
identifier: "com.example.fixture",
designated_requirement: "identifier com.example.fixture",
limitations: [
"Inspected a digest-verified Mach-O byte snapshot; filesystem-bound signing context and trust validation are not examined.",
],
});
});
it("projects typed plist scalars from a controlled entitlement response", async () => {
const { path, target } = await fixture();
const replay = new NativeFixtureRunner();
const xml = `<?xml version="1.0"?><plist><dict>
<key>positive</key><real>+infinity</real>
<key>negative</key><real>-inf</real>
<key>nan</key><real>nan</real>
<key>integer</key><integer>9007199254740993</integer>
<key>date</key><date>2001-01-01T00:00:00Z</date>
<key>data</key><data>AQID</data><key>control</key><true/>
</dict></plist>`;
const runner: NativeCommandRunner = {
run(tool, args) {
return args.includes("--entitlements")
? new NativeFixtureRunner({ codesign: xml }).run(tool, args)
: replay.run(tool, args);
},
};
const result = await new NativeMacOSProvider({}, runner, "darwin")
.createClient(target)
.execute("inspect_signature", {});
if (!result.ok) throw result.error;
const signature = inspectSignatureSchema.parse(result.value.result);
expect(signature.entitlements).toEqual({
positive: { $plist_type: "real", value: "+infinity" },
negative: { $plist_type: "real", value: "-infinity" },
nan: { $plist_type: "real", value: "nan" },
integer: { $plist_type: "integer", decimal: "9007199254740993" },
date: { $plist_type: "date", iso8601: "2001-01-01T00:00:00.000Z" },
data: { $plist_type: "data", base64: "AQID" },
control: true,
});
expect(signature.limitations).toEqual(
expect.arrayContaining([
expect.stringContaining("Entitlements: 3 non-finite real value(s)"),
expect.stringContaining("unambiguous integer value(s)"),
]),
);
expect(await readFile(path, "utf8")).toBe(CONTENT);
});
it.each(["changed", "missing", "directory", "symlink"] as const)(
"rejects a %s target before commands run",
async (replacement) => {
const { path, target } = await fixture();
if (replacement === "changed") await writeFile(path, "replacement bytes");
else {
await rm(path);
if (replacement === "directory") await mkdir(path);
if (replacement === "symlink") {
await symlink(join(path, "missing"), path);
}
}
const runner = new Captures();
const result = await new NativeMacOSProvider({}, runner, "darwin")
.createClient(target)
.execute("inspect_signature", {});
if (result.ok) throw new Error("Expected target-change failure");
expect(projectAnalysisError(result.error)).toMatchObject({
code: "artifact_changed",
details: { path },
});
expect(runner.calls).toBe(0);
},
);
it.each(["replace", "remove", "rewrite"] as const)(
"rejects %s after capturing signature metadata",
async (change) => {
const { path, target } = await fixture();
const runner = new Captures(async (args) => {
if (!args.includes("--entitlements")) return;
if (change === "remove") await rm(path);
else if (change === "rewrite") {
const before = await lstat(path);
await writeFile(path, CONTENT);
// Identical writes may share a filesystem clock tick. Make the
// metadata change observed by the target guard explicit.
await utimes(path, before.atimeMs / 1000, before.mtimeMs / 1000 + 1);
} else {
const replacement = `${path}.replacement`;
await writeFile(replacement, CONTENT);
await rename(replacement, path);
}
});
const result = await new NativeMacOSProvider({}, runner, "darwin")
.createClient(target)
.execute("inspect_signature", {});
if (result.ok) throw new Error("Expected target-change failure");
expect(projectAnalysisError(result.error)).toMatchObject({
code: "artifact_changed",
details: { path },
});
},
);
it.skipIf(process.getuid?.() === 0)(
"preserves read permission denial before commands",
async () => {
const { path, target } = await fixture();
await chmod(path, 0);
const runner = new Captures();
try {
const result = await new NativeMacOSProvider({}, runner, "darwin")
.createClient(target)
.execute("inspect_signature", {});
if (result.ok) throw new Error("Expected access denial");
expect(projectAnalysisError(result.error)).toMatchObject({
code: "access_denied",
details: { path },
});
expect(runner.calls).toBe(0);
} finally {
await chmod(path, 0o600);
}
},
);
it.skipIf(process.getuid?.() === 0)(
"preserves permission denial during final version lookup",
async () => {
const { directory, path, target } = await fixture();
const runner = new Captures(async (args) => {
if (args.includes("--entitlements")) await chmod(directory, 0);
});
try {
const result = await new NativeMacOSProvider({}, runner, "darwin")
.createClient(target)
.execute("inspect_signature", {});
if (result.ok) throw new Error("Expected access denial");
expect(projectAnalysisError(result.error)).toMatchObject({
code: "access_denied",
details: { path },
});
} finally {
await chmod(directory, 0o700);
}
},
);
});
it.each([
["display", "result"],
["requirements", "result"],
["entitlements", "result"],
["slice", "result"],
["display", "throw"],
["requirements", "diagnostic"],
] as const)(
"reports a removed target before the %s capture's %s failure can hide it",
async (stage, failure) => {
const { path, target } = await fixture();
const replay = new NativeFixtureRunner();
const unsigned: NativeCommandRunner = {
async run(tool, args, options) {
const diagnostic = `${args.at(-1)}: code object is not signed at all\n`;
const capture = await new NativeFixtureRunner(
{ codesign: diagnostic },
1,
).run(tool, args);
if (!capture.ok) return capture;
return ok({
...capture.value,
stdout: "",
stderr: diagnostic,
stdoutBytes: 0,
stderrBytes: Buffer.byteLength(diagnostic),
});
},
};
let failed = false;
const runner: NativeCommandRunner = {
async run(tool, args) {
const slice = args.includes("-a");
const selected =
stage === "slice"
? slice
: stage === "requirements"
? args.includes("-r-")
: stage === "entitlements"
? args.includes("--entitlements")
: !args.includes("-r-") &&
!args.includes("--entitlements") &&
!slice;
if (selected) {
failed = true;
await rm(path);
if (failure === "throw")
throw new Error("codesign target disappeared");
if (failure === "diagnostic")
return new NativeFixtureRunner(
{ codesign: "invalid or unsupported format\n" },
1,
).run(tool, args);
return err(new NativeCommandFailure(tool, "nonzero-exit", 1));
}
if (stage === "slice" && args.includes("-r-"))
return unsigned.run(tool, args, {});
return replay.run(tool, args);
},
};
const result = await new NativeMacOSProvider({}, runner, "darwin")
.createClient(target)
.execute("inspect_signature", {});
expect(failed).toBe(true);
expect(result).toMatchObject({
ok: false,
error: { _tag: "AnalysisArtifactChangedError" },
});
},
);
it("rejects an intermediate replacement before a later capture can restore the original inode", async () => {
const { path, target } = await fixture();
const original = `${path}.original`;
const runner = new Captures(async (args) => {
if (args.includes("-r-")) {
await rename(path, original);
await writeFile(path, "different executable");
} else if (args.includes("--entitlements")) {
await rm(path);
await rename(original, path);
}
});
const result = await new NativeMacOSProvider({}, runner, "darwin")
.createClient(target)
.execute("inspect_signature", {});
expect(result).toMatchObject({
ok: false,
error: { _tag: "AnalysisArtifactChangedError" },
});
});
it("returns typed cancellation during target acquisition before commands run", async () => {
const { path } = await fixture();
await writeFile(path, Buffer.alloc(4 * 1024 * 1024, 42));
const target = await nativeMachoTargetForFile(path);
const controller = new AbortController();
const runner = new Captures();
const pending = new NativeMacOSProvider({}, runner, "darwin")
.createClient(target)
.execute("inspect_signature", {}, { signal: controller.signal });
controller.abort();
const result = await pending;
expect(result).toMatchObject({
ok: false,
error: { _tag: "AnalysisCancelledError" },
});
expect(!result.ok && projectAnalysisError(result.error).code).toBe(
"cancelled",
);
expect(runner.calls).toBe(0);
});
it("returns typed cancellation at binding entry and final verification", async () => {
const { target } = await fixture();
const signal = AbortSignal.abort();
await expect(
withSignatureTarget(
target,
new ArtifactResourceScope(),
async () => {
throw new Error("Cancelled acquisition must not invoke inspection");
},
signal,
),
).resolves.toMatchObject({
ok: false,
error: { _tag: "AnalysisCancelledError" },
});
await withSignatureTarget(
target,
new ArtifactResourceScope(),
async (binding) => {
const result = await verifySignatureTarget(target, binding, signal);
expect(result).toMatchObject({
ok: false,
error: { _tag: "AnalysisCancelledError" },
});
return result.ok ? ok(signatureInspectionFixture()) : result;
},
);
});
it("preserves cancellation after a capture instead of classifying it as invalid output", async () => {
const { target } = await fixture();
const controller = new AbortController();
const runner = new Captures(async () => {
controller.abort();
});
const result = await new NativeMacOSProvider({}, runner, "darwin")
.createClient(target)
.execute("inspect_signature", {}, { signal: controller.signal });
expect(result).toMatchObject({
ok: false,
error: { _tag: "AnalysisCancelledError" },
});
});
it("does not classify an unrelated path-echo diagnostic as an unsigned observation", async () => {
const directory = await createTestTempDirectory(
"rea-signature-path-diagnostic-",
);
const path = join(directory, "source\ncode object is not signed\noperand");
await writeFile(path, CONTENT);
const target = await nativeMachoTargetForFile(path);
let snapshotPath: string | undefined;
const runner: NativeCommandRunner = {
async run(tool, args) {
snapshotPath = args.at(-1);
if (snapshotPath === undefined)
throw new Error("Expected the bound snapshot as the codesign operand");
expect(await readFile(snapshotPath)).toEqual(Buffer.from(CONTENT));
return new NativeFixtureRunner(
{ codesign: `${snapshotPath}: No such file or directory\n` },
1,
).run(tool, args);
},
};
const result = await new NativeMacOSProvider({}, runner, "darwin")
.createClient(target)
.execute("inspect_signature", {});
expect(result).toMatchObject({
ok: false,
error: { _tag: "ProviderAdapterError" },
});
expect(await readFile(path, "utf8")).toBe(CONTENT);
if (snapshotPath === undefined) throw new Error("Expected codesign capture");
await expect(lstat(dirname(snapshotPath))).rejects.toMatchObject({
code: "ENOENT",
});
});
it.skipIf(process.platform !== "darwin")(
"rejects a path-injected unsigned diagnostic when real codesign sees a removed private snapshot",
async () => {
const directory = await createTestTempDirectory(
"rea-signature-snapshot-path-",
);
const path = join(directory, "source\ncode object is not signed\noperand");
await copyFile("/usr/bin/true", path);
const target = await nativeMachoTargetForFile(path);
const originalBytes = await readFile(path);
const real = new XcrunCommandRunner({});
let snapshotPath: string | undefined;
const runner: NativeCommandRunner = {
async run(tool, args, options) {
snapshotPath = args.at(-1);
if (snapshotPath === undefined)
throw new Error(
"Expected the bound snapshot as the codesign operand",
);
expect(snapshotPath).not.toBe(path);
expect(snapshotPath).toContain("\ncode object is not signed\n");
await rm(snapshotPath);
return real.run(tool, args, options);
},
};
const result = await new NativeMacOSProvider({}, runner, "darwin")
.createClient(target)
.execute("inspect_signature", {});
expect(result).toMatchObject({
ok: false,
error: { _tag: "AnalysisArtifactChangedError" },
});
expect(await readFile(path)).toEqual(originalBytes);
if (snapshotPath === undefined)
throw new Error("Expected codesign capture");
await expect(lstat(dirname(snapshotPath))).rejects.toMatchObject({
code: "ENOENT",
});
},
);
it.skipIf(process.platform !== "darwin")(
"accepts the exact unsigned diagnostic for a caller path containing that phrase",
async () => {
const directory = await createTestTempDirectory(
"rea-signature-real-unsigned-",
);
const path = join(directory, "source\ncode object is not signed\noperand");
await copyFile("/usr/bin/true", path);
await runFile("/usr/bin/codesign", ["--remove-signature", path]);
const target = await nativeMachoTargetForFile(path);
const result = await new NativeMacOSProvider(
{},
new XcrunCommandRunner({}),
"darwin",
)
.createClient(target)
.execute("inspect_signature", {});
if (!result.ok) throw result.error;
const parsed = inspectSignatureSchema.parse(result.value.result);
expect(parsed).toMatchObject({ signed: false });
expect(parsed.provenance).toHaveLength(3);
expect(parsed.provenance.every(({ exit }) => exit.code === 1)).toBe(true);
},
);
it.each(["success", "failure", "cancellation", "throw"] as const)(
"releases the verified snapshot after %s",
async (outcome) => {
const { target } = await fixture();
const controller = new AbortController();
let snapshotPath: string | undefined;
const runner: NativeCommandRunner = {
async run(tool, args) {
snapshotPath = args.at(-1);
if (snapshotPath === undefined)
throw new Error("Missing command target");
expect(snapshotPath).not.toBe(target.path);
expect(await readFile(snapshotPath, "utf8")).toBe(CONTENT);
if (outcome === "throw") throw new Error("Unexpected native failure");
if (outcome === "failure")
return err(new NativeCommandFailure(tool, "io"));
if (outcome === "cancellation") controller.abort();
return new NativeFixtureRunner().run(tool, args);
},
};
const result = await new NativeMacOSProvider({}, runner, "darwin")
.createClient(target)
.execute("inspect_signature", {}, { signal: controller.signal });
expect(result.ok).toBe(outcome === "success");
if (snapshotPath === undefined)
throw new Error("Expected snapshot capture");
await expect(lstat(dirname(snapshotPath))).rejects.toMatchObject({
code: "ENOENT",
});
},
);
it("copies and hashes the same bytes across multiple snapshot chunks", async () => {
const { path } = await fixture();
const bytes = Buffer.alloc(128 * 1024 + 13, 42);
await writeFile(path, bytes);
const target = await nativeMachoTargetForFile(path);
await withSignatureTarget(
target,
new ArtifactResourceScope(),
async (binding) => {
expect(await readFile(binding.snapshotPath)).toEqual(bytes);
const verified = await verifySignatureTarget(target, binding);
return verified.ok ? ok(signatureInspectionFixture()) : verified;
},
);
});
it.skipIf(process.platform === "win32")(
"rejects a changed snapshot identity while the registered source remains unchanged",
async () => {
const { path, target } = await fixture();
const result = await withSignatureTarget(
target,
new ArtifactResourceScope(),
async (binding) => {
await chmod(binding.snapshotPath, 0o600);
await writeFile(binding.snapshotPath, "replacement snapshot bytes");
const verified = await verifySignatureTarget(target, binding);
return verified.ok ? ok(signatureInspectionFixture()) : verified;
},
);
expect(result).toMatchObject({
ok: false,
error: { _tag: "AnalysisArtifactChangedError" },
});
expect(await readFile(path, "utf8")).toBe(CONTENT);
},
);
it("cleans the private snapshot when inspection throws", async () => {
const { target } = await fixture();
let snapshotPath: string | undefined;
const result = await withSignatureTarget(
target,
new ArtifactResourceScope(),
async (binding) => {
snapshotPath = binding.snapshotPath;
throw new Error("Invalid parsed output");
},
);
expect(result).toMatchObject({
ok: false,
error: { _tag: "AnalysisOutputError" },
});
if (snapshotPath === undefined) throw new Error("Expected snapshot");
await expect(lstat(dirname(snapshotPath))).rejects.toMatchObject({
code: "ENOENT",
});
});
it.skipIf(process.platform !== "darwin")(
"keeps real codesign bound to registered bytes during an ancestor directory swap",
async () => {
const root = await createTestTempDirectory("rea-signature-swap-");
const selected = join(root, "selected");
const replacement = join(root, "replacement");
const saved = join(root, "saved");
await mkdir(selected);
await mkdir(replacement);
const path = join(selected, "program");
const replacementPath = join(replacement, "program");
const run = promisify(execFile);
for (const [program, identifier] of [
[path, "rea.original"],
[replacementPath, "rea.replacement"],
] as const) {
await copyFile("/usr/bin/true", program);
await run("/usr/bin/codesign", [
"-s",
"-",
"-f",
"--identifier",
identifier,
program,
]);
}
const target = await nativeMachoTargetForFile(path);
const real = new XcrunCommandRunner({});
let capturedPath: string | undefined;
const runner: NativeCommandRunner = {
async run(tool, args, options) {
capturedPath = args.at(-1);
await rename(selected, saved);
await rename(replacement, selected);
try {
expect(await readFile(path)).not.toEqual(
await readFile(join(saved, "program")),
);
return await real.run(tool, args, options);
} finally {
await rename(selected, replacement);
await rename(saved, selected);
}
},
};
const result = await new NativeMacOSProvider({}, runner, "darwin")
.createClient(target)
.execute("inspect_signature", {});
if (!result.ok) throw result.error;
expect(result.value.result).toMatchObject({
signed: true,
identifier: "rea.original",
});
expect(
inspectSignatureSchema
.parse(result.value.result)
.provenance.map(({ command }) => command.at(-1)),
).toEqual(["$ARTIFACT", "$ARTIFACT", "$ARTIFACT"]);
if (capturedPath === undefined)
throw new Error("Expected codesign invocation");
await expect(lstat(dirname(capturedPath))).rejects.toMatchObject({
code: "ENOENT",
});
},
);
for (const cleanupFails of [false, true])
it(`retains completed provider signature facts when root finalization cancels (fails: ${cleanupFails})`, async () => {
const { path, target } = await fixture();
const controller = new AbortController();
const reason = { caller: "provider-final-root-close" };
const root = join(dirname(path), "controlled-snapshot-root");
let attempts = 0;
const client = new NativeMacOSProvider(
{},
new Captures(),
"darwin",
undefined,
async () => {
await mkdir(root);
return {
path: root,
async close() {
try {
if (cleanupFails && attempts++ === 0)
throw new Error("controlled provider root cleanup failure");
await rm(root, { recursive: true });
} finally {
controller.abort(reason);
}
},
};
},
).createClient(target);
onTestFinished(async () => {
await client.close();
});
const result = await client.execute(
"inspect_signature",
{},
{ signal: controller.signal },
);
expect(result.ok).toBe(false);
if (result.ok)
throw new Error("Expected cancellation after completed provider facts");
expect(result.error.cleanupIncomplete).toBe(cleanupFails);
const facts = inspectSignatureSchema.parse(result.error.partialObservation);
expect(facts.signed).toBe(true);
expect(facts.provenance).toHaveLength(3);
expect(
projectAnalysisError(result.error).details?.partial_observation,
).toEqual(facts);
expect((await client.close()).ok).toBe(true);
expect(resolvedAnalysisError(result.error)).toMatchObject({
_tag: "AnalysisCancelledError",
cause: reason,
});
await expect(lstat(root)).rejects.toMatchObject({ code: "ENOENT" });
});