mirror of
https://github.com/morluto/rea.git
synced 2026-10-19 04:19:54 -05:00
fix(electron): preserve selected fuse inspection paths (#3104)
Preserve caller-selected POSIX symlink/parent paths during CLI anchoring and Electron bundle traversal. Resolve terminal directory suffixes through checked real directories, retaining their identities for before/after readback. Keep the existing Windows CLI resolution, bounded scan, and output contracts. Validation: six regressions reproduced on main 7ad75bdc8; 41 focused tests pass with one Windows-only skip. Full static/docs checks and final main-sync fast checks pass. Official Electron v44.7.1 release archives, extracted executable hashes and independent fuse wires verify actual ELF/PE/Mach-O bytes. Real CLI and stdio MCP reproduce main's wrong target and confirm corrected artifact identity plus CLI/MCP parity; owned MCP servers exit. Equivalent direct-file outputs remain 963-1,054 o200k tokens. Individual timing measurements and their limits are recorded in the PR; no speedup is claimed. Native Windows/macOS REA execution and target application execution are not claimed. Co-authored-by: Yann Roberto <165009858+shoyann@users.noreply.github.com> Co-authored-by: N0zoM1z0 <161784452+N0zoM1z0@users.noreply.github.com> Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
This commit is contained in:
5 files changed
+304
-29
No files matched your search
@@ -20,6 +20,11 @@ requires an absolute filesystem path and preserves that exact selection:
|
||||
}
|
||||
```
|
||||
|
||||
On POSIX hosts, the CLI preserves path components so symlinks are followed
|
||||
before parent (`..`) steps. Bundle selections may end in `.` or parent steps
|
||||
through real directories; a terminal parent step through a directory symlink
|
||||
is rejected. Windows CLI paths retain native drive and separator resolution.
|
||||
|
||||
Check this before `observe_javascript_runtime` with `electron-main`. When
|
||||
`EnableNodeCliInspectArguments` is disabled or removed, Electron ignores
|
||||
Inspector flags, and a later target listing can simply show no main-process
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { resolve } from "node:path";
|
||||
import { isAbsolute, resolve, sep } from "node:path";
|
||||
|
||||
import { Cli, z } from "incur";
|
||||
|
||||
@@ -55,7 +55,7 @@ const registerElectronFuseCommand = (
|
||||
withCommandCancellation((signal) =>
|
||||
logCliCommand(logger, CLI_COMMANDS.inspectElectronFuses, async () => {
|
||||
const result = await service.inspect(
|
||||
{ path: resolve(args.path) },
|
||||
{ path: absoluteElectronFusePath(args.path) },
|
||||
{ signal },
|
||||
);
|
||||
return result.ok ? result.value : projectAnalysisError(result.error);
|
||||
@@ -64,6 +64,16 @@ const registerElectronFuseCommand = (
|
||||
});
|
||||
};
|
||||
|
||||
/** Anchor relative input without changing which file symlink/.. selects. */
|
||||
const absoluteElectronFusePath = (path: string): string => {
|
||||
if (path === "") return path;
|
||||
// Win32 resolves dot components before directory links, including drive-relative paths.
|
||||
if (sep === "\\") return resolve(path);
|
||||
if (isAbsolute(path)) return path;
|
||||
const base = process.cwd();
|
||||
return `${base}${base.endsWith(sep) ? "" : sep}${path}`;
|
||||
};
|
||||
|
||||
const registerElectronActiveCommand = (
|
||||
cli: ReturnType<typeof Cli.create>,
|
||||
logger: Logger,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { BigIntStats } from "node:fs";
|
||||
import { lstat, readlink } from "node:fs/promises";
|
||||
import { basename, dirname, isAbsolute, join, sep } from "node:path";
|
||||
import { basename, dirname, isAbsolute, sep } from "node:path";
|
||||
|
||||
import { isPathWithinRoot } from "../domain/localPath.js";
|
||||
|
||||
@@ -56,9 +56,13 @@ export const resolveElectronFuseTarget = async (
|
||||
frameworkRootOf(selectedPath) === null
|
||||
? { path: selectedPath, metadata, nodes: [] }
|
||||
: await resolveFrameworkSymlink(selectedPath, signal);
|
||||
else if (metadata.isDirectory())
|
||||
else if (metadata.isDirectory()) {
|
||||
file = await resolveBundleBinary(selectedPath, signal);
|
||||
else
|
||||
file = {
|
||||
...file,
|
||||
nodes: [{ path: selectedPath, metadata }, ...file.nodes],
|
||||
};
|
||||
} else
|
||||
throw new ElectronFusePathFailure(
|
||||
"invalid_format",
|
||||
`Expected a regular file or Electron app bundle: ${selectedPath}`,
|
||||
@@ -94,20 +98,58 @@ const resolveBundleBinary = async (
|
||||
selectedPath: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<ResolvedElectronFuseFile> => {
|
||||
const name = basename(selectedPath);
|
||||
const directory = await resolveDirectorySuffix(selectedPath, signal);
|
||||
const name = basename(directory.path);
|
||||
let file: ResolvedElectronFuseFile;
|
||||
if (name === FRAMEWORK_NAME)
|
||||
return resolveKnownBinary(selectedPath, selectedPath, signal);
|
||||
if (name.endsWith(".app")) {
|
||||
return resolveKnownBinary(
|
||||
join(selectedPath, "Contents", "Frameworks", FRAMEWORK_NAME),
|
||||
file = await resolveKnownBinary(directory.path, selectedPath, signal);
|
||||
else if (name.endsWith(".app"))
|
||||
file = await resolveKnownBinary(
|
||||
appendPath(directory.path, "Contents", "Frameworks", FRAMEWORK_NAME),
|
||||
selectedPath,
|
||||
signal,
|
||||
);
|
||||
else
|
||||
throw new ElectronFusePathFailure(
|
||||
"invalid_format",
|
||||
`Expected an Electron .app bundle, ${FRAMEWORK_NAME} directory, or executable file: ${selectedPath}`,
|
||||
);
|
||||
return { ...file, nodes: [...directory.nodes, ...file.nodes] };
|
||||
};
|
||||
|
||||
/** Reduce directory suffixes only when doing so preserves filesystem selection. */
|
||||
const resolveDirectorySuffix = async (
|
||||
selectedPath: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<{
|
||||
readonly path: string;
|
||||
readonly nodes: readonly ElectronFusePathNode[];
|
||||
}> => {
|
||||
let path = selectedPath;
|
||||
const pending: string[] = [];
|
||||
const nodes: ElectronFusePathNode[] = [];
|
||||
while (true) {
|
||||
signal?.throwIfAborted();
|
||||
const name = basename(path);
|
||||
// lstat("link/") follows a link; inspect the named component itself.
|
||||
if (name !== "") while (path.endsWith(sep)) path = path.slice(0, -1);
|
||||
if (name === "." || name === "..") {
|
||||
pending.push(name);
|
||||
path = dirname(path);
|
||||
continue;
|
||||
}
|
||||
const component = pending.pop();
|
||||
if (component === undefined) return { path, nodes };
|
||||
if (component === ".") continue;
|
||||
const metadata = await lstat(path, { bigint: true });
|
||||
if (!metadata.isDirectory() || metadata.isSymbolicLink())
|
||||
throw new ElectronFusePathFailure(
|
||||
"invalid_value",
|
||||
`A bundle path ending in '..' requires a real directory before each parent segment: ${selectedPath}`,
|
||||
);
|
||||
nodes.push({ path, metadata });
|
||||
path = dirname(path);
|
||||
}
|
||||
throw new ElectronFusePathFailure(
|
||||
"invalid_format",
|
||||
`Expected an Electron .app bundle, ${FRAMEWORK_NAME} directory, or executable file: ${selectedPath}`,
|
||||
);
|
||||
};
|
||||
|
||||
/** `root` is the framework directory for both an `.app` and a framework selection. */
|
||||
@@ -154,7 +196,7 @@ const resolveInside = async (
|
||||
last = "directory";
|
||||
continue;
|
||||
}
|
||||
const absolute = join(root, ...resolved, segment);
|
||||
const absolute = appendPath(root, ...resolved, segment);
|
||||
if (!isPathWithinRoot(root, absolute)) return { kind: "escapes" };
|
||||
let metadata;
|
||||
try {
|
||||
@@ -185,7 +227,7 @@ const resolveInside = async (
|
||||
}
|
||||
if (last !== "file" || fileMetadata === undefined)
|
||||
return { kind: "not-file" };
|
||||
const path = join(root, ...resolved);
|
||||
const path = appendPath(root, ...resolved);
|
||||
return isPathWithinRoot(root, path)
|
||||
? { kind: "file", path, metadata: fileMetadata, nodes }
|
||||
: { kind: "escapes" };
|
||||
@@ -229,7 +271,7 @@ const requireRealDirectories = async (
|
||||
let current = anchor;
|
||||
const nodes: ElectronFusePathNode[] = [];
|
||||
for (const segment of segments) {
|
||||
current = join(current, segment);
|
||||
current = appendPath(current, segment);
|
||||
let metadata;
|
||||
try {
|
||||
metadata = await lstat(current, { bigint: true });
|
||||
@@ -301,6 +343,10 @@ const relativeInside = (root: string, path: string): string | null => {
|
||||
return value === "" ? null : value.split(sep).join("/");
|
||||
};
|
||||
|
||||
/** Append known components without normalizing a caller-selected parent path. */
|
||||
const appendPath = (parent: string, ...segments: readonly string[]): string =>
|
||||
`${parent}${parent.endsWith(sep) ? "" : sep}${segments.join(sep)}`;
|
||||
|
||||
const isMissing = (cause: unknown): boolean =>
|
||||
cause instanceof Error &&
|
||||
"code" in cause &&
|
||||
|
||||
@@ -682,3 +682,20 @@ it("reports a checked version directory removed before open as changed", async (
|
||||
readElectronFuseImage(located.file.path, { selection: located.file }),
|
||||
).rejects.toBeInstanceOf(RegularFileChangedError);
|
||||
});
|
||||
|
||||
it("retains the identity of a directory removed by a terminal parent segment", async () => {
|
||||
const root = await createTestTempDirectory("rea-electron-parent-identity-");
|
||||
const framework = join(root, "Electron Framework.framework");
|
||||
const versions = join(framework, "Versions");
|
||||
await mkdir(versions, { recursive: true });
|
||||
await writeFile(
|
||||
join(framework, "Electron Framework"),
|
||||
thinFuseImage([0x31]).bytes,
|
||||
);
|
||||
const located = await resolveElectronFuseTarget(`${versions}/..`);
|
||||
await rename(versions, `${versions}.saved`);
|
||||
await mkdir(versions);
|
||||
await expect(
|
||||
readElectronFuseImage(located.file.path, { selection: located.file }),
|
||||
).rejects.toBeInstanceOf(RegularFileChangedError);
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { mkdir, symlink, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { mkdir, realpath, symlink, writeFile } from "node:fs/promises";
|
||||
import { dirname, join, parse, sep } from "node:path";
|
||||
import { createHash } from "node:crypto";
|
||||
import { expect } from "vitest";
|
||||
|
||||
@@ -13,6 +13,142 @@ import {
|
||||
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
|
||||
import { cliTest } from "../support/cli/cliFixture.js";
|
||||
|
||||
// Windows resolves dot components before directory links in Win32 paths.
|
||||
cliTest.skipIf(process.platform === "win32").for([
|
||||
{ kind: "binary", mode: "absolute" },
|
||||
{ kind: "binary", mode: "relative" },
|
||||
{ kind: "bundle", mode: "absolute" },
|
||||
{ kind: "bundle", mode: "relative" },
|
||||
] as const)(
|
||||
"preserves $mode $kind selections containing a symlink before a parent segment",
|
||||
async ({ kind, mode }, { cli }) => {
|
||||
const root = await createTestTempDirectory("rea-electron-fuses-cli-path-");
|
||||
const framework = "Contents/Frameworks/Electron Framework.framework";
|
||||
const directory =
|
||||
kind === "binary"
|
||||
? join(root, "Fixture.app", framework, "Versions")
|
||||
: root;
|
||||
const selectedTail =
|
||||
kind === "binary" ? "A/Electron Framework" : "Fixture.app";
|
||||
const binaryTail =
|
||||
kind === "binary"
|
||||
? selectedTail
|
||||
: `Fixture.app/${framework}/Versions/A/Electron Framework`;
|
||||
const binary = join(directory, "nested", binaryTail);
|
||||
const sibling = join(directory, binaryTail);
|
||||
const image = thinFuseImage([0x31, 0x30, 0x30, 0x31]);
|
||||
const otherImage = thinFuseImage([0x30, 0x30, 0x30, 0x30]);
|
||||
await mkdir(dirname(binary), { recursive: true });
|
||||
await mkdir(dirname(sibling), { recursive: true });
|
||||
await mkdir(join(directory, "nested", "B"), { recursive: true });
|
||||
await writeFile(binary, image.bytes);
|
||||
await writeFile(sibling, otherImage.bytes);
|
||||
await symlink("nested/B", join(directory, "Current"));
|
||||
const selected = `${directory}/Current/../${selectedTail}`;
|
||||
expect(await realpath(selected)).toBe(
|
||||
kind === "binary" ? binary : join(root, "nested", "Fixture.app"),
|
||||
);
|
||||
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
"inspect-electron-fuses",
|
||||
mode === "absolute" ? selected : selected.slice(root.length + 1),
|
||||
"--json",
|
||||
],
|
||||
cwd: root,
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
const evidence = toolContract("inspect_electron_fuses").outputSchema.parse(
|
||||
result.json,
|
||||
);
|
||||
const digest = createHash("sha256").update(image.bytes).digest("hex");
|
||||
expect(evidence.normalized_result.artifact).toMatchObject({
|
||||
selected_path: selected,
|
||||
resolved_path:
|
||||
kind === "binary"
|
||||
? binary
|
||||
: `${selected}/${framework}/Versions/A/Electron Framework`,
|
||||
sha256: digest,
|
||||
});
|
||||
expect(
|
||||
await realpath(evidence.normalized_result.artifact.resolved_path),
|
||||
).toBe(binary);
|
||||
expect(evidence.subject?.local_path).toBe(
|
||||
evidence.normalized_result.artifact.resolved_path,
|
||||
);
|
||||
expect(evidence.subject?.digest.sha256).toBe(digest);
|
||||
expect(evidence.normalized_result.runtime_implications).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
subject: "electron_run_as_node",
|
||||
outcome: "honored",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
subject: "node_cli_inspect_arguments",
|
||||
outcome: "honored",
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
const control = await cli.run({
|
||||
arguments: ["inspect-electron-fuses", sibling, "--json"],
|
||||
});
|
||||
expect(control.exitCode, control.stderr).toBe(0);
|
||||
const siblingEvidence = toolContract(
|
||||
"inspect_electron_fuses",
|
||||
).outputSchema.parse(control.json);
|
||||
expect(siblingEvidence.normalized_result.artifact.sha256).toBe(
|
||||
createHash("sha256").update(otherImage.bytes).digest("hex"),
|
||||
);
|
||||
expect(siblingEvidence.normalized_result.runtime_implications).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
subject: "electron_run_as_node",
|
||||
outcome: "not_honored",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
subject: "node_cli_inspect_arguments",
|
||||
outcome: "not_honored",
|
||||
}),
|
||||
]),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
cliTest.skipIf(process.platform !== "win32")(
|
||||
"retains Windows drive, root, and mixed-separator path resolution",
|
||||
async ({ cli }) => {
|
||||
const root = await createTestTempDirectory("rea-electron-fuses-win-path-");
|
||||
const tail =
|
||||
"Fixture.app/Contents\\Frameworks/Electron Framework.framework/Versions\\A/Electron Framework";
|
||||
const binary = join(root, tail);
|
||||
const image = thinFuseImage([0x31]);
|
||||
await mkdir(dirname(binary), { recursive: true });
|
||||
await writeFile(binary, image.bytes);
|
||||
const volume = parse(root).root;
|
||||
for (const input of [
|
||||
binary.replaceAll("\\", "/"),
|
||||
tail,
|
||||
`${volume.slice(0, -1)}${tail}`,
|
||||
binary.slice(volume.length - 1),
|
||||
]) {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-electron-fuses", input, "--json"],
|
||||
cwd: root,
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
const evidence = toolContract(
|
||||
"inspect_electron_fuses",
|
||||
).outputSchema.parse(result.json);
|
||||
expect(evidence.normalized_result.artifact).toMatchObject({
|
||||
selected_path: binary,
|
||||
resolved_path: binary,
|
||||
sha256: createHash("sha256").update(image.bytes).digest("hex"),
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
cliTest(
|
||||
"reads thin and universal Electron fuse wires through the public CLI",
|
||||
async ({ cli }) => {
|
||||
@@ -116,19 +252,80 @@ cliTest(
|
||||
await mkdir(join(binary, ".."), { recursive: true });
|
||||
await writeFile(binary, image.bytes);
|
||||
await symlink("A", join(binary, "..", "..", "Current"));
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-electron-fuses", app, "--json"],
|
||||
for (const { input, cwd, selected } of [
|
||||
{ input: app, cwd: root, selected: app },
|
||||
{ input: `${app}/./`, cwd: root, selected: `${app}/./` },
|
||||
{ input: "Fixture.app/.", cwd: root, selected: `${app}/.` },
|
||||
{ input: ".", cwd: app, selected: `${app}${sep}.` },
|
||||
{
|
||||
input: "..",
|
||||
cwd: join(app, "Contents"),
|
||||
selected: `${app}${sep}Contents${sep}..`,
|
||||
},
|
||||
{
|
||||
input: ".././..",
|
||||
cwd: join(app, "Contents", "Frameworks"),
|
||||
selected: `${app}${sep}Contents${sep}Frameworks${sep}.././..`,
|
||||
},
|
||||
]) {
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-electron-fuses", input, "--json"],
|
||||
cwd,
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
const evidence = toolContract(
|
||||
"inspect_electron_fuses",
|
||||
).outputSchema.parse(result.json);
|
||||
expect(evidence.normalized_result.artifact).toMatchObject({
|
||||
selected_path: process.platform === "win32" ? app : selected,
|
||||
resolved_path: binary,
|
||||
format: "mach-o",
|
||||
});
|
||||
expect(evidence.subject?.local_path).toBe(binary);
|
||||
}
|
||||
|
||||
const empty = await cli.run({
|
||||
arguments: ["inspect-electron-fuses", "", "--json"],
|
||||
cwd: app,
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
const evidence = toolContract("inspect_electron_fuses").outputSchema.parse(
|
||||
result.json,
|
||||
expect(empty.exitCode, empty.stderr).toBe(1);
|
||||
expect(empty.json).toMatchObject({
|
||||
code: "invalid_request",
|
||||
details: { operation: "inspect_electron_fuses" },
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
cliTest.skipIf(process.platform === "win32")(
|
||||
"refuses a terminal bundle parent segment after a directory symlink",
|
||||
async ({ cli }) => {
|
||||
const root = await createTestTempDirectory(
|
||||
"rea-electron-fuses-cli-parent-",
|
||||
);
|
||||
expect(evidence.normalized_result.artifact).toMatchObject({
|
||||
selected_path: app,
|
||||
resolved_path: binary,
|
||||
format: "mach-o",
|
||||
const app = join(root, "Fixture.app");
|
||||
const binary = join(
|
||||
app,
|
||||
"Contents",
|
||||
"Frameworks",
|
||||
"Electron Framework.framework",
|
||||
"Electron Framework",
|
||||
);
|
||||
await mkdir(dirname(binary), { recursive: true });
|
||||
await writeFile(binary, thinFuseImage([0x31]).bytes);
|
||||
await symlink("Contents", join(app, "linked"));
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-electron-fuses", `${app}/linked//..`, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(1);
|
||||
expect(result.json).toMatchObject({
|
||||
code: "invalid_request",
|
||||
details: {
|
||||
operation: "inspect_electron_fuses",
|
||||
issues: [
|
||||
expect.objectContaining({ path: ["path"], reason: "invalid_value" }),
|
||||
],
|
||||
},
|
||||
});
|
||||
expect(evidence.subject?.local_path).toBe(binary);
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
Reference in new issue
Block a user