Files
rea/src/ghidra/GhidraSessionValues.ts
T
N0zoM1z0 fb679b63c2 feat(ghidra): integrate optional NativeAOT metadata recovery (#748)
* feat(ghidra): integrate optional NativeAOT metadata recovery

* fix(ghidra): bind extension snapshots to configured artifacts

* test(ghidra): verify lazy NativeAOT startup failures

* fix(ghidra): preserve NativeAOT failure reasons and coverage

* fix(ghidra): preserve NativeAOT x64 calling conventions

* test(ghidra): guard recovered NativeAOT method conventions

* fix(ghidra): reject blocking extension file inputs

* fix(ghidra): diagnose incompatible NativeAOT extension classes

* test(ghidra): verify real extension initialization failures

* fix(ghidra): keep extension descriptor fields optional
2026-10-07 00:57:08 +08:00

99 lines
3.6 KiB
TypeScript

import { z } from "zod";
import type { JsonValue } from "../domain/jsonValue.js";
import { err, ok, type Result } from "../domain/result.js";
import { GHIDRA_INVENTORY_OPERATIONS } from "./GhidraInventoryValues.js";
import { GHIDRA_FUNCTION_OPERATIONS } from "./GhidraFunctionValues.js";
import { ghidraExtensionResultSchema } from "./extensions/GhidraExtensions.js";
/** Exact methods proved by the bridge handshake after auto-analysis. */
export const GHIDRA_SESSION_CAPABILITIES = [
"ping",
"shutdown",
...GHIDRA_INVENTORY_OPERATIONS,
...GHIDRA_FUNCTION_OPERATIONS,
] as const;
const capabilitySchema = z.enum(GHIDRA_SESSION_CAPABILITIES);
const sessionInfoSchema = z
.object({
name: z.literal("REA Ghidra bridge"),
run_id: z.string().uuid(),
profile_digest: z.string().regex(/^[a-f0-9]{64}$/u),
provider: z
.object({
id: z.literal("ghidra"),
version: z.string().min(1),
})
.strict(),
read_only: z.boolean(),
analysis_complete: z.boolean(),
analysis_timed_out: z.boolean(),
analysis_extensions: z.array(ghidraExtensionResultSchema).optional(),
capabilities: z.array(capabilitySchema),
target: z
.object({
name: z.string().min(1),
language_id: z.string().min(1),
compiler_spec_id: z.string().min(1),
image_base: z.string().regex(/^0x[0-9a-f]+$/u),
default_address_space: z.string().min(1),
sha256: z.string().regex(/^[a-f0-9]{64}$/u),
})
.strict(),
})
.strict();
/** Authenticated metadata produced after Ghidra finishes auto-analysis. */
export type GhidraSessionInfo = z.infer<typeof sessionInfoSchema>;
/** Require exact run, provider, and profile identity from the Java bridge. */
export const parseGhidraSessionInfo = (
value: JsonValue,
expected: {
readonly runId: string;
readonly providerVersion: string;
readonly profileDigest: string;
readonly targetSha256: string;
readonly expectedReadOnly?: boolean;
readonly expectedLanguageId?: string;
readonly expectedCompilerSpecId?: string;
},
): Result<GhidraSessionInfo, Error> => {
const parsed = sessionInfoSchema.safeParse(value);
const readOnly = expected.expectedReadOnly ?? false;
const expectedCapabilities = GHIDRA_SESSION_CAPABILITIES.filter(
(capability) => !readOnly || capability !== "annotate_native_function",
);
if (
!parsed.success ||
parsed.data.run_id !== expected.runId ||
parsed.data.read_only !== readOnly ||
parsed.data.capabilities.length !== expectedCapabilities.length ||
parsed.data.provider.version !== expected.providerVersion ||
parsed.data.profile_digest !== expected.profileDigest ||
parsed.data.target.sha256 !== expected.targetSha256 ||
(expected.expectedLanguageId !== undefined &&
parsed.data.target.language_id !== expected.expectedLanguageId) ||
(expected.expectedCompilerSpecId !== undefined &&
parsed.data.target.compiler_spec_id !==
expected.expectedCompilerSpecId) ||
new Set(parsed.data.capabilities).size !== expectedCapabilities.length ||
expectedCapabilities.some(
(capability) => !parsed.data.capabilities.includes(capability),
) ||
parsed.data.analysis_complete === parsed.data.analysis_timed_out
)
return err(new Error("Ghidra bridge handshake identity is invalid"));
return ok(parsed.data);
};
/** Require acknowledgement that the private bridge loop has ended. */
export const isGhidraShutdownAcknowledgement = (value: JsonValue): boolean =>
typeof value === "object" &&
value !== null &&
!Array.isArray(value) &&
value.shutdown === true &&
value.project_ephemeral === true;