[GH-ISSUE #6862] [Bug] Android graph infers Flutter from a text file inside a library-named directory #4203

Closed
opened 2026-10-31 00:47:40 +00:00 by bsakaguchi · 0 comments
Owner

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

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

projectAndroidApplicationEvidence adds "flutter" to runtime_families for an APK containing only an inert manifest and assets/libflutter.so/NOTICE.txt. The authenticated inventory identifies assets/libflutter.so as a directory, and the graph correctly returns native_libraries: []. Its runtime summary nevertheless treats that directory's name as a Flutter native-library convention and reports complete-within-inventory coverage.

Renaming only the directory to flutter-docs removes the Flutter hint. This is a production inventory/application-boundary reproduction, not a compiled CLI/MCP or target-execution test.

Reproduction

From the source checkout with locked dependencies, save this actually executed probe as /tmp/agent-work.0TqT4J/android/probe-runtime-directory.mts:

import { createRequire } from 'node:module';
import { writeFile } from 'node:fs/promises';
const root = process.env.REA_ROOT!;
const { ZipWriter, Uint8ArrayWriter, TextReader } = createRequire(`${root}/package.json`)('@zip.js/zip.js');
const { inventoryArtifact } = await import(`${root}/src/artifacts/inventory/ArtifactInventory.ts`);
const { ArtifactResourceScope } = await import(`${root}/src/artifacts/ArtifactResourceScope.ts`);
const { createEvidence } = await import(`${root}/src/domain/evidence.ts`);
const { ARTIFACT_GRAPH_PROVIDER } = await import(`${root}/src/domain/investigationProviders.ts`);
const { projectAndroidApplicationEvidence } = await import(`${root}/src/application/android/AndroidApplicationService.ts`);
const outputRoot = '/tmp/agent-work.0TqT4J/android';
for (const folder of ['flutter-docs', 'libflutter.so']) {
  const zip = new ZipWriter(new Uint8ArrayWriter());
  await zip.add('AndroidManifest.xml', new TextReader('<manifest package="example.inert"/>'));
  await zip.add(`assets/${folder}/`, undefined, { directory: true });
  await zip.add(`assets/${folder}/NOTICE.txt`, new TextReader('inert text'));
  const path = `${outputRoot}/${folder}.apk`;
  await writeFile(path, await zip.close());
  const resourceScope = new ArtifactResourceScope();
  try {
    const inventory = await inventoryArtifact(path, { resourceScope });
    const source = createEvidence({path, sha256: inventory.manifest.root_sha256, format:'apk'},
      ARTIFACT_GRAPH_PROVIDER, {operation:'inventory_artifact', parameters:{}, result:inventory, confidence:'observed', authority:'shipped-artifact'});
    const projected = projectAndroidApplicationEvidence({ inventory_evidence:[source] });
    if (!projected.ok) throw projected.error;
    const result = projected.value.evidence.normalized_result as any;
    await writeFile(`${path}.evidence.json`, JSON.stringify({source, projection:projected.value.evidence}, null, 2));
    console.log(JSON.stringify({folder, occurrences:inventory.occurrences.map(x=>[x.logical_path,x.entry_kind]),
      native_libraries:result.components.native_libraries, runtime_families:result.runtime_families, coverage:result.coverage}));
  } finally { await resourceScope.close(); }
}

Run from the checkout with the pinned Node.js toolchain:

mkdir -p /tmp/agent-work.0TqT4J/android
REA_ROOT="$PWD" node_modules/.bin/tsx /tmp/agent-work.0TqT4J/android/probe-runtime-directory.mts

The reduced probe was executed once before this draft; an earlier equivalent local fixture produced the same contrast. It creates ZIP APK fixtures, runs the actual artifact inventory, authenticates that inventory as Evidence, then calls the shared Android application projection. It runs no APK code or native provider.

Expected behavior

A known directory named libflutter.so and its plain text descendant must not establish a Flutter native-library hint. Preserve the existing file/directory distinction when deriving the runtime summary.

The Android graph contract distinguishes file/slice components from directories and says framework hints match complete native library names. This fixture contains no native-library occurrence or component.

Impact and scope

The shared graph returns a false runtime-family hint for a directory containing ordinary text. The demonstrated input has no executable bytes and native_libraries remains empty. This can misdirect an analyst's choice of an Android runtime-specific inspection path. Real package prevalence and other family names/platforms were not tested.

A practical workaround is to check the retained inventory and components.native_libraries before accepting the hint. Both executed outputs preserve those facts, allowing the false Flutter hint to be discarded without changing the target.

REA version

6.7.0; immutable source revision 96ea32b7e4e28d3c9cd79a1de4b8e1ba3b4244e9.

Runtime and platform

Node.js v24.22.1; Linux 7.2.8-9-omarchy x86_64; locked tsx 4.23.15 and @zip.js/zip.js 2.15.0. No Java, Android device, native provider, or APK execution.

Target and artifact context

Two local synthetic ZIP APKs. Each contains the inert text AndroidManifest.xml, an explicit asset directory, and NOTICE.txt containing inert text. Only the directory name differs: flutter-docs versus libflutter.so. The actual APK fixtures and returned inventory/projection Evidence are retained under /tmp/agent-work.0TqT4J/android/.

Interface

Other

Earliest failure stage

Transport or result projection

Relevant output or evidence

The executed probe exited 0 and printed:

{"folder":"flutter-docs","occurrences":[[".","file"],["AndroidManifest.xml","file"],["assets/flutter-docs","directory"],["assets/flutter-docs/NOTICE.txt","file"]],"native_libraries":[],"runtime_families":[],"coverage":{"status":"complete-within-inventory","inventory_complete":true}}
{"folder":"libflutter.so","occurrences":[[".","file"],["AndroidManifest.xml","file"],["assets/libflutter.so","directory"],["assets/libflutter.so/NOTICE.txt","file"]],"native_libraries":[],"runtime_families":["flutter"],"coverage":{"status":"complete-within-inventory","inventory_complete":true}}
  • Executed probe/output: /tmp/agent-work.0TqT4J/android/probe-runtime-directory.mts and probe-runtime-directory.out.
  • Actual Evidence: /tmp/agent-work.0TqT4J/android/libflutter.so.apk.evidence.json and flutter-docs.apk.evidence.json.
  • Evidence provenance: observed production inventory and shared application projection over authored inert ZIPs.
  • Local verification: the known-directory case and renamed-directory control both executed.
  • Reproduction completeness: complete for the shared projection boundary; compiled CLI/MCP and real APK/provider behavior were not run.

Typed outcome or recovery expected

Return no Flutter hint from this directory/text-only fixture while retaining its exact source inventory. Continue to report unknown runtime behavior as an inference rather than execution evidence.

Analysis or suspected cause

Source-derived: runtimeFamilies scans every projected file path. The surviving NOTICE.txt path contains the known directory's basename. nativeRuntimeConvention compares every path segment with native-library and framework names, losing which segment is the file. Filtering directory occurrences earlier therefore does not prevent this descendant-file case.

Additional context

Open and closed searches included Android directory runtime, "runtime_families" "directory", flutter directory, "nativeRuntimeConvention", and "graph" "libflutter.so"; the supplied open-title ledger was also searched. Search receipts are retained in /tmp/agent-work.0TqT4J/android/ as runtime-directory-open.json, runtime-directory-closed.json, and duplicate-runtime-directory*.json.

Related but different closed reports:

  • #3349: directories themselves became code/library components and bridge endpoints. Here directory components are correctly excluded; a plain descendant file still supplies the runtime hint.
  • #4782: .dex/.class basename rules disagreed with bytecode runtime summaries. This case concerns native-library conventions in an ancestor directory.
  • #1489: unrelated substrings such as community matched runtime conventions. This uses an exact libflutter.so segment whose authenticated kind is directory.

This research, inert fixtures, and draft were produced with AI assistance. No human review or real-target execution is represented. No known-good release was established.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: Executed shared-boundary probes show a false Flutter runtime hint for a known directory containing only text, with complete-within-inventory coverage and no native-library components. The impact is bounded incorrect analysis metadata; real package prevalence is unknown.
  • Workaround status: available
  • Workaround basis: Both executed results retain the authenticated directory kind and an empty native-library component list, so callers can check those observations and discard the unsupported hint without changing the target.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6862 ### 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 `projectAndroidApplicationEvidence` adds `"flutter"` to `runtime_families` for an APK containing only an inert manifest and `assets/libflutter.so/NOTICE.txt`. The authenticated inventory identifies `assets/libflutter.so` as a directory, and the graph correctly returns `native_libraries: []`. Its runtime summary nevertheless treats that directory's name as a Flutter native-library convention and reports complete-within-inventory coverage. Renaming only the directory to `flutter-docs` removes the Flutter hint. This is a production inventory/application-boundary reproduction, not a compiled CLI/MCP or target-execution test. ### Reproduction From the source checkout with locked dependencies, save this actually executed probe as `/tmp/agent-work.0TqT4J/android/probe-runtime-directory.mts`: ```ts import { createRequire } from 'node:module'; import { writeFile } from 'node:fs/promises'; const root = process.env.REA_ROOT!; const { ZipWriter, Uint8ArrayWriter, TextReader } = createRequire(`${root}/package.json`)('@zip.js/zip.js'); const { inventoryArtifact } = await import(`${root}/src/artifacts/inventory/ArtifactInventory.ts`); const { ArtifactResourceScope } = await import(`${root}/src/artifacts/ArtifactResourceScope.ts`); const { createEvidence } = await import(`${root}/src/domain/evidence.ts`); const { ARTIFACT_GRAPH_PROVIDER } = await import(`${root}/src/domain/investigationProviders.ts`); const { projectAndroidApplicationEvidence } = await import(`${root}/src/application/android/AndroidApplicationService.ts`); const outputRoot = '/tmp/agent-work.0TqT4J/android'; for (const folder of ['flutter-docs', 'libflutter.so']) { const zip = new ZipWriter(new Uint8ArrayWriter()); await zip.add('AndroidManifest.xml', new TextReader('<manifest package="example.inert"/>')); await zip.add(`assets/${folder}/`, undefined, { directory: true }); await zip.add(`assets/${folder}/NOTICE.txt`, new TextReader('inert text')); const path = `${outputRoot}/${folder}.apk`; await writeFile(path, await zip.close()); const resourceScope = new ArtifactResourceScope(); try { const inventory = await inventoryArtifact(path, { resourceScope }); const source = createEvidence({path, sha256: inventory.manifest.root_sha256, format:'apk'}, ARTIFACT_GRAPH_PROVIDER, {operation:'inventory_artifact', parameters:{}, result:inventory, confidence:'observed', authority:'shipped-artifact'}); const projected = projectAndroidApplicationEvidence({ inventory_evidence:[source] }); if (!projected.ok) throw projected.error; const result = projected.value.evidence.normalized_result as any; await writeFile(`${path}.evidence.json`, JSON.stringify({source, projection:projected.value.evidence}, null, 2)); console.log(JSON.stringify({folder, occurrences:inventory.occurrences.map(x=>[x.logical_path,x.entry_kind]), native_libraries:result.components.native_libraries, runtime_families:result.runtime_families, coverage:result.coverage})); } finally { await resourceScope.close(); } } ``` Run from the checkout with the pinned Node.js toolchain: ```sh mkdir -p /tmp/agent-work.0TqT4J/android REA_ROOT="$PWD" node_modules/.bin/tsx /tmp/agent-work.0TqT4J/android/probe-runtime-directory.mts ``` The reduced probe was executed once before this draft; an earlier equivalent local fixture produced the same contrast. It creates ZIP APK fixtures, runs the actual artifact inventory, authenticates that inventory as Evidence, then calls the shared Android application projection. It runs no APK code or native provider. ### Expected behavior A known directory named `libflutter.so` and its plain text descendant must not establish a Flutter native-library hint. Preserve the existing file/directory distinction when deriving the runtime summary. The [Android graph contract](https://github.com/morluto/rea/blob/96ea32b7e4e28d3c9cd79a1de4b8e1ba3b4244e9/docs/android-analysis.md#L250-L258) distinguishes file/slice components from directories and says framework hints match complete native library names. This fixture contains no native-library occurrence or component. ### Impact and scope The shared graph returns a false runtime-family hint for a directory containing ordinary text. The demonstrated input has no executable bytes and `native_libraries` remains empty. This can misdirect an analyst's choice of an Android runtime-specific inspection path. Real package prevalence and other family names/platforms were not tested. A practical workaround is to check the retained inventory and `components.native_libraries` before accepting the hint. Both executed outputs preserve those facts, allowing the false Flutter hint to be discarded without changing the target. ### REA version 6.7.0; immutable source revision `96ea32b7e4e28d3c9cd79a1de4b8e1ba3b4244e9`. ### Runtime and platform Node.js v24.22.1; Linux 7.2.8-9-omarchy x86_64; locked tsx 4.23.15 and @zip.js/zip.js 2.15.0. No Java, Android device, native provider, or APK execution. ### Target and artifact context Two local synthetic ZIP APKs. Each contains the inert text `AndroidManifest.xml`, an explicit asset directory, and `NOTICE.txt` containing `inert text`. Only the directory name differs: `flutter-docs` versus `libflutter.so`. The actual APK fixtures and returned inventory/projection Evidence are retained under `/tmp/agent-work.0TqT4J/android/`. ### Interface Other ### Earliest failure stage Transport or result projection ### Relevant output or evidence The executed probe exited 0 and printed: ```json {"folder":"flutter-docs","occurrences":[[".","file"],["AndroidManifest.xml","file"],["assets/flutter-docs","directory"],["assets/flutter-docs/NOTICE.txt","file"]],"native_libraries":[],"runtime_families":[],"coverage":{"status":"complete-within-inventory","inventory_complete":true}} {"folder":"libflutter.so","occurrences":[[".","file"],["AndroidManifest.xml","file"],["assets/libflutter.so","directory"],["assets/libflutter.so/NOTICE.txt","file"]],"native_libraries":[],"runtime_families":["flutter"],"coverage":{"status":"complete-within-inventory","inventory_complete":true}} ``` - Executed probe/output: `/tmp/agent-work.0TqT4J/android/probe-runtime-directory.mts` and `probe-runtime-directory.out`. - Actual Evidence: `/tmp/agent-work.0TqT4J/android/libflutter.so.apk.evidence.json` and `flutter-docs.apk.evidence.json`. - Evidence provenance: observed production inventory and shared application projection over authored inert ZIPs. - Local verification: the known-directory case and renamed-directory control both executed. - Reproduction completeness: complete for the shared projection boundary; compiled CLI/MCP and real APK/provider behavior were not run. ### Typed outcome or recovery expected Return no Flutter hint from this directory/text-only fixture while retaining its exact source inventory. Continue to report unknown runtime behavior as an inference rather than execution evidence. ### Analysis or suspected cause Source-derived: [runtimeFamilies](https://github.com/morluto/rea/blob/96ea32b7e4e28d3c9cd79a1de4b8e1ba3b4244e9/src/domain/android/androidApplication.ts#L180-L195) scans every projected file path. The surviving `NOTICE.txt` path contains the known directory's basename. [nativeRuntimeConvention](https://github.com/morluto/rea/blob/96ea32b7e4e28d3c9cd79a1de4b8e1ba3b4244e9/src/domain/nativeConvention.ts#L26-L38) compares every path segment with native-library and framework names, losing which segment is the file. Filtering directory occurrences earlier therefore does not prevent this descendant-file case. ### Additional context Open and closed searches included `Android directory runtime`, `"runtime_families" "directory"`, `flutter directory`, `"nativeRuntimeConvention"`, and `"graph" "libflutter.so"`; the supplied open-title ledger was also searched. Search receipts are retained in `/tmp/agent-work.0TqT4J/android/` as `runtime-directory-open.json`, `runtime-directory-closed.json`, and `duplicate-runtime-directory*.json`. Related but different closed reports: - [#3349](https://github.com/morluto/rea/issues/3349): directories themselves became code/library components and bridge endpoints. Here directory components are correctly excluded; a plain descendant file still supplies the runtime hint. - [#4782](https://github.com/morluto/rea/issues/4782): `.dex`/`.class` basename rules disagreed with bytecode runtime summaries. This case concerns native-library conventions in an ancestor directory. - [#1489](https://github.com/morluto/rea/issues/1489): unrelated substrings such as `community` matched runtime conventions. This uses an exact `libflutter.so` segment whose authenticated kind is directory. This research, inert fixtures, and draft were produced with AI assistance. No human review or real-target execution is represented. No known-good release was established. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: Executed shared-boundary probes show a false Flutter runtime hint for a known directory containing only text, with complete-within-inventory coverage and no native-library components. The impact is bounded incorrect analysis metadata; real package prevalence is unknown. - Workaround status: available - Workaround basis: Both executed results retain the authenticated directory kind and an empty native-library component list, so callers can check those observations and discard the unsupported hint without changing the target.
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#4203
No description provided.