From b31f52946fa78ca1df95d641e1611c5144f7a9bd Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Sat, 24 Oct 2026 02:24:28 +0800 Subject: [PATCH] fix(android): honor selected Java paths and retain input issues --- .../references/android-applications.md | 6 ++-- src/android/JadxConfiguration.ts | 4 ++- .../android/AndroidAnalysisService.test.ts | 29 +++++++++++++++++++ .../android/AndroidAnalysisService.ts | 14 ++++++++- 4 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 src/application/android/AndroidAnalysisService.test.ts diff --git a/.agents/skills/reverse-engineer-anything/references/android-applications.md b/.agents/skills/reverse-engineer-anything/references/android-applications.md index 07e8c4ce93..59406d6cbe 100644 --- a/.agents/skills/reverse-engineer-anything/references/android-applications.md +++ b/.agents/skills/reverse-engineer-anything/references/android-applications.md @@ -15,9 +15,9 @@ instead of guessing. The provider requires a separately supplied headless JADX MCP JAR 0.7.1 and Java 17 or newer. `REA_JADX_MCP_JAR` selects the absolute JAR path; `JAVA_HOME` -can select Java. REA setup does not install or configure that JAR, and doctor -provider scopes cover Hopper/Ghidra rather than JADX. Use existing tools and -report missing prerequisites for the Android task specifically. +can select Java. REA setup does not install or configure that JAR. Use +`rea doctor --provider jadx --json` to check configuration and obtain prerequisite +remediation. Readiness does not establish successful analysis of a particular APK. Linux and macOS use the POSIX owned-process boundary. Windows x64 requires the matching bundled native controls and uses an owned Job Object with bidirectional diff --git a/src/android/JadxConfiguration.ts b/src/android/JadxConfiguration.ts index ae93b732b1..3ff117927a 100644 --- a/src/android/JadxConfiguration.ts +++ b/src/android/JadxConfiguration.ts @@ -595,7 +595,9 @@ const resolveJava = async ( Object.entries(environment).find( ([key]) => key.toLowerCase() === "path", )?.[1] ?? ""; - for (const directory of [process.cwd(), ...path.split(delimiter)]) { + for (const directory of path + .split(delimiter) + .filter((entry) => entry !== "")) { throwIfJadxInterrupted(signal); // Windows PATH commonly quotes individual entries containing spaces. const selected = diff --git a/src/application/android/AndroidAnalysisService.test.ts b/src/application/android/AndroidAnalysisService.test.ts new file mode 100644 index 0000000000..5ac8d36c9d --- /dev/null +++ b/src/application/android/AndroidAnalysisService.test.ts @@ -0,0 +1,29 @@ +import { expect, it, vi } from "vitest"; +import { AndroidAnalysisService } from "./AndroidAnalysisService.js"; +import type { AndroidAnalysisPort } from "./AndroidAnalysisPort.js"; +import { projectAnalysisError } from "../../domain/analysisErrorProjection.js"; + +it("retains selector correction issues before target acquisition", async () => { + const execute = vi.fn(); + const provider: AndroidAnalysisPort = { + execute, + inspectAvailability: vi.fn(), + close: vi.fn(), + }; + const result = await new AndroidAnalysisService(provider).execute( + "inspect_android_class", + { + path: "/nonexistent.apk", + class_name: "", + }, + ); + expect(result.ok).toBe(false); + if (result.ok) return; + expect(projectAnalysisError(result.error)).toMatchObject({ + code: "invalid_request", + details: { + issues: [{ path: ["class_name"], reason: "out_of_range", minimum: 1 }], + }, + }); + expect(execute).not.toHaveBeenCalled(); +}); diff --git a/src/application/android/AndroidAnalysisService.ts b/src/application/android/AndroidAnalysisService.ts index 1f91661415..2d3c26f9a2 100644 --- a/src/application/android/AndroidAnalysisService.ts +++ b/src/application/android/AndroidAnalysisService.ts @@ -1,3 +1,4 @@ +import { projectInputIssues } from "../../domain/inputIssueProjection.js"; import type { EvidenceWriter } from "../investigation/InvestigationRecordPort.js"; import { summarizeRetainedAndroidSearch } from "./AndroidSearchEvidenceSummary.js"; import type { AndroidAnalysisPort } from "./AndroidAnalysisPort.js"; @@ -36,7 +37,18 @@ export class AndroidAnalysisService { ); const request = androidRequestSchema.safeParse({ operation, input }); if (!request.success) - return err(new AnalysisInputError(operation, { cause: request.error })); + return err( + new AnalysisInputError( + operation, + { cause: request.error }, + projectInputIssues(request.error.issues, { operation, input }).map( + (issue) => + issue.path[0] === "input" + ? { ...issue, path: issue.path.slice(1) } + : issue, + ), + ), + ); const target = await parseBinaryTarget( request.data.input.path, options?.signal === undefined ? {} : { signal: options.signal },