[GH-ISSUE #3924] [Bug] MCP JavaScript application analysis accepts a relative input_path #1533

Closed
opened 2026-10-16 21:06:46 +00:00 by bsakaguchi · 3 comments
Owner

Originally created by @coygeek on GitHub (Oct 15, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/3924

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Evidence, artifacts, or reconstruction

Observed behavior

analyze_javascript_application accepts input_path: "app" and analyzes the app directory under the MCP server's working directory, even though its documented input contract requires an absolute path. The observed request completed successfully and parsed the fixture's JavaScript file. This makes relative-input handling depend on where the server was started and omits the promised input correction.

Reproduction

From the baseline checkout root with the compiled runtime and dependencies available, run the following script. It creates a disposable directory containing app/main.js, starts the real stdio MCP entry point with that directory as its working directory, and compares relative and absolute paths to the same fixture. This script was executed and exited successfully.

node --input-type=module <<'NODE'
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { mkdtemp, mkdir, writeFile, realpath, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";

const root = await mkdtemp(join(tmpdir(), "rea-relative-input-"));
const app = join(root, "app");
const home = join(root, "home");
await mkdir(app);
await mkdir(home);
await writeFile(join(app, "main.js"), "export const value = 1;\n");
const client = new Client({ name: "relative-input-check", version: "1" });
const transport = new StdioClientTransport({
  command: process.execPath,
  args: [resolve("scripts/rea.mjs"), "mcp"],
  cwd: root,
  env: { PATH: process.env.PATH ?? "", HOME: home },
  stderr: "pipe",
});
transport.stderr?.on("data", () => {});
try {
  await client.connect(transport);
  const responses = [];
  for (const input_path of ["app", app]) {
    const response = await client.callTool({
      name: "analyze_javascript_application",
      arguments: { input_path, format: "directory" },
    });
    const evidence = response.structuredContent;
    responses.push(evidence);
    console.log(JSON.stringify({
      relative_input: input_path === "app",
      isError: response.isError ?? false,
      provider: evidence?.provider?.id,
      operation: evidence?.operation,
      selected_server_app: evidence?.normalized_result?.input_path === await realpath(app),
      parsed_javascript_files: evidence?.normalized_result?.statistics?.parsed_javascript_files,
    }));
  }
  console.log(JSON.stringify({ same_evidence: responses[0]?.evidence_id === responses[1]?.evidence_id }));
} finally {
  await client.close();
  await transport.close();
  await rm(root, { recursive: true, force: true });
}
NODE

The first call is the failing case: {"input_path":"app","format":"directory"}. The second call uses the generated absolute path as a control. The script prints the relevant result fields and removes its fixture after closing the MCP connection.

Expected behavior

Reject relative input_path values with an input error identifying the absolute-path requirement, without analyzing server-relative content. Keep the absolute-path control successful. docs/javascript-artifact-reconstruction.md:40-56, especially line 50 immediately after the MCP example, explicitly requires an absolute input_path.

Impact and scope

P2. A relative MCP input violates the documented absolute-path contract and selects content according to the server working directory. The successful analysis was reproduced. Selection of a different unintended artifact is a source-derived consequence, not an observed incident. No privilege boundary was crossed. The absolute-path control succeeded and is an observed practical workaround.

REA version

rea 6.5.0 at source commit 4692bb3330

Runtime and platform

Node 24.18.0; macOS arm64

Target and artifact context

Disposable local directory containing only app/main.js with inert JavaScript; production stdio MCP server with a controlled working directory and isolated HOME.

Interface

MCP

Earliest failure stage

Request parsing or validation

Relevant output or evidence

Both calls returned successful Evidence, selected the canonical path of the server's app directory, and parsed one JavaScript file. They returned the same Evidence ID. The script printed:

{"relative_input":true,"isError":false,"provider":"rea-javascript-application","operation":"analyze_javascript_application","selected_server_app":true,"parsed_javascript_files":1}
{"relative_input":false,"isError":false,"provider":"rea-javascript-application","operation":"analyze_javascript_application","selected_server_app":true,"parsed_javascript_files":1}
{"same_evidence":true}

A separate observed call with input_path: "missing-app" returned an invalid_request error with category invalid_input, issue reason invalid_value, and the message No file or directory exists at the selected input path (ENOENT): missing-app. It did not identify the relative-path violation. These observations show that the same unsupported path shape is accepted or treated as missing according to the server's filesystem state.

Source-derived reasoning explains the result: the registered tool's input schema admits nonempty relative strings, and the shared workflow resolves the selected string with realpath before checking its format. A same-named directory under a different server working directory can therefore select a different artifact. This consequence was not tested with a second artifact and is not an observed wrong-target incident.

Typed outcome or recovery expected

Run the same relative request with the server-relative app fixture present. It must return an input error identifying input_path and explaining that an absolute path is required, without returning successful analysis Evidence. Repeat with missing-app; it must receive the same path-shape correction rather than an existence-dependent error. Keep the absolute-path control successful and confirm it still selects the fixture and parses its JavaScript file. These checks restore the documented MCP contract without prescribing a particular validation layer or changing the CLI's path policy.

Analysis or suspected cause

Source-derived: the public input schema uses localPathStringSchema, which rejects empty strings and NUL bytes but does not require an absolute path. See src/contracts/javascript/electronToolContracts.ts:34-38,92-98, src/domain/javascript/javascriptApplicationAnalysis.ts:27-33, and src/domain/localPath.ts:15-20. Reconstruction uses a nonempty string schema at src/application/javascript/JavaScriptArtifactReconstructionInput.ts:7-12, then realpath(inputPath) at src/application/javascript/JavaScriptArtifactReconstruction.ts:330-335. The handler forwards admitted input at src/server/registerElectronTools.ts:99-117.

Additional context

The cited failing code is unchanged on current origin/main at 22ec8bdb2f7d4626587f23b1eed4a207df600386. The expectation is confined to MCP; this report does not impose a relative-path rejection policy on the CLI.

Originally created by @coygeek on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3924 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Evidence, artifacts, or reconstruction ### Observed behavior `analyze_javascript_application` accepts `input_path: "app"` and analyzes the `app` directory under the MCP server's working directory, even though its documented input contract requires an absolute path. The observed request completed successfully and parsed the fixture's JavaScript file. This makes relative-input handling depend on where the server was started and omits the promised input correction. ### Reproduction From the baseline checkout root with the compiled runtime and dependencies available, run the following script. It creates a disposable directory containing `app/main.js`, starts the real stdio MCP entry point with that directory as its working directory, and compares relative and absolute paths to the same fixture. This script was executed and exited successfully. ```sh node --input-type=module <<'NODE' import { Client } from "@modelcontextprotocol/client"; import { StdioClientTransport } from "@modelcontextprotocol/client/stdio"; import { mkdtemp, mkdir, writeFile, realpath, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; const root = await mkdtemp(join(tmpdir(), "rea-relative-input-")); const app = join(root, "app"); const home = join(root, "home"); await mkdir(app); await mkdir(home); await writeFile(join(app, "main.js"), "export const value = 1;\n"); const client = new Client({ name: "relative-input-check", version: "1" }); const transport = new StdioClientTransport({ command: process.execPath, args: [resolve("scripts/rea.mjs"), "mcp"], cwd: root, env: { PATH: process.env.PATH ?? "", HOME: home }, stderr: "pipe", }); transport.stderr?.on("data", () => {}); try { await client.connect(transport); const responses = []; for (const input_path of ["app", app]) { const response = await client.callTool({ name: "analyze_javascript_application", arguments: { input_path, format: "directory" }, }); const evidence = response.structuredContent; responses.push(evidence); console.log(JSON.stringify({ relative_input: input_path === "app", isError: response.isError ?? false, provider: evidence?.provider?.id, operation: evidence?.operation, selected_server_app: evidence?.normalized_result?.input_path === await realpath(app), parsed_javascript_files: evidence?.normalized_result?.statistics?.parsed_javascript_files, })); } console.log(JSON.stringify({ same_evidence: responses[0]?.evidence_id === responses[1]?.evidence_id })); } finally { await client.close(); await transport.close(); await rm(root, { recursive: true, force: true }); } NODE ``` The first call is the failing case: `{"input_path":"app","format":"directory"}`. The second call uses the generated absolute path as a control. The script prints the relevant result fields and removes its fixture after closing the MCP connection. ### Expected behavior Reject relative `input_path` values with an input error identifying the absolute-path requirement, without analyzing server-relative content. Keep the absolute-path control successful. `docs/javascript-artifact-reconstruction.md:40-56`, especially line 50 immediately after the MCP example, explicitly requires an absolute `input_path`. ### Impact and scope P2. A relative MCP input violates the documented absolute-path contract and selects content according to the server working directory. The successful analysis was reproduced. Selection of a different unintended artifact is a source-derived consequence, not an observed incident. No privilege boundary was crossed. The absolute-path control succeeded and is an observed practical workaround. ### REA version rea 6.5.0 at source commit 4692bb33300c7cb77b095b6cc0c33da861c1b860 ### Runtime and platform Node 24.18.0; macOS arm64 ### Target and artifact context Disposable local directory containing only `app/main.js` with inert JavaScript; production stdio MCP server with a controlled working directory and isolated HOME. ### Interface MCP ### Earliest failure stage Request parsing or validation ### Relevant output or evidence Both calls returned successful Evidence, selected the canonical path of the server's `app` directory, and parsed one JavaScript file. They returned the same Evidence ID. The script printed: ```json {"relative_input":true,"isError":false,"provider":"rea-javascript-application","operation":"analyze_javascript_application","selected_server_app":true,"parsed_javascript_files":1} {"relative_input":false,"isError":false,"provider":"rea-javascript-application","operation":"analyze_javascript_application","selected_server_app":true,"parsed_javascript_files":1} {"same_evidence":true} ``` A separate observed call with `input_path: "missing-app"` returned an `invalid_request` error with category `invalid_input`, issue reason `invalid_value`, and the message `No file or directory exists at the selected input path (ENOENT): missing-app`. It did not identify the relative-path violation. These observations show that the same unsupported path shape is accepted or treated as missing according to the server's filesystem state. Source-derived reasoning explains the result: the registered tool's input schema admits nonempty relative strings, and the shared workflow resolves the selected string with `realpath` before checking its format. A same-named directory under a different server working directory can therefore select a different artifact. This consequence was not tested with a second artifact and is not an observed wrong-target incident. ### Typed outcome or recovery expected Run the same relative request with the server-relative `app` fixture present. It must return an input error identifying `input_path` and explaining that an absolute path is required, without returning successful analysis Evidence. Repeat with `missing-app`; it must receive the same path-shape correction rather than an existence-dependent error. Keep the absolute-path control successful and confirm it still selects the fixture and parses its JavaScript file. These checks restore the documented MCP contract without prescribing a particular validation layer or changing the CLI's path policy. ### Analysis or suspected cause Source-derived: the public input schema uses `localPathStringSchema`, which rejects empty strings and NUL bytes but does not require an absolute path. See `src/contracts/javascript/electronToolContracts.ts:34-38,92-98`, `src/domain/javascript/javascriptApplicationAnalysis.ts:27-33`, and `src/domain/localPath.ts:15-20`. Reconstruction uses a nonempty string schema at `src/application/javascript/JavaScriptArtifactReconstructionInput.ts:7-12`, then `realpath(inputPath)` at `src/application/javascript/JavaScriptArtifactReconstruction.ts:330-335`. The handler forwards admitted input at `src/server/registerElectronTools.ts:99-117`. ### Additional context The cited failing code is unchanged on current `origin/main` at `22ec8bdb2f7d4626587f23b1eed4a207df600386`. The expectation is confined to MCP; this report does not impose a relative-path rejection policy on the CLI.
Author
Owner

@XinwangTao commented on GitHub (Oct 15, 2026):

Confirmed: accepting a relative input_path silently makes analysis depend on the MCP server working directory, which violates the documented absolute-path contract and makes results non-reproducible across launchers. Either reject non-absolute paths with a clear diagnostic, or explicitly resolve them to a deterministic location and surface that resolution in the response.

<!-- gh-comment-id:6186426064 --> @XinwangTao commented on GitHub (Oct 15, 2026): Confirmed: accepting a relative input_path silently makes analysis depend on the MCP server working directory, which violates the documented absolute-path contract and makes results non-reproducible across launchers. Either reject non-absolute paths with a clear diagnostic, or explicitly resolve them to a deterministic location and surface that resolution in the response.
Author
Owner

@yankikucuk commented on GitHub (Oct 15, 2026):

Working on this. Reproduced through the stdio MCP server with a controlled working directory: input_path: "app" analyzed the server-relative directory successfully and missing-app returned an existence-dependent ENOENT. The MCP request contract now requires an absolute input_path (same pattern as the other absolute-path tool inputs), so both relative forms return an input validation error naming input_path and the absolute-path requirement, while the absolute control still selects the fixture and parses its file. The CLI route is unchanged; it resolves its path argument before analysis. Draft PR follows shortly.

<!-- gh-comment-id:6189918223 --> @yankikucuk commented on GitHub (Oct 15, 2026): Working on this. Reproduced through the stdio MCP server with a controlled working directory: `input_path: "app"` analyzed the server-relative directory successfully and `missing-app` returned an existence-dependent ENOENT. The MCP request contract now requires an absolute `input_path` (same pattern as the other absolute-path tool inputs), so both relative forms return an input validation error naming `input_path` and the absolute-path requirement, while the absolute control still selects the fixture and parses its file. The CLI route is unchanged; it resolves its path argument before analysis. Draft PR follows shortly.
Author
Owner

@coygeek commented on GitHub (Oct 24, 2026):

Checked the reported fix on main at d87e73f2db.

Both MCP detail branches now require absoluteLocalPathStringSchema for input_path, and the registered contract uses it before request execution. app and missing-app fail identically at input_path with an absolute-filesystem-path message; an absolute control passes admission. The in-memory MCP regression includes this operation, asserts rejection containing absolute, and advertised-schema checks accept absolute/reject relative.

Implementation: electronToolContracts.ts, localPath.ts.

The relevant regression checks in absoluteLocalPathSchema.test.ts, applicationWorkflowCli.test.ts passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.

<!-- gh-comment-id:6380731346 --> @coygeek commented on GitHub (Oct 24, 2026): Checked the reported fix on `main` at d87e73f2db50be72d33a4b3c7199af35518b441c. Both MCP detail branches now require absoluteLocalPathStringSchema for input_path, and the registered contract uses it before request execution. app and missing-app fail identically at input_path with an absolute-filesystem-path message; an absolute control passes admission. The in-memory MCP regression includes this operation, asserts rejection containing absolute, and advertised-schema checks accept absolute/reject relative. Implementation: [electronToolContracts.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/contracts/javascript/electronToolContracts.ts#L39), [localPath.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/localPath.ts#L23). The relevant regression checks in [absoluteLocalPathSchema.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/tests/boundary/mcp/absoluteLocalPathSchema.test.ts), [applicationWorkflowCli.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/tests/acceptance/applications/applicationWorkflowCli.test.ts) passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#1533
No description provided.