fix(verify): report every nested verifier failure reason (#11202)

This commit is contained in:
morluto authored and GitHub committed 2026-11-16 00:39:50 +08:00
1 parent 25b1c42ce4
commit 9cc208fc60
6 files changed
+56 -10

No files matched your search

+3
View File
@@ -8,3 +8,6 @@ export function withVerifierCleanup<T>(
operation: () => T | Promise<T>,
cleanupTasks: readonly (() => void | Promise<void>)[],
): Promise<T>;
/** The leaf reasons of a verifier failure, through nested AggregateErrors. */
export function verifierFailureReasons(cause: unknown): string[];
+12
View File
@@ -36,3 +36,15 @@ export async function withVerifierCleanup(operation, cleanupTasks) {
await closeVerifierResources(cleanupTasks);
return value;
}
/**
* The leaf reasons of a verifier failure. `withVerifierCleanup` and the
* fixtures it releases nest `AggregateError`s (verification failure, cleanup
* failure, retained root), so a reporter that unwraps one level shows only
* "Verifier cleanup failed" instead of why.
*/
export function verifierFailureReasons(cause) {
if (cause instanceof AggregateError)
return cause.errors.flatMap(verifierFailureReasons);
return [cause instanceof Error ? cause.message : String(cause)];
}
+5 -3
View File
@@ -3,7 +3,10 @@ import { basename } from "node:path";
import { FridaInstrumentationService } from "../dist/application/frida/FridaInstrumentationService.js";
import { FridaInstrumentationManager } from "../dist/frida/FridaInstrumentationManager.js";
import { isValidFridaRemoteAddress } from "../dist/domain/fridaRemoteAddress.js";
import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs";
import {
verifierFailureReasons,
withVerifierCleanup,
} from "./lib/verifier-cleanup.mjs";
const options = parseArguments(process.argv.slice(2));
if (options.help) {
@@ -113,9 +116,8 @@ try {
process.stdout.write(`${JSON.stringify(report)}\n`);
if (!report.ok) process.exitCode = 1;
} catch (cause) {
const causes = cause instanceof AggregateError ? cause.errors : [cause];
process.stderr.write(
`Frida real-provider verification failed: ${causes.map((reason) => (reason instanceof Error ? reason.message : String(reason))).join("; ")}\n`,
`Frida real-provider verification failed: ${verifierFailureReasons(cause).join("; ")}\n`,
);
process.exitCode = 1;
}
+5 -6
View File
@@ -4,7 +4,10 @@ import {
findMappedNonFunction,
} from "./lib/ida-feature-trace.mjs";
import { requireMcpToolError } from "./lib/mcp-verifier-results.mjs";
import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs";
import {
verifierFailureReasons,
withVerifierCleanup,
} from "./lib/verifier-cleanup.mjs";
import { withVerifierResourceOwner } from "./lib/verifier-file-resources.mjs";
import assert from "node:assert/strict";
@@ -454,11 +457,7 @@ try {
phase,
message: cause instanceof Error ? cause.message : String(cause),
...(cause instanceof AggregateError
? {
causes: cause.errors.map((failure) =>
failure instanceof Error ? failure.message : String(failure),
),
}
? { causes: verifierFailureReasons(cause) }
: {}),
};
process.exitCode = 1;
+8 -1
View File
@@ -32,7 +32,7 @@ export class FridaInstrumentationService {
return { ok: true, value: { cleanupError: null, evidence: { normalized_result: { pid, messages: [{ type: 'send', payload: { rea_frida_verification: 'ok', pid: process.env.REA_PROBE_KIND === 'wrong-marker' ? 42 : pid } }] } } } };
}
}\`;
const manager = \`export class FridaInstrumentationManager { async closeAll() {} }\`;
const manager = \`export class FridaInstrumentationManager { async closeAll() { if (process.env.REA_PROBE_KIND === 'cleanup') throw new AggregateError([new Error('controlled EACCES removing the Frida fixture')], 'Frida cleanup failed'); } }\`;
registerHooks({ resolve(specifier, context, next) {
if (context.parentURL?.endsWith('/scripts/verify-frida.mjs')) {
if (specifier.endsWith('/FridaInstrumentationService.js')) return { shortCircuit: true, url: 'data:text/javascript,' + encodeURIComponent(service) };
@@ -57,6 +57,13 @@ registerHooks({ resolve(specifier, context, next) {
["missing", false, "Frida returned no valid target PID"],
["wrong", true, "Frida observed a different target PID"],
["wrong-marker", true, "Selected target did not return the Frida marker"],
// The verification failure and the nested cleanup reason both reach the
// operator, not only the outer "cleanup failed" wrapper.
[
"cleanup",
true,
"Frida observed a different target PID; controlled EACCES removing the Frida fixture",
],
] as const) {
await expect(invoke(kind, remote)).rejects.toMatchObject({
stderr: expect.stringContaining(message),
@@ -1,6 +1,7 @@
import { expect, it } from "vitest";
import {
closeVerifierResources,
verifierFailureReasons,
withVerifierCleanup,
} from "../../../scripts/lib/verifier-cleanup.mjs";
@@ -215,3 +216,25 @@ it("preserves nested aggregates and independent failures with the same identity"
expect(error.errors[2]).toBe(shared);
expect(error.errors[3]).toBe(shared);
});
it("reports every leaf reason of a nested verifier failure", async () => {
const failure: unknown = await withVerifierCleanup(async () => {
throw new Error("analysis assertion failed");
}, [
() =>
closeVerifierResources([
async () => {
throw new AggregateError(
[new Error("EACCES: fixture root not removed"), "raw reason"],
"Retained verifier fixture",
);
},
]),
]).catch((cause: unknown) => cause);
expect(verifierFailureReasons(failure)).toEqual([
"analysis assertion failed",
"EACCES: fixture root not removed",
"raw reason",
]);
expect(verifierFailureReasons(new Error("plain"))).toEqual(["plain"]);
});