fix(ida): canonicalize the headless workspace root before opening (#1481)
On macOS the default workspace root is os.tmpdir() under /var/folders, and /var is a symlink to /private/var. idalib-mcp reports the canonical input path from idb_open/idb_list, while REA compared it against the uncanonicalized private copy with normalize(), so every headless IDA analysis failed its identity check (surfacing as unreadable_output) and the spawned idalib worker was never released. Resolve the workspace root with realpath() when allocating the private workspace so the input path REA sends already matches what upstream reports. Co-authored-by: Xingchuan Ma <xingchuan.ma@msn.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
24d9337649
commit
de619bd334
|
|
@ -1,6 +1,6 @@
|
|||
import { createHash, randomUUID } from "node:crypto";
|
||||
import { createReadStream, constants } from "node:fs";
|
||||
import { copyFile } from "node:fs/promises";
|
||||
import { copyFile, realpath } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { basename, join, normalize } from "node:path";
|
||||
import type { BinaryTarget } from "../domain/binaryTargetTypes.js";
|
||||
|
|
@ -39,8 +39,10 @@ export class IdaWorkspace {
|
|||
target: BinaryTarget,
|
||||
root = tmpdir(),
|
||||
): Promise<IdaWorkspace> {
|
||||
// Upstream reports canonical paths (macOS /var -> /private/var), so the
|
||||
// private input path must already be canonical for identity checks.
|
||||
const runtime = await PrivateRuntimeRoot.create({
|
||||
parent: normalize(root),
|
||||
parent: await realpath(normalize(root)),
|
||||
prefix: "rea-ida-",
|
||||
});
|
||||
const inputPath = join(runtime.path, basename(target.path));
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import { chmod, readdir, rm } from "node:fs/promises";
|
||||
import { chmod, readdir, realpath, rm, symlink } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
createIdaTarget,
|
||||
RecordingIdaMcp,
|
||||
} from "../../tests/fixtures/idaMcp.js";
|
||||
import { createTestTempDirectory } from "../../tests/fixtures/temporaryDirectory.js";
|
||||
import type { BinaryTarget } from "../domain/binaryTargetTypes.js";
|
||||
import { IdaSessionClient } from "./IdaSessionClient.js";
|
||||
|
||||
|
|
@ -33,6 +35,26 @@ const clientFor = (root: string, target: BinaryTarget) => {
|
|||
};
|
||||
|
||||
describe("IDA workspace ownership before input preparation", () => {
|
||||
it.skipIf(process.platform === "win32")(
|
||||
"opens the private input through a canonical path when the workspace root is a symlink",
|
||||
async () => {
|
||||
const { root, target } = await createIdaTarget();
|
||||
roots.push(root);
|
||||
const linked = join(
|
||||
await createTestTempDirectory("rea-ida-link-"),
|
||||
"root",
|
||||
);
|
||||
await symlink(root, linked);
|
||||
const { client, producer } = clientFor(linked, target);
|
||||
expect((await client.execute("health", {})).ok).toBe(true);
|
||||
const open = producer.calls.find(({ name }) => name === "idb_open");
|
||||
expect(
|
||||
String(open?.args.input_path).startsWith(`${await realpath(root)}/`),
|
||||
).toBe(true);
|
||||
expect((await client.close()).ok).toBe(true);
|
||||
},
|
||||
);
|
||||
|
||||
it("retains cleanup ownership when copying the admitted input fails", async () => {
|
||||
const { root, target } = await createIdaTarget();
|
||||
roots.push(root);
|
||||
|
|
|
|||
Loading…
Reference in New Issue