[GH-ISSUE #3402] [Bug] Equivalent WASM MCP path spelling introduces false glue ambiguity #1269

Closed
opened 2026-10-16 21:03:34 +00:00 by bsakaguchi · 1 comment
Owner

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

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

inspect_wasm_artifact changes a unique relative JavaScript glue association into a basename ambiguity when the caller spells the selected absolute module path with /./. The result adds a different selected file as a possible match even though the glue literal resolves to the original selected file. The canonical spelling of the same input returns the unique local association.

Both public stdio MCP calls succeed. The selected module and glue bytes remain identical between the calls. The same failure was observed first with a module containing imports, exports, and a custom section, then with the reduced empty module below.

Reproduction

  1. Start from a clean checkout of https://github.com/morluto/rea at revision 8dde6370d0809ad04cda3342d4e6f290f48aa063. From the repository root, install the locked dependencies and build the runtime:
npm ci
npm run build:cached
  1. Run the following from the repository root. The script calls the public stdio MCP tool twice, once per spelling. It creates two valid empty core modules and a glue file containing fetch("./module.wasm"). Executable tool fakes provide the accepted WABT version banner, empty-module section output, and WAT output. This isolates the production MCP, application, stable file acquisition, and glue-association behavior; it does not verify the real WABT engine. WebAssembly.validate independently checks the fixture bytes without executing a module. The fixture directory must not already exist, and the script removes its fixtures on exit.
work_dir="$(mktemp -d)"
trap 'rm -rf "$work_dir"' EXIT
cat > "$work_dir/reproduce.mjs" <<'EOF'
import { createRequire } from "node:module";
import { pathToFileURL } from "node:url";
import { join, resolve } from "node:path";
import { mkdir, rm, writeFile } from "node:fs/promises";

// Run from the REA repository root and pass an absent fixture directory.
// The directory must not already exist. This script removes its fixtures on exit.
// Executable fakes supply an empty-module WABT output profile.
// This checks the production MCP/application/glue path, not the real WABT engine.
const repo = process.cwd();
const fixtureDirectory = process.argv[2];
if (!fixtureDirectory) {
  throw new Error("Pass a new caller-chosen fixture directory");
}
const workspace = resolve(repo, fixtureDirectory);
const requireFromRepo = createRequire(join(repo, "package.json"));
const { Client } = await import(pathToFileURL(requireFromRepo.resolve("@modelcontextprotocol/client")).href);
const { StdioClientTransport } = await import(pathToFileURL(requireFromRepo.resolve("@modelcontextprotocol/client/stdio")).href);
const objdump = "\nmodule.wasm:\tfile format wasm 0x1\n\nSections:\n\n\nSection Details:\n";
const decodedWat = "(module)\n";
const moduleBytes = Buffer.from("0061736d01000000", "hex");
if (!WebAssembly.validate(moduleBytes)) throw new Error("Fixture is not a valid core WASM module");
await mkdir(workspace);
let client;
try {
  for (const directory of ["tools", "app", "other", "config", "cache"]) {
    await mkdir(join(workspace, directory));
  }
  for (const directory of ["app", "other"]) {
    await writeFile(join(workspace, directory, "module.wasm"), moduleBytes);
  }
  await writeFile(join(workspace, "app", "glue.js"), 'fetch("./module.wasm");\n');
  for (const tool of ["wasm-validate", "wasm-objdump", "wasm2wat"]) {
    const text = tool === "wasm-objdump" ? objdump : tool === "wasm2wat" ? decodedWat : "";
    const script = '#!/usr/bin/env node\nif (process.argv[2] === "--version") process.stdout.write("1.0.42\\n"); else process.stdout.write(' + JSON.stringify(text) + ');\n';
    await writeFile(join(workspace, "tools", tool), script, { mode: 0o700 });
  }
  const env = {
    PATH: process.env.PATH,
    REA_WABT_BIN_DIRECTORY: join(workspace, "tools"),
    REA_IDA_MCP_CONFIG: join(workspace, "no-ida-config.json"),
    XDG_CONFIG_HOME: join(workspace, "config"),
    XDG_CACHE_HOME: join(workspace, "cache"),
    REA_LOG_LEVEL: "silent",
  };
  client = new Client({ name: "wasm-glue-contract-check", version: "1" });
  const transport = new StdioClientTransport({
    command: process.execPath,
    args: [join(repo, "scripts", "rea.mjs"), "mcp"],
    env,
    stderr: "pipe",
  });
  await client.connect(transport);
  for (const selectedSpelling of ["app/module.wasm", "app/./module.wasm"]) {
    // Concatenate deliberately: join/resolve would remove the second input's /./.
    const reply = await client.callTool({
      name: "inspect_wasm_artifact",
      arguments: {
        path: workspace + "/" + selectedSpelling,
        glue_paths: [join(workspace, "app", "glue.js")],
        candidate_paths: [join(workspace, "other", "module.wasm")],
      },
    });
    if (reply.isError || !reply.structuredContent?.normalized_result?.glue) {
      throw new Error(JSON.stringify(reply));
    }
    const reference = reply.structuredContent.normalized_result.glue[0].references[0];
    console.log(JSON.stringify({
      selected_spelling: selectedSpelling,
      value: reference.value,
      association: reference.association,
      candidate_paths: reference.candidate_paths.map((path) => path.slice(workspace.length + 1)),
    }));
  }
} finally {
  if (client) await client.close();
  await rm(workspace, { recursive: true, force: true });
}
EOF
node "$work_dir/reproduce.mjs" "$work_dir/files"
  1. Compare the two printed association records. The canonical spelling has only the selected module as a local-path candidate; adding /./ introduces the unrelated module as a basename candidate.

Expected behavior

Both accepted absolute spellings select the same file. The literal ./module.wasm resolves to that file relative to the selected glue file, so both results should report association: "local-path-candidate" with only the selected module. Evidence should retain the caller's spelling in the returned candidate path. The other module is a basename match only and should not displace an existing local-path match.

docs/wasm-artifacts.md:89 through docs/wasm-artifacts.md:99 distinguishes relative filesystem matches from basename candidates. src/application/wasm/WasmArtifactService.ts:45 through src/application/wasm/WasmArtifactService.ts:68 accepts the reproduced absolute path spellings.

Impact and scope

Impact level: P2, supported. Valid MCP input introduces a false additional candidate and loses a unique supported association, which can mislead follow-up reconstruction analysis. The observed defect is in association handling; actual WABT compatibility was not exercised.

Workaround status: available. Supplying the canonical absolute spelling returns the unique local-path association in the observed control without modifying the selected file or its digest.

REA version

6.4.0 (main at 8dde6370d0)

Runtime and platform

Node.js v26.10.0 on macOS 27.0.1; compiled source-checkout runtime. Architecture was not recorded in the supplied reproduction evidence.

Target and artifact context

Two caller-selected valid empty WebAssembly core modules named module.wasm in separate app and other directories, plus app/glue.js containing the static literal ./module.wasm. The module bytes are 0061736d01000000. Executable WABT tool fakes report version 1.0.42 and supply empty-module output; no real WABT engine or target module was executed. The earlier observed fixture also contained imports, exports, and a custom section.

Interface

MCP

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

The public MCP reproduction printed these observations, with fixture-root prefixes removed only for display:

{"selected_spelling":"app/module.wasm","value":"./module.wasm","association":"local-path-candidate","candidate_paths":["app/module.wasm"]}
{"selected_spelling":"app/./module.wasm","value":"./module.wasm","association":"basename-candidates","candidate_paths":["app/./module.wasm","other/module.wasm"]}

The failure evidence is observed, local verification reproduced it, and the reproduction is complete.

Typed outcome or recovery expected

Run the script again. Both spellings must retain a unique local-path association to the selected module while preserving each original path spelling in Evidence. A literal without a selected local-path match should still report all applicable basename candidates, so restoration must preserve documented ambiguity when no local match exists.

Analysis or suspected cause

Source-derived reasoning: src/wasm/WasmGlueAdapter.ts:44 through src/wasm/WasmGlueAdapter.ts:54 compares the preserved candidate spelling with a resolved glue-relative path, then falls back to basename matching. A selected path containing /./ differs textually from that resolved path even though it selects the same file. This explains the observed loss of the local-path match and addition of the other basename candidate. The production MCP observations above establish the failure; this source comparison explains the suspected mechanism.

Additional context

Related completed report #2618 concerns correction metadata naming the primary path when a relative glue or candidate path is rejected; this report concerns incorrect successful association results for accepted absolute paths.

Related completed feature #884 introduced WABT inspection and static glue associations; this report identifies an equivalent-path-spelling defect in those associations.

Related open report #3307 concerns empty URL path segments selecting the wrong ESM or HTML dependency file; this report concerns local filesystem glue matching for the same selected WASM file.

Related open report #3321 concerns trailing app-bundle separators changing the CLI analysis lane; this report concerns glue-reference matching after successful MCP selection.

Originally created by @coygeek on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3402 ### 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 `inspect_wasm_artifact` changes a unique relative JavaScript glue association into a basename ambiguity when the caller spells the selected absolute module path with `/./`. The result adds a different selected file as a possible match even though the glue literal resolves to the original selected file. The canonical spelling of the same input returns the unique local association. Both public stdio MCP calls succeed. The selected module and glue bytes remain identical between the calls. The same failure was observed first with a module containing imports, exports, and a custom section, then with the reduced empty module below. ### Reproduction 1. Start from a clean checkout of `https://github.com/morluto/rea` at revision `8dde6370d0809ad04cda3342d4e6f290f48aa063`. From the repository root, install the locked dependencies and build the runtime: ```sh npm ci npm run build:cached ``` 2. Run the following from the repository root. The script calls the public stdio MCP tool twice, once per spelling. It creates two valid empty core modules and a glue file containing `fetch("./module.wasm")`. Executable tool fakes provide the accepted WABT version banner, empty-module section output, and WAT output. This isolates the production MCP, application, stable file acquisition, and glue-association behavior; it does not verify the real WABT engine. `WebAssembly.validate` independently checks the fixture bytes without executing a module. The fixture directory must not already exist, and the script removes its fixtures on exit. ```sh work_dir="$(mktemp -d)" trap 'rm -rf "$work_dir"' EXIT cat > "$work_dir/reproduce.mjs" <<'EOF' import { createRequire } from "node:module"; import { pathToFileURL } from "node:url"; import { join, resolve } from "node:path"; import { mkdir, rm, writeFile } from "node:fs/promises"; // Run from the REA repository root and pass an absent fixture directory. // The directory must not already exist. This script removes its fixtures on exit. // Executable fakes supply an empty-module WABT output profile. // This checks the production MCP/application/glue path, not the real WABT engine. const repo = process.cwd(); const fixtureDirectory = process.argv[2]; if (!fixtureDirectory) { throw new Error("Pass a new caller-chosen fixture directory"); } const workspace = resolve(repo, fixtureDirectory); const requireFromRepo = createRequire(join(repo, "package.json")); const { Client } = await import(pathToFileURL(requireFromRepo.resolve("@modelcontextprotocol/client")).href); const { StdioClientTransport } = await import(pathToFileURL(requireFromRepo.resolve("@modelcontextprotocol/client/stdio")).href); const objdump = "\nmodule.wasm:\tfile format wasm 0x1\n\nSections:\n\n\nSection Details:\n"; const decodedWat = "(module)\n"; const moduleBytes = Buffer.from("0061736d01000000", "hex"); if (!WebAssembly.validate(moduleBytes)) throw new Error("Fixture is not a valid core WASM module"); await mkdir(workspace); let client; try { for (const directory of ["tools", "app", "other", "config", "cache"]) { await mkdir(join(workspace, directory)); } for (const directory of ["app", "other"]) { await writeFile(join(workspace, directory, "module.wasm"), moduleBytes); } await writeFile(join(workspace, "app", "glue.js"), 'fetch("./module.wasm");\n'); for (const tool of ["wasm-validate", "wasm-objdump", "wasm2wat"]) { const text = tool === "wasm-objdump" ? objdump : tool === "wasm2wat" ? decodedWat : ""; const script = '#!/usr/bin/env node\nif (process.argv[2] === "--version") process.stdout.write("1.0.42\\n"); else process.stdout.write(' + JSON.stringify(text) + ');\n'; await writeFile(join(workspace, "tools", tool), script, { mode: 0o700 }); } const env = { PATH: process.env.PATH, REA_WABT_BIN_DIRECTORY: join(workspace, "tools"), REA_IDA_MCP_CONFIG: join(workspace, "no-ida-config.json"), XDG_CONFIG_HOME: join(workspace, "config"), XDG_CACHE_HOME: join(workspace, "cache"), REA_LOG_LEVEL: "silent", }; client = new Client({ name: "wasm-glue-contract-check", version: "1" }); const transport = new StdioClientTransport({ command: process.execPath, args: [join(repo, "scripts", "rea.mjs"), "mcp"], env, stderr: "pipe", }); await client.connect(transport); for (const selectedSpelling of ["app/module.wasm", "app/./module.wasm"]) { // Concatenate deliberately: join/resolve would remove the second input's /./. const reply = await client.callTool({ name: "inspect_wasm_artifact", arguments: { path: workspace + "/" + selectedSpelling, glue_paths: [join(workspace, "app", "glue.js")], candidate_paths: [join(workspace, "other", "module.wasm")], }, }); if (reply.isError || !reply.structuredContent?.normalized_result?.glue) { throw new Error(JSON.stringify(reply)); } const reference = reply.structuredContent.normalized_result.glue[0].references[0]; console.log(JSON.stringify({ selected_spelling: selectedSpelling, value: reference.value, association: reference.association, candidate_paths: reference.candidate_paths.map((path) => path.slice(workspace.length + 1)), })); } } finally { if (client) await client.close(); await rm(workspace, { recursive: true, force: true }); } EOF node "$work_dir/reproduce.mjs" "$work_dir/files" ``` 3. Compare the two printed association records. The canonical spelling has only the selected module as a local-path candidate; adding `/./` introduces the unrelated module as a basename candidate. ### Expected behavior Both accepted absolute spellings select the same file. The literal `./module.wasm` resolves to that file relative to the selected glue file, so both results should report `association: "local-path-candidate"` with only the selected module. Evidence should retain the caller's spelling in the returned candidate path. The other module is a basename match only and should not displace an existing local-path match. `docs/wasm-artifacts.md:89` through `docs/wasm-artifacts.md:99` distinguishes relative filesystem matches from basename candidates. `src/application/wasm/WasmArtifactService.ts:45` through `src/application/wasm/WasmArtifactService.ts:68` accepts the reproduced absolute path spellings. ### Impact and scope Impact level: P2, supported. Valid MCP input introduces a false additional candidate and loses a unique supported association, which can mislead follow-up reconstruction analysis. The observed defect is in association handling; actual WABT compatibility was not exercised. Workaround status: available. Supplying the canonical absolute spelling returns the unique local-path association in the observed control without modifying the selected file or its digest. ### REA version 6.4.0 (main at 8dde6370d0809ad04cda3342d4e6f290f48aa063) ### Runtime and platform Node.js v26.10.0 on macOS 27.0.1; compiled source-checkout runtime. Architecture was not recorded in the supplied reproduction evidence. ### Target and artifact context Two caller-selected valid empty WebAssembly core modules named `module.wasm` in separate `app` and `other` directories, plus `app/glue.js` containing the static literal `./module.wasm`. The module bytes are `0061736d01000000`. Executable WABT tool fakes report version 1.0.42 and supply empty-module output; no real WABT engine or target module was executed. The earlier observed fixture also contained imports, exports, and a custom section. ### Interface MCP ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence The public MCP reproduction printed these observations, with fixture-root prefixes removed only for display: ```text {"selected_spelling":"app/module.wasm","value":"./module.wasm","association":"local-path-candidate","candidate_paths":["app/module.wasm"]} {"selected_spelling":"app/./module.wasm","value":"./module.wasm","association":"basename-candidates","candidate_paths":["app/./module.wasm","other/module.wasm"]} ``` The failure evidence is observed, local verification reproduced it, and the reproduction is complete. ### Typed outcome or recovery expected Run the script again. Both spellings must retain a unique local-path association to the selected module while preserving each original path spelling in Evidence. A literal without a selected local-path match should still report all applicable basename candidates, so restoration must preserve documented ambiguity when no local match exists. ### Analysis or suspected cause Source-derived reasoning: `src/wasm/WasmGlueAdapter.ts:44` through `src/wasm/WasmGlueAdapter.ts:54` compares the preserved candidate spelling with a resolved glue-relative path, then falls back to basename matching. A selected path containing `/./` differs textually from that resolved path even though it selects the same file. This explains the observed loss of the local-path match and addition of the other basename candidate. The production MCP observations above establish the failure; this source comparison explains the suspected mechanism. ### Additional context Related completed report [#2618](https://github.com/morluto/rea/issues/2618) concerns correction metadata naming the primary path when a relative glue or candidate path is rejected; this report concerns incorrect successful association results for accepted absolute paths. Related completed feature [#884](https://github.com/morluto/rea/issues/884) introduced WABT inspection and static glue associations; this report identifies an equivalent-path-spelling defect in those associations. Related open report [#3307](https://github.com/morluto/rea/issues/3307) concerns empty URL path segments selecting the wrong ESM or HTML dependency file; this report concerns local filesystem glue matching for the same selected WASM file. Related open report [#3321](https://github.com/morluto/rea/issues/3321) concerns trailing app-bundle separators changing the CLI analysis lane; this report concerns glue-reference matching after successful MCP selection.
Author
Owner

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

Addressed on main by github.com/morluto/rea@fc791bb606. WASM glue matching now compares both the glue-relative target and selected candidates through absoluteCallerPath, which removes interior dot segments while retaining each candidate spelling in the returned association. A unique local match therefore takes precedence over basename candidates for /./ spellings.

I checked the current source and relevant regression coverage; I have not rerun the original runtime reproduction in this review. Closing as completed.

<!-- gh-comment-id:6244129745 --> @coygeek commented on GitHub (Oct 18, 2026): Addressed on main by https://github.com/morluto/rea/commit/fc791bb606d56d0b5c986237886b9eb59c425825. WASM glue matching now compares both the glue-relative target and selected candidates through absoluteCallerPath, which removes interior dot segments while retaining each candidate spelling in the returned association. A unique local match therefore takes precedence over basename candidates for /./ spellings. I checked the current source and relevant regression coverage; I have not rerun the original runtime reproduction in this review. Closing as completed.
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#1269
No description provided.