Files
rea/tests/boundary/cli/javascriptIntegrityPolicyCli.test.ts
641a8395ee fix(cli): refuse a JavaScript integrity policy the binary analyze lane cannot honor (#2808)
* fix(cli): refuse an integrity policy the binary analyze lane cannot honor

`analyze` declares --integrity-policy for its JavaScript lane and forwards it
only there. On a binary target the flag was accepted and then dropped:
inspect_artifact-style integrity handling does not exist on this path, and
binary_overview is a strictObject with no input at all, so the request could
never take effect and the run proceeded as if the caller had not asked.

The same branch already refuses source_paths on a binary target with a typed
AnalysisInputError, and the sibling `inspect` command refuses the flag outright,
so the refusal is the established handling here. The policy option carries a
default, so the check keys off a value that differs from the owner schema's own
default rather than the flag's presence; otherwise every binary analyze would be
refused. The default is read from the schema instead of being restated.

Verified with 5 integrity policy CLI cases, check:fast and
verify:test-discovery (1023 files) on macOS arm64, and by running the built CLI
for the no-flag, explicit-default and record-and-continue cases. The regression
case failed with the command reaching the binary lane before the change.

* fix(cli): reject explicit default integrity policies for binaries

* fix(cli): keep an omitted integrity policy absent on the binary lane

`javascriptApplicationOptions.shape.integrityPolicy` fills an omission with its
default, and `.optional()` keeps reporting that parsed value, so
`options.integrityPolicy !== undefined` was true for every `analyze <target>`
call: ordinary binary analysis was rejected before the target was even read.

List `z.undefined()` first in a union so an omitted option stays absent while
the owner still validates a supplied value, and reapply the owner's metadata so
help and `--schema` keep describing the option.

Validation: tests/boundary/cli/javascriptIntegrityPolicyCli.test.ts reports
1 failed | 5 passed without this change (the case "still routes a binary
analyze target without an integrity policy" returned invalid_request for
integrity_policy instead of reaching the target) and 6 passed with it;
`npm run check:fast` passes on macOS 26.5.1 arm64 with Node.js 24.18.0.

* fix(cli): omit integrity defaults from binary option schema

* test(ci): isolate report fixtures and declare PowerPC Java bounds

---------

Co-authored-by: 吴佳浩 <wujiahao@supremind.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-10-14 07:28:00 +08:00

107 lines
3.3 KiB
TypeScript

import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { expect, it } from "vitest";
import { createStrippedAsarAddon } from "../../fixtures/strippedAsarAddon.js";
const execute = promisify(execFile);
const run = async (arguments_: readonly string[]): Promise<unknown> => {
const { stdout } = await execute(
process.execPath,
["scripts/rea.mjs", ...arguments_, "--json"],
{ cwd: process.cwd(), maxBuffer: 16 * 1024 * 1024 },
);
return JSON.parse(stdout);
};
const runFailure = async (arguments_: readonly string[]): Promise<unknown> => {
try {
await run(arguments_);
} catch (cause: unknown) {
if (typeof cause !== "object" || cause === null || !("stdout" in cause))
throw cause;
const stdout = cause.stdout;
if (typeof stdout !== "string") throw cause;
return JSON.parse(stdout);
}
throw new Error("Expected the CLI command to fail");
};
it.each(["analyze-javascript-application", "analyze"])(
"%s carries the integrity policy into shared JavaScript analysis",
async (command) => {
const { archive, addon } = await createStrippedAsarAddon();
const result = await run([
command,
archive,
"--integrity-policy",
"record-and-continue",
]);
expect(result).toMatchObject({
operation: "analyze_javascript_application",
parameters: { integrity_policy: "record-and-continue" },
normalized_result: {
integrity_contradictions: [
{ logical_path: addon, trust: "observed-untrusted" },
],
graph: { coverage: { status: "partial" } },
},
});
},
);
it("rejects the JavaScript integrity policy on binary-only inspection", async () => {
await expect(
run(["inspect", "unused.asar", "--integrityPolicy", "record-and-continue"]),
).rejects.toMatchObject({
code: 1,
stdout: expect.stringContaining("Unknown flag: --integrityPolicy"),
});
});
it.each(["fail", "record-and-continue"] as const)(
"rejects the explicitly selected %s integrity policy on a binary analyze target",
async (policy) => {
const failure = await runFailure([
"analyze",
"unused.bin",
"--integrity-policy",
policy,
]);
expect(failure).toMatchObject({
code: "invalid_request",
details: {
issues: [
{
path: ["integrity_policy"],
reason: "invalid_value",
message:
"An integrity policy requires the static JavaScript ASAR/directory analysis lane; binary_overview accepts no input.",
},
],
},
});
},
);
it("still routes a binary analyze target without an integrity policy", async () => {
await expect(run(["analyze", "unused.bin"])).rejects.toMatchObject({
code: 1,
stdout: expect.stringContaining("target does not exist"),
});
});
it("advertises the optional integrity policy without an undefined union", async () => {
const schema = (await run(["analyze", "--schema"])) as {
options?: { properties?: { integrityPolicy?: unknown } };
};
const policy = schema.options?.properties?.integrityPolicy;
expect(policy).toMatchObject({
type: "string",
enum: ["fail", "record-and-continue"],
description: "Behavior when declared application integrity does not match",
});
expect(JSON.stringify(policy)).not.toContain("undefined");
});