[GH-ISSUE #5467] [Bug] Managed/native verification treats an empty normalized module identity as known #3322

Closed
opened 2026-10-31 00:39:09 +00:00 by bsakaguchi · 1 comment
Owner

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

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

node scripts/rea.mjs verify-managed-native-boundaries <request.json> --json accepts a P/Invoke declaration with scope .dll and entry point open_native, then returns verified/exact-export-name against supplied native Evidence named lib.dylib or lib.so. Changing only the native filename to foo.dll returns contradicted/module-mismatch. Every call exits 0 and retains one native candidate. The declared module name becomes empty after extension normalization, so neither a verified module link nor a module contradiction has a usable declared identity.

Reproduction

From the built source checkout at the revision below, run this self-contained reproduction. The independent verifier ran this exact block and observed all three results. It uses the production Evidence constructor and the advertised workflow example, isolates CLI state in a disposable directory, and saves the requests and replies. Native Evidence is synthetic; no native provider or managed code executes.

export REA_VERIFY_ROOT="$(mktemp -d "${TMPDIR:-.}/rea-verify.XXXXXX")"
node --input-type=module <<'JS'
import { mkdirSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { spawnSync } from 'node:child_process';
import { createEvidence } from './dist/domain/evidence.js';
import { MANAGED_NATIVE_VERIFICATION_EXAMPLE as example } from './dist/contracts/managed/managedWorkflowExamples.js';
const root = process.env.REA_VERIFY_ROOT;
for (const dir of ['home', 'config', 'data', 'cache', 'state', 'out']) mkdirSync(join(root, dir), { recursive: true });
const env = { ...process.env, HOME: join(root, 'home'), XDG_CONFIG_HOME: join(root, 'config'), XDG_DATA_HOME: join(root, 'data'), XDG_CACHE_HOME: join(root, 'cache'), XDG_STATE_HOME: join(root, 'state') };
const managed = structuredClone(example.managed_boundaries.normalized_result);
managed.module_refs[0].name = '.dll';
managed.pinvoke_imports[0].import_scope_name = '.dll';
const boundaries = createEvidence(undefined, example.managed_boundaries.provider, { operation: 'inspect_managed_native_boundaries', parameters: {}, result: managed, rawResult: null });
const base = example.native_observations[0];
for (const name of ['lib.dylib', 'lib.so', 'foo.dll']) {
  const native = createEvidence({ path: join(root, 'out', name), sha256: base.subject.digest.sha256, format: base.subject.format, architecture: base.subject.architecture }, base.provider, { operation: base.operation, parameters: {}, result: base.normalized_result, rawResult: null });
  const input = join(root, 'out', name + '-request.json');
  writeFileSync(input, JSON.stringify({ managed_boundaries: boundaries, native_observations: [native] }));
  const run = spawnSync(process.execPath, ['scripts/rea.mjs', 'verify-managed-native-boundaries', input, '--json'], { env, encoding: 'utf8' });
  writeFileSync(join(root, 'out', name + '-reply.json'), run.stdout);
  if (run.status !== 0) throw new Error(run.stderr + run.stdout);
  const match = JSON.parse(run.stdout).normalized_result.pinvoke_imports[0];
  console.log(JSON.stringify({ module: name, status: match.status, basis: match.basis, confidence: match.confidence, candidates: match.candidates.length }));
}
JS
printf 'Reproduction records: %s\n' "$REA_VERIFY_ROOT"

Expected behavior

All three declarations should remain unresolved with basis module-identity-unknown and retain the supplied name candidate. docs/managed-code-analysis.md:370-376 requires unresolved correspondence when either side lacks a usable module identity, and a mismatch only when both identities are available and differ. src/contracts/managed/managedWorkflowToolContracts.ts:119 advertises the same behavior. These are declaration comparisons; they do not establish runtime loading, binding, or execution.

Impact and scope

P2. The accepted inputs produce an unsupported verified declaration link or a false module contradiction, which can mislead consumers of the machine-readable analysis result. The reproduced scope is .dll against lib.dylib, lib.so, and foo.dll with the same exact entry point. Frequency in real assemblies and severe harm were not established.

The reply retains the literal scope and native candidates, allowing an analyst to classify correspondence manually as unknown. This does not correct automated verification or prove a runtime target.

REA version

6.7.0, source commit 074768901f3cdc8205b61773fb1faf6f6c7b3ca8, built with locked dependencies.

Runtime and platform

Node.js v26.12.0; macOS, Darwin arm64. The verifier did not record the macOS version. No Hopper, Ghidra, or other native analysis provider was required.

Target and artifact context

Synthetic authenticated managed-boundary and Mach-O export Evidence derived from the advertised example. A separate independent replay also inspected a generated PE/CLI fixture declaring .dll: metadata and coverage were complete with no issues, the literal scope was preserved, and verification against synthetic native Evidence produced the same three erroneous results. No managed assembly executed.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

The independent verifier observed this per-declaration output from the exact reproduction above. All calls exited 0 and retained one candidate:

module       status        basis              declaration confidence
lib.dylib    verified      exact-export-name  observed
lib.so       verified      exact-export-name  observed
foo.dll      contradicted  module-mismatch    unknown

The separate PE/CLI inspection replay also exercised ordinary-name controls: nativehelper.dll against nativehelper.dll verified, nativehelper.dll against other.dll returned module mismatch, and declared foo matched libfoo.dylib. These controls bound the observed failure; they do not establish every other input's behavior. The observed confidence above belongs to the declaration item; the outer verification Evidence remains analyst inference.

Typed outcome or recovery expected

Retain the raw .dll declaration and its native candidate, return unresolved/module-identity-unknown, and avoid both verified or inferred correspondence and module-mismatch when the declared normalized identity is empty. Preserve the ordinary-name controls above. These are proposed restoration checks; no fix was applied.

Analysis or suspected cause

Source-derived reasoning at the tested revision: src/domain/managed/managedNativeVerificationMatch.ts:111-114 strips the entire .dll name. Its shared-library alias construction at line 120 contributes an empty identity for lib.dylib and lib.so. moduleCompatible at line 129 checks the raw scope for nonblankness before comparing normalized identities, and scopeKnown at line 203 likewise uses the raw scope, bypassing the unresolved branch at line 244 for a different native library.

Additional context

Prepared with AI assistance and independently reproduced through the supported CLI.

Related closed report #3201 covers absent native identity or a literally empty managed scope. This follow-up concerns a nonempty scope that loses its identity during normalization, including a false verified-link outcome. #3351 and its open PR #4144 concern Windows path separators with usable names.

Freshness check: fetched origin/main at 052956af3416fa01d93837e54fdec3ad2739f6d1. No commits after the reproduced baseline changed the cited matching implementation or contract files; this was a source freshness check, not a runtime replay on that newer revision. Open and closed issue/PR searches covered the command, empty module identity, source module, normalized identities, library names, and #3201 references. No matching follow-up was found.

NOT_ENOUGH_INFO: no last-known-good version was established.

Originally created by @coygeek on GitHub (Oct 20, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5467 ### 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 `node scripts/rea.mjs verify-managed-native-boundaries <request.json> --json` accepts a P/Invoke declaration with scope `.dll` and entry point `open_native`, then returns `verified/exact-export-name` against supplied native Evidence named `lib.dylib` or `lib.so`. Changing only the native filename to `foo.dll` returns `contradicted/module-mismatch`. Every call exits 0 and retains one native candidate. The declared module name becomes empty after extension normalization, so neither a verified module link nor a module contradiction has a usable declared identity. ### Reproduction From the built source checkout at the revision below, run this self-contained reproduction. The independent verifier ran this exact block and observed all three results. It uses the production Evidence constructor and the advertised workflow example, isolates CLI state in a disposable directory, and saves the requests and replies. Native Evidence is synthetic; no native provider or managed code executes. ```sh export REA_VERIFY_ROOT="$(mktemp -d "${TMPDIR:-.}/rea-verify.XXXXXX")" node --input-type=module <<'JS' import { mkdirSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { spawnSync } from 'node:child_process'; import { createEvidence } from './dist/domain/evidence.js'; import { MANAGED_NATIVE_VERIFICATION_EXAMPLE as example } from './dist/contracts/managed/managedWorkflowExamples.js'; const root = process.env.REA_VERIFY_ROOT; for (const dir of ['home', 'config', 'data', 'cache', 'state', 'out']) mkdirSync(join(root, dir), { recursive: true }); const env = { ...process.env, HOME: join(root, 'home'), XDG_CONFIG_HOME: join(root, 'config'), XDG_DATA_HOME: join(root, 'data'), XDG_CACHE_HOME: join(root, 'cache'), XDG_STATE_HOME: join(root, 'state') }; const managed = structuredClone(example.managed_boundaries.normalized_result); managed.module_refs[0].name = '.dll'; managed.pinvoke_imports[0].import_scope_name = '.dll'; const boundaries = createEvidence(undefined, example.managed_boundaries.provider, { operation: 'inspect_managed_native_boundaries', parameters: {}, result: managed, rawResult: null }); const base = example.native_observations[0]; for (const name of ['lib.dylib', 'lib.so', 'foo.dll']) { const native = createEvidence({ path: join(root, 'out', name), sha256: base.subject.digest.sha256, format: base.subject.format, architecture: base.subject.architecture }, base.provider, { operation: base.operation, parameters: {}, result: base.normalized_result, rawResult: null }); const input = join(root, 'out', name + '-request.json'); writeFileSync(input, JSON.stringify({ managed_boundaries: boundaries, native_observations: [native] })); const run = spawnSync(process.execPath, ['scripts/rea.mjs', 'verify-managed-native-boundaries', input, '--json'], { env, encoding: 'utf8' }); writeFileSync(join(root, 'out', name + '-reply.json'), run.stdout); if (run.status !== 0) throw new Error(run.stderr + run.stdout); const match = JSON.parse(run.stdout).normalized_result.pinvoke_imports[0]; console.log(JSON.stringify({ module: name, status: match.status, basis: match.basis, confidence: match.confidence, candidates: match.candidates.length })); } JS printf 'Reproduction records: %s\n' "$REA_VERIFY_ROOT" ``` ### Expected behavior All three declarations should remain `unresolved` with basis `module-identity-unknown` and retain the supplied name candidate. `docs/managed-code-analysis.md:370-376` requires unresolved correspondence when either side lacks a usable module identity, and a mismatch only when both identities are available and differ. `src/contracts/managed/managedWorkflowToolContracts.ts:119` advertises the same behavior. These are declaration comparisons; they do not establish runtime loading, binding, or execution. ### Impact and scope P2. The accepted inputs produce an unsupported verified declaration link or a false module contradiction, which can mislead consumers of the machine-readable analysis result. The reproduced scope is `.dll` against `lib.dylib`, `lib.so`, and `foo.dll` with the same exact entry point. Frequency in real assemblies and severe harm were not established. The reply retains the literal scope and native candidates, allowing an analyst to classify correspondence manually as unknown. This does not correct automated verification or prove a runtime target. ### REA version 6.7.0, source commit `074768901f3cdc8205b61773fb1faf6f6c7b3ca8`, built with locked dependencies. ### Runtime and platform Node.js v26.12.0; macOS, Darwin arm64. The verifier did not record the macOS version. No Hopper, Ghidra, or other native analysis provider was required. ### Target and artifact context Synthetic authenticated managed-boundary and Mach-O export Evidence derived from the advertised example. A separate independent replay also inspected a generated PE/CLI fixture declaring `.dll`: metadata and coverage were complete with no issues, the literal scope was preserved, and verification against synthetic native Evidence produced the same three erroneous results. No managed assembly executed. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence The independent verifier observed this per-declaration output from the exact reproduction above. All calls exited 0 and retained one candidate: ```text module status basis declaration confidence lib.dylib verified exact-export-name observed lib.so verified exact-export-name observed foo.dll contradicted module-mismatch unknown ``` The separate PE/CLI inspection replay also exercised ordinary-name controls: `nativehelper.dll` against `nativehelper.dll` verified, `nativehelper.dll` against `other.dll` returned module mismatch, and declared `foo` matched `libfoo.dylib`. These controls bound the observed failure; they do not establish every other input's behavior. The `observed` confidence above belongs to the declaration item; the outer verification Evidence remains analyst inference. ### Typed outcome or recovery expected Retain the raw `.dll` declaration and its native candidate, return `unresolved/module-identity-unknown`, and avoid both verified or inferred correspondence and `module-mismatch` when the declared normalized identity is empty. Preserve the ordinary-name controls above. These are proposed restoration checks; no fix was applied. ### Analysis or suspected cause Source-derived reasoning at the tested revision: `src/domain/managed/managedNativeVerificationMatch.ts:111-114` strips the entire `.dll` name. Its shared-library alias construction at line 120 contributes an empty identity for `lib.dylib` and `lib.so`. `moduleCompatible` at line 129 checks the raw scope for nonblankness before comparing normalized identities, and `scopeKnown` at line 203 likewise uses the raw scope, bypassing the unresolved branch at line 244 for a different native library. ### Additional context Prepared with AI assistance and independently reproduced through the supported CLI. Related closed report [#3201](https://github.com/morluto/rea/issues/3201) covers absent native identity or a literally empty managed scope. This follow-up concerns a nonempty scope that loses its identity during normalization, including a false verified-link outcome. [#3351](https://github.com/morluto/rea/issues/3351) and its open PR #4144 concern Windows path separators with usable names. Freshness check: fetched `origin/main` at `052956af3416fa01d93837e54fdec3ad2739f6d1`. No commits after the reproduced baseline changed the cited matching implementation or contract files; this was a source freshness check, not a runtime replay on that newer revision. Open and closed issue/PR searches covered the command, empty module identity, source module, normalized identities, library names, and #3201 references. No matching follow-up was found. NOT_ENOUGH_INFO: no last-known-good version was established.
Author
Owner

@matbrgz commented on GitHub (Oct 20, 2026):

Cross-referencing with sibling managed/native boundary reports in Cluster AK: #3351 ([Bug] Managed/native verification contradicts Windows module paths on macOS) and #5468. Both issues isolate module identity normalization and P/Invoke validation failures in src/verification/.

Consolidated under Cluster AK (Managed/Native P/Invoke Boundary Verification Anomalies) in tracker #4330.

<!-- gh-comment-id:6291443591 --> @matbrgz commented on GitHub (Oct 20, 2026): Cross-referencing with sibling managed/native boundary reports in **Cluster AK**: #3351 (`[Bug] Managed/native verification contradicts Windows module paths on macOS`) and #5468. Both issues isolate module identity normalization and P/Invoke validation failures in `src/verification/`. Consolidated under **Cluster AK (Managed/Native P/Invoke Boundary Verification Anomalies)** in tracker #4330.
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#3322
No description provided.