[GH-ISSUE #5219] [Bug] Explicit ABI selection rejects a label reported and inspectable by default #3196

Closed
opened 2026-10-31 00:37:44 +00:00 by bsakaguchi · 2 comments
Owner

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

Before submitting

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

Area

MCP or agent integration

Observed behavior

identify_flutter_build reports ARM64-V8A from a synthetic APK, and inspect_dart_aot can inspect that payload by default when it is the sole library. Explicitly requesting the same label returns an MCP input-validation error before the Flutter provider executes. In a paired APK, omission selects a different x86_64 payload instead.

Reproduction

With the source revision below and its lockfile dependencies installed, save this complete test as tests/boundary/mcp/reportedAbiSelection.test.ts. It uses the existing APK/ELF fixtures, real Flutter provider and production MCP registration over linked in-memory SDK transports; the binary-session fixture is inert.

import { expect, it, onTestFinished, vi } from "vitest";
import { createFlutterBuildAnalysisProvider } from "../../../src/composition/flutter.js";
import { parseEvidence } from "../../../src/domain/evidence.js";
import { flutterResultSchemas } from "../../../src/domain/flutter/flutterBuildAnalysis.js";
import { aotFixtureImage } from "../../../src/flutter/FlutterBuild.fixture.js";
import { createFlutterApk } from "../../fixtures/flutterApk.js";
import { createApplicationMcpHarness } from "../../fixtures/applicationMcpHarness.js";

it("reproduces explicit selection of a reported ABI", async () => {
  const provider = createFlutterBuildAnalysisProvider();
  const execution = vi.spyOn(provider, "execute");
  onTestFinished(() => provider.close());
  onTestFinished(() => execution.mockRestore());
  const harness = await createApplicationMcpHarness({ serverOptions: { flutterAnalysis: provider } });
  onTestFinished(harness.close);
  for (const paired of [false, true]) {
    const files = [{ path: "lib/ARM64-V8A/libapp.so", bytes: aotFixtureImage("a".repeat(32)) }];
    if (paired) files.push({ path: "lib/x86_64/libapp.so", bytes: aotFixtureImage("b".repeat(32)) });
    const path = await createFlutterApk(paired ? "paired.apk" : "single.apk", files);
    const call = (name: string, abi?: string) => harness.client.callTool({ name, arguments: { path, ...(abi === undefined ? {} : { abi }) } });
    const identified = await call("identify_flutter_build");
    expect(identified.isError).not.toBe(true);
    const inventory = flutterResultSchemas.identify_flutter_build.parse(parseEvidence(identified.structuredContent).normalized_result);
    expect(inventory.abis.map(row => row.abi)).toEqual(paired ? ["ARM64-V8A", "x86_64"] : ["ARM64-V8A"]);
    const before = execution.mock.calls.length;
    const explicit = await call("inspect_dart_aot", "ARM64-V8A");
    expect(explicit.isError).toBe(true);
    expect(explicit.content).toEqual([{ type: "text", text: "Input validation error: Invalid arguments for tool inspect_dart_aot: abi: ABI name as it appears under lib/, e.g. arm64-v8a" }]);
    expect(execution.mock.calls.length).toBe(before);
    const defaultReply = await call("inspect_dart_aot");
    expect(defaultReply.isError).not.toBe(true);
    const facts = flutterResultSchemas.inspect_dart_aot.parse(parseEvidence(defaultReply.structuredContent).normalized_result);
    expect(facts.abi).toBe(paired ? "x86_64" : "ARM64-V8A");
    expect(facts.libapp.snapshot_hash).toBe((paired ? "b" : "a").repeat(32));
    expect(facts.libapp.coverage).toBe("complete");
    if (paired) {
      const control = await call("inspect_dart_aot", "x86_64");
      expect(control.isError).not.toBe(true);
      const selected = flutterResultSchemas.inspect_dart_aot.parse(parseEvidence(control.structuredContent).normalized_result);
      expect(selected.abi).toBe("x86_64");
      expect(selected.libapp.snapshot_hash).toBe("b".repeat(32));
    }
    console.log(JSON.stringify({ paired, reported: inventory.abis.map(row => row.abi), explicitRejected: explicit.isError, defaultAbi: facts.abi, defaultHash: facts.libapp.snapshot_hash }));
  }
});

Run from the repository root with its pinned Node.js toolchain:

case_dir=$(mktemp -d)
mkdir -p "$case_dir/runtime" "$case_dir/home" "$case_dir/config" "$case_dir/cache"
TMPDIR="$case_dir/runtime" HOME="$case_dir/home" XDG_CONFIG_HOME="$case_dir/config" XDG_CACHE_HOME="$case_dir/cache" \
  node node_modules/vitest/vitest.mjs run --project mcp-boundary tests/boundary/mcp/reportedAbiSelection.test.ts --maxWorkers=1
rm tests/boundary/mcp/reportedAbiSelection.test.ts

The inline test executed successfully: one file, one test, seven MCP requests. Its passing assertions establish the defect and controls, not a fix. Explicit uppercase selection failed for both fixtures; default inspection succeeded for both; explicit x86_64 succeeded for the paired fixture. A separate retained six-request test also reproduced the same refusal for AB, with successful arm64-v8a controls.

Expected behavior

Explicitly selecting an identified, default-inspectable directory label should inspect that same payload and preserve its identity. The Flutter workflow documents an optional ABI selector, default selection including remaining ABIs, and preservation of caller choice.

Impact and scope

Selection of parser-supported nonstandard labels fails. The paired fixture proves omission does not preserve the intended payload. Standard-label controls work; population frequency is unknown. These are synthetic parsing inputs, not a claim that uppercase labels are standard Android packaging or that the APKs are installable.

REA version

Source runtime: 39275420ea18ad803eccfcc2702865b98ed36e44, also the current default-branch HEAD when checked. Latest release rea-agents-6.7.0 at 27dc42d10249f0410904a75c0997842eb03b931c has identical affected contracts, provider, adapters, fixtures and tests by source comparison; release runtime was not executed.

Runtime and platform

Node.js 24.18.0; npm 11.16.0; Vitest 5.0.3; macOS 27.0.1 arm64. No Hopper, Ghidra, device or target execution.

Target and artifact context

Synthetic ZIP APKs contain the repository's ELF64 x86_64 AOT images with distinct a/b snapshot hashes. Directory labels do not change ELF machine type. The inputs omit libflutter.so and an Android manifest. Identification coverage is partial because engine libraries are absent; successful AOT controls report complete structural library coverage.

Interface

MCP

Earliest failure stage

Request parsing or validation

Relevant output or evidence

Both uppercase requests returned isError: true and exactly:

Input validation error: Invalid arguments for tool inspect_dart_aot: abi: ABI name as it appears under lib/, e.g. arm64-v8a

The provider spy observed no dispatch for either rejection. Single-library default inspection returned ABI ARM64-V8A and snapshot hash aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; paired default and explicit x86_64 returned bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb. The exact inline test was replayed against the source revision above; separate retained verification independently observed these outcomes. Compiled CLI, standalone stdio MCP and release-runtime reproduction were not run.

Typed outcome or recovery expected

For the paired fixture, explicit ARM64-V8A should succeed with its original label, a hash and complete structural library coverage. Default and explicit x86_64 should retain the b payload. The single-library fixture should give the same payload for explicit and default selection. Suppressing the reported identity or selecting another payload does not restore caller choice.

Analysis or suspected cause

Source-derived: discovery retains directory labels, and provider selection matches the supplied label exactly. The earlier canonical input schema restricts the selector to lowercase letters, digits, underscores and hyphens, rejecting these reported identities before dispatch.

Additional context

Single-character, overlong, punctuation and dot-segment variants were not tested.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: Explicit selection fails for reported, default-inspectable nonstandard labels; standard-name controls succeed. No severe harm or corrupted analysis was observed, and population frequency is unknown.
  • Workaround status: unverified
  • Workaround basis: Omitting the selector works for the sole-library fixture but selects a different payload in the paired fixture. No practical general workaround preserving the intended selection and archive identity was verified.
Originally created by @coygeek on GitHub (Oct 19, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5219 ### 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 MCP or agent integration ### Observed behavior `identify_flutter_build` reports `ARM64-V8A` from a synthetic APK, and `inspect_dart_aot` can inspect that payload by default when it is the sole library. Explicitly requesting the same label returns an MCP input-validation error before the Flutter provider executes. In a paired APK, omission selects a different `x86_64` payload instead. ### Reproduction With the source revision below and its lockfile dependencies installed, save this complete test as `tests/boundary/mcp/reportedAbiSelection.test.ts`. It uses the existing APK/ELF fixtures, real Flutter provider and production MCP registration over linked in-memory SDK transports; the binary-session fixture is inert. ```ts import { expect, it, onTestFinished, vi } from "vitest"; import { createFlutterBuildAnalysisProvider } from "../../../src/composition/flutter.js"; import { parseEvidence } from "../../../src/domain/evidence.js"; import { flutterResultSchemas } from "../../../src/domain/flutter/flutterBuildAnalysis.js"; import { aotFixtureImage } from "../../../src/flutter/FlutterBuild.fixture.js"; import { createFlutterApk } from "../../fixtures/flutterApk.js"; import { createApplicationMcpHarness } from "../../fixtures/applicationMcpHarness.js"; it("reproduces explicit selection of a reported ABI", async () => { const provider = createFlutterBuildAnalysisProvider(); const execution = vi.spyOn(provider, "execute"); onTestFinished(() => provider.close()); onTestFinished(() => execution.mockRestore()); const harness = await createApplicationMcpHarness({ serverOptions: { flutterAnalysis: provider } }); onTestFinished(harness.close); for (const paired of [false, true]) { const files = [{ path: "lib/ARM64-V8A/libapp.so", bytes: aotFixtureImage("a".repeat(32)) }]; if (paired) files.push({ path: "lib/x86_64/libapp.so", bytes: aotFixtureImage("b".repeat(32)) }); const path = await createFlutterApk(paired ? "paired.apk" : "single.apk", files); const call = (name: string, abi?: string) => harness.client.callTool({ name, arguments: { path, ...(abi === undefined ? {} : { abi }) } }); const identified = await call("identify_flutter_build"); expect(identified.isError).not.toBe(true); const inventory = flutterResultSchemas.identify_flutter_build.parse(parseEvidence(identified.structuredContent).normalized_result); expect(inventory.abis.map(row => row.abi)).toEqual(paired ? ["ARM64-V8A", "x86_64"] : ["ARM64-V8A"]); const before = execution.mock.calls.length; const explicit = await call("inspect_dart_aot", "ARM64-V8A"); expect(explicit.isError).toBe(true); expect(explicit.content).toEqual([{ type: "text", text: "Input validation error: Invalid arguments for tool inspect_dart_aot: abi: ABI name as it appears under lib/, e.g. arm64-v8a" }]); expect(execution.mock.calls.length).toBe(before); const defaultReply = await call("inspect_dart_aot"); expect(defaultReply.isError).not.toBe(true); const facts = flutterResultSchemas.inspect_dart_aot.parse(parseEvidence(defaultReply.structuredContent).normalized_result); expect(facts.abi).toBe(paired ? "x86_64" : "ARM64-V8A"); expect(facts.libapp.snapshot_hash).toBe((paired ? "b" : "a").repeat(32)); expect(facts.libapp.coverage).toBe("complete"); if (paired) { const control = await call("inspect_dart_aot", "x86_64"); expect(control.isError).not.toBe(true); const selected = flutterResultSchemas.inspect_dart_aot.parse(parseEvidence(control.structuredContent).normalized_result); expect(selected.abi).toBe("x86_64"); expect(selected.libapp.snapshot_hash).toBe("b".repeat(32)); } console.log(JSON.stringify({ paired, reported: inventory.abis.map(row => row.abi), explicitRejected: explicit.isError, defaultAbi: facts.abi, defaultHash: facts.libapp.snapshot_hash })); } }); ``` Run from the repository root with its pinned Node.js toolchain: ```sh case_dir=$(mktemp -d) mkdir -p "$case_dir/runtime" "$case_dir/home" "$case_dir/config" "$case_dir/cache" TMPDIR="$case_dir/runtime" HOME="$case_dir/home" XDG_CONFIG_HOME="$case_dir/config" XDG_CACHE_HOME="$case_dir/cache" \ node node_modules/vitest/vitest.mjs run --project mcp-boundary tests/boundary/mcp/reportedAbiSelection.test.ts --maxWorkers=1 rm tests/boundary/mcp/reportedAbiSelection.test.ts ``` The inline test executed successfully: one file, one test, seven MCP requests. Its passing assertions establish the defect and controls, not a fix. Explicit uppercase selection failed for both fixtures; default inspection succeeded for both; explicit `x86_64` succeeded for the paired fixture. A separate retained six-request test also reproduced the same refusal for `AB`, with successful `arm64-v8a` controls. ### Expected behavior Explicitly selecting an identified, default-inspectable directory label should inspect that same payload and preserve its identity. The [Flutter workflow](https://github.com/morluto/rea/blob/39275420ea18ad803eccfcc2702865b98ed36e44/docs/flutter-analysis.md#L23-L35) documents an optional ABI selector, default selection including remaining ABIs, and preservation of caller choice. ### Impact and scope Selection of parser-supported nonstandard labels fails. The paired fixture proves omission does not preserve the intended payload. Standard-label controls work; population frequency is unknown. These are synthetic parsing inputs, not a claim that uppercase labels are standard Android packaging or that the APKs are installable. ### REA version Source runtime: `39275420ea18ad803eccfcc2702865b98ed36e44`, also the current default-branch HEAD when checked. Latest release `rea-agents-6.7.0` at `27dc42d10249f0410904a75c0997842eb03b931c` has identical affected contracts, provider, adapters, fixtures and tests by source comparison; release runtime was not executed. ### Runtime and platform Node.js 24.18.0; npm 11.16.0; Vitest 5.0.3; macOS 27.0.1 arm64. No Hopper, Ghidra, device or target execution. ### Target and artifact context Synthetic ZIP APKs contain the repository's ELF64 x86_64 AOT images with distinct `a`/`b` snapshot hashes. Directory labels do not change ELF machine type. The inputs omit `libflutter.so` and an Android manifest. Identification coverage is partial because engine libraries are absent; successful AOT controls report complete structural library coverage. ### Interface MCP ### Earliest failure stage Request parsing or validation ### Relevant output or evidence Both uppercase requests returned `isError: true` and exactly: ```text Input validation error: Invalid arguments for tool inspect_dart_aot: abi: ABI name as it appears under lib/, e.g. arm64-v8a ``` The provider spy observed no dispatch for either rejection. Single-library default inspection returned ABI `ARM64-V8A` and snapshot hash `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`; paired default and explicit `x86_64` returned `bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`. The exact inline test was replayed against the source revision above; separate retained verification independently observed these outcomes. Compiled CLI, standalone stdio MCP and release-runtime reproduction were not run. ### Typed outcome or recovery expected For the paired fixture, explicit `ARM64-V8A` should succeed with its original label, `a` hash and complete structural library coverage. Default and explicit `x86_64` should retain the `b` payload. The single-library fixture should give the same payload for explicit and default selection. Suppressing the reported identity or selecting another payload does not restore caller choice. ### Analysis or suspected cause Source-derived: [discovery](https://github.com/morluto/rea/blob/39275420ea18ad803eccfcc2702865b98ed36e44/src/flutter/FlutterBuildProvider.ts#L273-L302) retains directory labels, and [provider selection](https://github.com/morluto/rea/blob/39275420ea18ad803eccfcc2702865b98ed36e44/src/flutter/FlutterBuildProvider.ts#L938-L947) matches the supplied label exactly. The earlier [canonical input schema](https://github.com/morluto/rea/blob/39275420ea18ad803eccfcc2702865b98ed36e44/src/domain/flutter/flutterBuildAnalysis.ts#L18-L28) restricts the selector to lowercase letters, digits, underscores and hyphens, rejecting these reported identities before dispatch. ### Additional context Single-character, overlong, punctuation and dot-segment variants were not tested. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: Explicit selection fails for reported, default-inspectable nonstandard labels; standard-name controls succeed. No severe harm or corrupted analysis was observed, and population frequency is unknown. - Workaround status: unverified - Workaround basis: Omitting the selector works for the sole-library fixture but selects a different payload in the paired fixture. No practical general workaround preserving the intended selection and archive identity was verified.
Author
Owner

@Yi-111-a commented on GitHub (Oct 19, 2026):

Fixed in #5223: the inspect_dart_aot selector now uses the same label grammar discovery reads, so a reported ARM64-V8A payload stays selectable and keeps its label. A schema case also pins that empty and path syntax stay refused.

<!-- gh-comment-id:6263676691 --> @Yi-111-a commented on GitHub (Oct 19, 2026): Fixed in #5223: the `inspect_dart_aot` selector now uses the same label grammar discovery reads, so a reported `ARM64-V8A` payload stays selectable and keeps its label. A schema case also pins that empty and path syntax stay refused.
Author
Owner

@morluto commented on GitHub (Oct 19, 2026):

Fixed on main in 64abd4aefb.

The selector preserves the literal reported ZIP label, including nonstandard spelling and characters; provider selection still compares exact strings and extraction uses a fixed owned filename. CLI and MCP now consume the same canonical fields. Inventory retains the exact1024-byte UTF-8 path limit; the bounded name query permits up to1010 Unicode characters, covering every possible libapp label without claiming that every admitted query exists.

Reviewed and adapted the schema-sharing work from #5223 at b9ccd2a349, with verified coauthor credit. Review caught two remaining counterexamples in that proposal: its335-unit cap rejected admitted ASCII paths, and its character filter rejected literal labels inventory can report. Those restrictions are absent from this fix. A Unicode regex also avoids Zod/JSON Schema length-unit drift and consumes a final newline correctly. Context7/DeepWiki were navigation aids; the primary zip.js name-validation documentation and executed ZIP fixtures establish the relevant behavior.

Baseline registered MCP regressions:14 failed,1 passed. Final focused suite:127 tests/5 files passed; final CLI file with added canonical-path controls:9 passed. Standalone compiled CLI and stdio MCP completed17 explicit inspections (8 CLI,9 MCP), including a1010-byte ASCII label, multibyte labels, backslash and newline; NUL was tested through JSON MCP, not OS argv. Paired fixtures retain the caller-selected a-hash while omission selects x86_64's b-hash. Check/docs passed. These are real parsing workflows over synthetic APK/ELF data, not installable APK or real Flutter release qualification; directory labels do not establish ELF architecture.

Descriptive cost control, three fresh processes per version with warm OS caches: complete CLI schema+inspection and stdio initialization/catalog/identify/inspection workflow used817371→817424 cl100k-estimated request/reply tokens; median2.880→2.886s elapsed,4.035→4.176s wait4-reported CPU,1004.7→1004.5MiB sampled family RSS. All captured protocol frames and CLI argv/output were counted; actual model usage and the larger audit task's context/retries were not measured. No speedup or true-cold claim.

<!-- gh-comment-id:6265442819 --> @morluto commented on GitHub (Oct 19, 2026): Fixed on main in 64abd4aefb23317dd4f787094456d1b19de47c77. The selector preserves the literal reported ZIP label, including nonstandard spelling and characters; provider selection still compares exact strings and extraction uses a fixed owned filename. CLI and MCP now consume the same canonical fields. Inventory retains the exact1024-byte UTF-8 path limit; the bounded name query permits up to1010 Unicode characters, covering every possible libapp label without claiming that every admitted query exists. Reviewed and adapted the schema-sharing work from #5223 at b9ccd2a349922cd3e354baac5234febe9823c956, with verified coauthor credit. Review caught two remaining counterexamples in that proposal: its335-unit cap rejected admitted ASCII paths, and its character filter rejected literal labels inventory can report. Those restrictions are absent from this fix. A Unicode regex also avoids Zod/JSON Schema length-unit drift and consumes a final newline correctly. Context7/DeepWiki were navigation aids; the [primary zip.js name-validation documentation](https://gildas-lormeau.github.io/zip.js/api/interfaces/GetEntriesOptions.html#filenamevalidation) and executed ZIP fixtures establish the relevant behavior. Baseline registered MCP regressions:14 failed,1 passed. Final focused suite:127 tests/5 files passed; final CLI file with added canonical-path controls:9 passed. Standalone compiled CLI and stdio MCP completed17 explicit inspections (8 CLI,9 MCP), including a1010-byte ASCII label, multibyte labels, backslash and newline; NUL was tested through JSON MCP, not OS argv. Paired fixtures retain the caller-selected a-hash while omission selects x86_64's b-hash. Check/docs passed. These are real parsing workflows over synthetic APK/ELF data, not installable APK or real Flutter release qualification; directory labels do not establish ELF architecture. Descriptive cost control, three fresh processes per version with warm OS caches: complete CLI schema+inspection and stdio initialization/catalog/identify/inspection workflow used817371→817424 cl100k-estimated request/reply tokens; median2.880→2.886s elapsed,4.035→4.176s wait4-reported CPU,1004.7→1004.5MiB sampled family RSS. All captured protocol frames and CLI argv/output were counted; actual model usage and the larger audit task's context/retries were not measured. No speedup or true-cold claim.
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#3196
No description provided.