[GH-ISSUE #2603] fix(android): disclose unread components omitted from application projection #919

Closed
opened 2026-10-15 18:29:35 +00:00 by bsakaguchi · 2 comments
Owner

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

Summary

An APK inventory can retain an encrypted library entry whose content hash is unavailable, but the Android application graph silently loses that known path. The graph still claims complete coverage and gives no limitation explaining the omission. This can turn an unknown component identity into apparent component absence.

Steps to reproduce

At the repository revision below, save the following focused test as src/domain/android/unreadComponent.test.ts and run npm run test:focused -- src/domain/android/unreadComponent.test.ts. It constructs readable and encrypted ZIP controls, inventories them through the source application entry point, and checks the graph. The archive uses inert text and no Android provider or target execution.

import { mkdtemp, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { TextReader, Uint8ArrayWriter, ZipWriter } from '@zip.js/zip.js';
import { expect, it } from 'vitest';
import { createDirectAnalysis } from '../../composition/directAnalysis.js';
import { parseEvidence } from '../evidence.js';
import { parseArtifactInventoryEvidence } from '../artifactInventoryEvidence.js';
import { projectAndroidApplication } from './androidApplication.js';

it('compares readable and encrypted occurrence coverage', async () => {
  const root = await mkdtemp(join(tmpdir(), 'android-projection-'));
  const { runProviderAnalysis } = createDirectAnalysis({});
  try {
    for (const encrypted of [false, true]) {
      const writer = new ZipWriter(new Uint8ArrayWriter());
      await writer.add('AndroidManifest.xml', new TextReader('<manifest/>'));
      await writer.add('classes.dex', new TextReader('dex bytes'));
      await writer.add('lib/arm64-v8a/libentry.so', new TextReader('library bytes'),
        encrypted ? { password: 'fixture-only' } : {});
      const path = join(root, encrypted ? 'encrypted.apk' : 'readable.apk');
      await writeFile(path, await writer.close());
      const evidence = parseEvidence(await runProviderAnalysis(path, 'inventory_artifact', {}));
      const { inventory } = parseArtifactInventoryEvidence([evidence]);
      const occurrence = inventory.occurrences.find(
        entry => entry.logical_path === 'lib/arm64-v8a/libentry.so');
      const graph = projectAndroidApplication({ inventory_evidence: [evidence] });
      expect(occurrence).toBeDefined();
      console.log({ encrypted, hashStatus: occurrence?.hash_status,
        nativePaths: graph.components.native_libraries.map(entry => entry.path),
        coverage: graph.coverage, limitations: graph.limitations });
    }
  } finally {
    await rm(root, { recursive: true, force: true });
  }
}, 30000);

Expected behavior

The graph preserves the known presence of an unread library path and its unavailable identity, or explicitly reports that this occurrence was omitted and component absence is unknown. Complete coverage must not conceal this loss of supplied inventory evidence. The readable control retains its native-library component and existing coverage.

Actual behavior

The encrypted inventory contains lib/arm64-v8a/libentry.so with artifact_id: null, encrypted: true, and hash_status: unavailable. Its graph has an empty native-library array and no bridge pair, yet reports coverage.status: complete-within-inventory and inventory_complete: true. None of its limitations names unread or omitted components. The readable control retains the library and a path-based bridge pair. A focused source test also confirmed that the application service accepts both inventories and returns these same component and coverage results.

Evidence

  • Expected source: AGENTS.md:19 requires preserving omission and uncertainty across boundaries, and :21 requires unresolved identity to remain unknown rather than absent. docs/android-analysis.md:185 defines the projection as exact component paths with path-based hypotheses and explicit coverage.
  • Failure source: src/domain/mobileApplicationGraphProjection.ts:66 filters occurrences with null content identity; src/domain/android/androidApplication.ts:94 consumes only the remaining components and :131 computes coverage without accounting for that omission. The generated ZIP inventory and source application-service checks reproduced the silent loss.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

The application adapter calls this same projection and wraps its result as Evidence. No fabricated Evidence or invalid root format is required. The readable inventory and encrypted inventory both have complete member counts. The closed container-classification report and its described fixes concern byte-format selection, which is a separate contract from retaining unread occurrences in the graph.

Restoration check

Repeat both ZIP inputs through inventory and projection. The encrypted path must remain a visible unavailable fact or receive an explicit omission notice that prevents interpreting its absence as proven absence. Coverage must reflect the lost component information. Do not invent a content hash or assert that unread bytes contain a valid native library. The readable control must continue to retain its component and bridge hypothesis with complete coverage.

Runtime or environment

Repository revision 8b181716ac; Node v26.10.0; locked dependencies already installed. Verification used focused source tests and the application service shared by CLI/MCP; compiled CLI/MCP transport was not executed.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: Inventories with unread component entries produce incomplete graph facts and misleading completeness without disclosing the omission; the result is bounded to these inventory inputs.
  • Workaround status: available
  • Workaround basis: The retained inventory visibly contains the path and unavailable hash, so callers can safely cross-check graph components against inventory occurrences to preserve the known presence and uncertainty.
Originally created by @coygeek on GitHub (Oct 13, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2603 ## Summary An APK inventory can retain an encrypted library entry whose content hash is unavailable, but the Android application graph silently loses that known path. The graph still claims complete coverage and gives no limitation explaining the omission. This can turn an unknown component identity into apparent component absence. ## Steps to reproduce At the repository revision below, save the following focused test as `src/domain/android/unreadComponent.test.ts` and run `npm run test:focused -- src/domain/android/unreadComponent.test.ts`. It constructs readable and encrypted ZIP controls, inventories them through the source application entry point, and checks the graph. The archive uses inert text and no Android provider or target execution. ```ts import { mkdtemp, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { TextReader, Uint8ArrayWriter, ZipWriter } from '@zip.js/zip.js'; import { expect, it } from 'vitest'; import { createDirectAnalysis } from '../../composition/directAnalysis.js'; import { parseEvidence } from '../evidence.js'; import { parseArtifactInventoryEvidence } from '../artifactInventoryEvidence.js'; import { projectAndroidApplication } from './androidApplication.js'; it('compares readable and encrypted occurrence coverage', async () => { const root = await mkdtemp(join(tmpdir(), 'android-projection-')); const { runProviderAnalysis } = createDirectAnalysis({}); try { for (const encrypted of [false, true]) { const writer = new ZipWriter(new Uint8ArrayWriter()); await writer.add('AndroidManifest.xml', new TextReader('<manifest/>')); await writer.add('classes.dex', new TextReader('dex bytes')); await writer.add('lib/arm64-v8a/libentry.so', new TextReader('library bytes'), encrypted ? { password: 'fixture-only' } : {}); const path = join(root, encrypted ? 'encrypted.apk' : 'readable.apk'); await writeFile(path, await writer.close()); const evidence = parseEvidence(await runProviderAnalysis(path, 'inventory_artifact', {})); const { inventory } = parseArtifactInventoryEvidence([evidence]); const occurrence = inventory.occurrences.find( entry => entry.logical_path === 'lib/arm64-v8a/libentry.so'); const graph = projectAndroidApplication({ inventory_evidence: [evidence] }); expect(occurrence).toBeDefined(); console.log({ encrypted, hashStatus: occurrence?.hash_status, nativePaths: graph.components.native_libraries.map(entry => entry.path), coverage: graph.coverage, limitations: graph.limitations }); } } finally { await rm(root, { recursive: true, force: true }); } }, 30000); ``` ## Expected behavior The graph preserves the known presence of an unread library path and its unavailable identity, or explicitly reports that this occurrence was omitted and component absence is unknown. Complete coverage must not conceal this loss of supplied inventory evidence. The readable control retains its native-library component and existing coverage. ## Actual behavior The encrypted inventory contains `lib/arm64-v8a/libentry.so` with `artifact_id: null`, `encrypted: true`, and `hash_status: unavailable`. Its graph has an empty native-library array and no bridge pair, yet reports `coverage.status: complete-within-inventory` and `inventory_complete: true`. None of its limitations names unread or omitted components. The readable control retains the library and a path-based bridge pair. A focused source test also confirmed that the application service accepts both inventories and returns these same component and coverage results. ## Evidence - Expected source: `AGENTS.md:19` requires preserving omission and uncertainty across boundaries, and `:21` requires unresolved identity to remain unknown rather than absent. `docs/android-analysis.md:185` defines the projection as exact component paths with path-based hypotheses and explicit coverage. - Failure source: `src/domain/mobileApplicationGraphProjection.ts:66` filters occurrences with null content identity; `src/domain/android/androidApplication.ts:94` consumes only the remaining components and `:131` computes coverage without accounting for that omission. The generated ZIP inventory and source application-service checks reproduced the silent loss. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete The application adapter calls this same projection and wraps its result as Evidence. No fabricated Evidence or invalid root format is required. The readable inventory and encrypted inventory both have complete member counts. The closed [container-classification report](https://github.com/morluto/rea/issues/1491) and its described fixes concern byte-format selection, which is a separate contract from retaining unread occurrences in the graph. ## Restoration check Repeat both ZIP inputs through inventory and projection. The encrypted path must remain a visible unavailable fact or receive an explicit omission notice that prevents interpreting its absence as proven absence. Coverage must reflect the lost component information. Do not invent a content hash or assert that unread bytes contain a valid native library. The readable control must continue to retain its component and bridge hypothesis with complete coverage. ## Runtime or environment Repository revision 8b181716aca2cb467be499cba226abfb5ae19141; Node v26.10.0; locked dependencies already installed. Verification used focused source tests and the application service shared by CLI/MCP; compiled CLI/MCP transport was not executed. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: Inventories with unread component entries produce incomplete graph facts and misleading completeness without disclosing the omission; the result is bounded to these inventory inputs. - Workaround status: available - Workaround basis: The retained inventory visibly contains the path and unavailable hash, so callers can safely cross-check graph components against inventory occurrences to preserve the known presence and uncertainty.
bsakaguchi 2026-10-15 18:29:35 +00:00
Author
Owner

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

Verified the reported defect is addressed on main by ec271121e4.

Root cause and resolution: Android projection retains unread/encrypted inventory occurrences as unavailable_components, marks coverage partial and states component absence is unknown.

Validation: tests/boundary/filesystem/android/application.test.ts — retains encrypted native library paths as unknown component evidence checks encrypted path, null identity/hash, encrypted=true, partial coverage, retained count and unknown-absence limitation; readable inventory is a complete control.

Evidence: CI run 500e: owner unchanged and assertion passed.

Verification scope: ZIP inventory/projection behavior only; no Android analysis-provider claim.

Current main CI has separate failures under repair; this closure does not claim the complete main workflow is green.

<!-- gh-comment-id:6162511869 --> @morluto commented on GitHub (Oct 14, 2026): Verified the reported defect is addressed on main by ec271121e4996f89f666a5a2ed1cd2f3229411b4. Root cause and resolution: Android projection retains unread/encrypted inventory occurrences as unavailable_components, marks coverage partial and states component absence is unknown. Validation: `tests/boundary/filesystem/android/application.test.ts` — `retains encrypted native library paths as unknown component evidence` checks encrypted path, null identity/hash, `encrypted=true`, partial coverage, retained count and unknown-absence limitation; readable inventory is a complete control. Evidence: CI run 500e: owner unchanged and assertion passed. Verification scope: ZIP inventory/projection behavior only; no Android analysis-provider claim. Current main CI has separate failures under repair; this closure does not claim the complete main workflow is green.
Author
Owner

@mohdkasim222-alt commented on GitHub (Oct 14, 2026):

Please do not send these emails back to back

On Wed, Oct 14, Reiwa 8 at 12:10 morluto @.***> wrote:

Closed #2603 https://github.com/morluto/rea/issues/2603 as completed.

—
Reply to this email directly, view it on GitHub
https://github.com/morluto/rea/issues/2603?email_source=notifications&email_token=CISOJP46VKERIVFVYKBAB335T4NV7A5CNFSNUABQM5UWIORPF5TWS5BNNB2WEL2JONZXKZKFOZSW45CON52GSZTJMNQXI2LPNYXTGMZSGAYDQNJVHE4TTJTSMVQXG33OVJZXKYTTMNZGSYTFMSSWK5TFNZ2KYZTPN52GK4S7MNWGSY3L#event-33200855999,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/CISOJP64PS37UWDTMO3TYC35T4NV7AVCNFSNUABGKJSXA33TNF2G64TZHMYTEMBZHE3DMOJTGM5US43TOVSTWNJYGI2TCNBQGYYTPILWAI
.
You are receiving this because you are subscribed to this thread.Message
ID: @.***>

<!-- gh-comment-id:6162542295 --> @mohdkasim222-alt commented on GitHub (Oct 14, 2026): Please do not send these emails back to back On Wed, Oct 14, Reiwa 8 at 12:10 morluto ***@***.***> wrote: > Closed #2603 <https://github.com/morluto/rea/issues/2603> as completed. > > — > Reply to this email directly, view it on GitHub > <https://github.com/morluto/rea/issues/2603?email_source=notifications&email_token=CISOJP46VKERIVFVYKBAB335T4NV7A5CNFSNUABQM5UWIORPF5TWS5BNNB2WEL2JONZXKZKFOZSW45CON52GSZTJMNQXI2LPNYXTGMZSGAYDQNJVHE4TTJTSMVQXG33OVJZXKYTTMNZGSYTFMSSWK5TFNZ2KYZTPN52GK4S7MNWGSY3L#event-33200855999>, > or unsubscribe > <https://github.com/notifications/unsubscribe-auth/CISOJP64PS37UWDTMO3TYC35T4NV7AVCNFSNUABGKJSXA33TNF2G64TZHMYTEMBZHE3DMOJTGM5US43TOVSTWNJYGI2TCNBQGYYTPILWAI> > . > You are receiving this because you are subscribed to this thread.Message > ID: ***@***.***> >
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#919
No description provided.