diff --git a/src/ghidra/GhidraInstallation.test.ts b/src/ghidra/GhidraInstallation.test.ts index 18f45e26be..3c6d46354c 100644 --- a/src/ghidra/GhidraInstallation.test.ts +++ b/src/ghidra/GhidraInstallation.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "vitest"; +import { projectGhidraDoctorInspection } from "./GhidraDoctor.js"; import { ghidraJavaEnvironment, inspectGhidraInstallation, @@ -378,3 +379,47 @@ describe("Ghidra release line and JDK bounds", () => { ).toMatchObject({ status: "available", javaVersion: "17.0.20.1" }); }); }); + +describe("Ghidra configuration paths", () => { + it.each([ + [{ installDir: "./ghidra" }, "GHIDRA_INSTALL_DIR must be absolute"], + [{ installDir: INSTALL, javaHome: "./jdk" }, "JAVA_HOME must be absolute"], + [ + { installDir: "ghidra", javaHome: "jdk" }, + "GHIDRA_INSTALL_DIR and JAVA_HOME must be absolute", + ], + ])( + "rejects relative paths that REA's configuration parser rejects (%o)", + (paths, detail) => { + const result = inspectGhidraInstallation( + { ...paths, platform: "linux", architecture: "x64" }, + host(), + ); + expect(result).toMatchObject({ + status: "unavailable", + rejection: { + code: "not_configured", + reason: expect.stringContaining("reject relative paths"), + }, + }); + expect(result.checks).toContainEqual( + expect.objectContaining({ name: "configuration", detail }), + ); + // Setup registers only an available inspection's environment. + expect(projectGhidraDoctorInspection(result)).toMatchObject({ + available: false, + registrationEnvironment: {}, + }); + }, + ); + + it("judges absolute paths by the inspected platform", () => { + const result = inspectGhidraInstallation( + { installDir: "C:\\ghidra", platform: "win32", architecture: "x64" }, + host(), + ); + expect(result.checks).toContainEqual( + expect.objectContaining({ name: "configuration", status: "passed" }), + ); + }); +}); diff --git a/src/ghidra/GhidraInstallation.ts b/src/ghidra/GhidraInstallation.ts index a521fc171b..d0906fb527 100644 --- a/src/ghidra/GhidraInstallation.ts +++ b/src/ghidra/GhidraInstallation.ts @@ -306,13 +306,7 @@ const installationChecks = ({ javaHome, host, }: GhidraInstallationCheckContext): readonly GhidraInstallationCheck[] => [ - installationCheck({ - name: "configuration", - passed: coordinates.installDir !== null, - code: "not_configured", - detail: coordinates.installDir ?? "GHIDRA_INSTALL_DIR is not set", - remediation: `Set GHIDRA_INSTALL_DIR to an extracted Ghidra ${ghidraReleaseLine()} release directory.`, - }), + configurationCheck(coordinates.installDir, javaHome, platform), installationCheck({ name: "platform", passed: NATIVE_PLATFORMS[platform] !== undefined, @@ -366,6 +360,40 @@ const installationChecks = ({ ), ]; +/** + * REA's configuration parser, and so the MCP server and analysis, accept only + * absolute paths; a relative one that happens to resolve here must not pass. + */ +const configurationCheck = ( + installDir: string | null, + javaHome: string | undefined, + platform: NodeJS.Platform, +): GhidraInstallationCheck => { + const { isAbsolute } = platform === "win32" ? win32 : posix; + const relative = [ + ...(installDir !== null && !isAbsolute(installDir) + ? ["GHIDRA_INSTALL_DIR"] + : []), + ...(javaHome !== undefined && !isAbsolute(javaHome) ? ["JAVA_HOME"] : []), + ]; + if (installDir === null || relative.length === 0) + return installationCheck({ + name: "configuration", + passed: installDir !== null, + code: "not_configured", + detail: installDir ?? "GHIDRA_INSTALL_DIR is not set", + remediation: `Set GHIDRA_INSTALL_DIR to an extracted Ghidra ${ghidraReleaseLine()} release directory.`, + }); + const settings = relative.join(" and "); + return installationCheck({ + name: "configuration", + passed: false, + code: "not_configured", + detail: `${settings} must be absolute`, + remediation: `Set ${settings} to ${relative.length === 1 ? "an absolute path" : "absolute paths"}. REA's analysis and MCP server reject relative paths even when they resolve from the current directory.`, + }); +}; + /** Project an installation probe into caller-visible, secret-free diagnostics. */ export const ghidraInstallationDiagnostics = ( inspection: GhidraInstallationInspection,