From 4758ba30b6852c49b9539a2e815af22b3f822341 Mon Sep 17 00:00:00 2001 From: CodeAlex <59381946+CodeAlex52@users.noreply.github.com> Date: Wed, 14 Oct 2026 03:06:53 +0800 Subject: [PATCH] fix(package-verifier): reject overview responses without a procedure count (#2936) * fix(package-verifier): reject overview responses without a procedure count The analyze and inspect checks compared procedure_count with `< 1`, and an absent or null normalized_result makes that comparison false, so a binary_overview response without its required result passed the platform smoke check. Require an integer procedure count before the positive-count comparison. * test(package-verifier): cover malformed overview receipts --------- Co-authored-by: REA Maintenance <76467478+morluto@users.noreply.github.com> --- scripts/verify-package-platform.d.mts | 6 ++ scripts/verify-package-platform.mjs | 44 ++++++++----- .../packagePlatformDiagnostics.test.ts | 62 ++++++++++++++++++- 3 files changed, 96 insertions(+), 16 deletions(-) diff --git a/scripts/verify-package-platform.d.mts b/scripts/verify-package-platform.d.mts index 4342027c25..631ddc93b1 100644 --- a/scripts/verify-package-platform.d.mts +++ b/scripts/verify-package-platform.d.mts @@ -8,4 +8,10 @@ export function assertLinuxPackageProviderFailure(execution: { export function verifyPackagePlatform(input: { readonly cli: string; readonly environment: Readonly>; + readonly platform?: string; + readonly runCommand?: ( + command: string, + arguments_: readonly string[], + environment: Readonly>, + ) => Promise; }): Promise; diff --git a/scripts/verify-package-platform.mjs b/scripts/verify-package-platform.mjs index b4db6bf167..2539376886 100644 --- a/scripts/verify-package-platform.mjs +++ b/scripts/verify-package-platform.mjs @@ -1,8 +1,13 @@ import { json, run, runWithStatus } from "./lib/verify-package-core.mjs"; /** Run platform-specific deep-analysis smoke tests. */ -export async function verifyPackagePlatform({ cli, environment }) { - if (process.platform === "linux") { +export async function verifyPackagePlatform({ + cli, + environment, + platform = process.platform, + runCommand = run, +}) { + if (platform === "linux") { const unsupportedExecution = await runWithStatus( cli, ["analyze", process.execPath, "--json"], @@ -11,25 +16,27 @@ export async function verifyPackagePlatform({ cli, environment }) { assertLinuxPackageProviderFailure(unsupportedExecution); } else { const overview = json( - await run(cli, ["analyze", process.execPath, "--json"], environment), + await runCommand( + cli, + ["analyze", process.execPath, "--json"], + environment, + ), ); - if ( - overview.operation !== "binary_overview" || - overview.normalized_result?.procedure_count < 1 - ) + if (!hasPositiveProcedureCount(overview)) throw new Error( `packaged Hopper-backed analyze CLI failed: ${JSON.stringify(overview)}`, ); const inspected = json( - await run(cli, ["inspect", process.execPath, "--json"], environment), + await runCommand( + cli, + ["inspect", process.execPath, "--json"], + environment, + ), ); - if ( - inspected.operation !== "binary_overview" || - inspected.normalized_result?.procedure_count < 1 - ) + if (!hasPositiveProcedureCount(inspected)) throw new Error("packaged inspect CLI failed"); const functionResult = json( - await run( + await runCommand( cli, ["function", process.execPath, "0x1000", "--json"], environment, @@ -44,7 +51,7 @@ export async function verifyPackagePlatform({ cli, environment }) { `packaged function CLI failed: ${JSON.stringify(functionResult)}`, ); const xrefs = json( - await run( + await runCommand( cli, ["xrefs", process.execPath, "0x1000", "--json"], environment, @@ -56,7 +63,7 @@ export async function verifyPackagePlatform({ cli, environment }) { ) throw new Error(`packaged xrefs CLI failed: ${JSON.stringify(xrefs)}`); const trace = json( - await run( + await runCommand( cli, ["trace", process.execPath, "fixture", "--json"], environment, @@ -93,3 +100,10 @@ export const assertLinuxPackageProviderFailure = (execution) => { `packaged Linux Hopper verification did not fail closed: ${JSON.stringify(failure)}`, ); }; + +const hasPositiveProcedureCount = (result) => + typeof result === "object" && + result !== null && + result.operation === "binary_overview" && + Number.isInteger(result.normalized_result?.procedure_count) && + result.normalized_result.procedure_count > 0; diff --git a/tests/evaluation/package/packagePlatformDiagnostics.test.ts b/tests/evaluation/package/packagePlatformDiagnostics.test.ts index 3eb752d865..3ffb1d0df1 100644 --- a/tests/evaluation/package/packagePlatformDiagnostics.test.ts +++ b/tests/evaluation/package/packagePlatformDiagnostics.test.ts @@ -1,6 +1,9 @@ import { describe, expect, it } from "vitest"; -import { assertLinuxPackageProviderFailure } from "../../../scripts/verify-package-platform.mjs"; +import { + assertLinuxPackageProviderFailure, + verifyPackagePlatform, +} from "../../../scripts/verify-package-platform.mjs"; const missingDisplay = { code: "provider_unavailable", @@ -22,6 +25,37 @@ const execution = (failure: unknown, status = 1) => ({ stdout: JSON.stringify(failure), }); +const overview = (procedure_count?: unknown, operation = "binary_overview") => ({ + operation, + ...(procedure_count === undefined + ? {} + : { normalized_result: { procedure_count } }), +}); + +const verifyMacOS = async ( + analyze: unknown, + inspect: unknown, +): Promise => { + const outputs: Readonly> = { + analyze, + inspect, + function: { + operation: "analyze_function", + provider: { id: "hopper" }, + normalized_result: { procedure: { address: "0x1000" } }, + }, + xrefs: { operation: "xrefs", normalized_result: ["0x1000"] }, + trace: { operation: "trace_feature" }, + }; + await verifyPackagePlatform({ + cli: "fixture-cli", + environment: {}, + platform: "darwin", + runCommand: async (_cli, arguments_) => + JSON.stringify(outputs[arguments_[0]!]) ?? "", + }); +}; + describe("packaged Linux failure verification", () => { it("accepts the unsupported fixture build or an exact missing display dependency", () => { expect(() => @@ -71,3 +105,29 @@ describe("packaged Linux failure verification", () => { }, ); }); + +describe("packaged analyze and inspect result guards", () => { + it("accepts positive integer procedure counts for both commands", async () => { + await verifyMacOS(overview(1), overview(2)); + }); + + const malformed = [ + ["missing result", undefined], + ["null result", null], + ["missing normalized count", overview()], + ["zero count", overview(0)], + ["non-integer count", overview(1.5)], + ["wrong operation", overview(2, "other_operation")], + ] as const; + + it.each(["analyze", "inspect"] as const)( + "rejects malformed %s results before trusting the package", + async (command) => { + for (const [label, invalid] of malformed) { + const analyze = command === "analyze" ? invalid : overview(4); + const inspect = command === "inspect" ? invalid : overview(4); + await expect(verifyMacOS(analyze, inspect), label).rejects.toThrow(); + } + }, + ); +});