[GH-ISSUE #6601] [Bug] Firmware extraction returns nonexistent paths for decomposed Unicode filenames #3986

Closed
opened 2026-10-31 00:45:48 +00:00 by bsakaguchi · 1 comment
Owner

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

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

The real firmware publication boundary returns an unusable files[].path for an ordinary extracted file named cafe\u0301.txt (decomposed e plus combining acute). The staging inventory and producer report retain that spelling, while publication creates caf\u00e9.txt (NFC). The result still constructs its path with the original spelling: stat(result.files[0].path) returns ENOENT, although publication succeeds with coverage: "complete", no extraction gaps, and a matching SHA-256 at the differently named actual file. An ASCII filename control returns an existing path and matching content.

The executed probe used real report normalization, output inventory, file hashing, exclusive publication, durable digest readback, and commit. It supplied the small producer report and staged extraction tree rather than launching Unblob. Public CLI/MCP and actual firmware-engine execution were not run.

Reproduction

Use the reported checkout and locked dependencies, with pinned Node.js and Python 3. Save the following fixture generator as $WORK/fixture.py, and the TypeScript probe below as $WORK/probe.mts, in an existing disposable directory. Run python "$WORK/fixture.py" "$WORK", then from the checkout run node node_modules/tsx/dist/cli.mjs "$WORK/probe.mts" "$PWD" "$WORK". The generator creates and independently reads back two valid PAX TAR files, each with a single six-byte regular member. The probe creates producer-shaped private extraction output, normalizes its report, then runs production publication and reads the actual committed output. It invokes no external provider and changes no live configuration.

import io
import pathlib
import tarfile
import sys

root = pathlib.Path(sys.argv[1])
for label, name in [('ascii', 'plain.txt'), ('nfd', 'cafe\u0301.txt')]:
    archive = root / f'gpu-firmware-{label}.tar'
    payload = b'alpha\n'
    with tarfile.open(archive, 'w', format=tarfile.PAX_FORMAT) as tar:
        member = tarfile.TarInfo(name)
        member.size = len(payload)
        member.mode = 0o644
        member.mtime = 0
        tar.addfile(member, io.BytesIO(payload))
    with tarfile.open(archive, 'r') as tar:
        members = tar.getmembers()
        assert len(members) == 1
        assert members[0].name == name
        assert tar.extractfile(members[0]).read() == payload
    print(label, archive.name, archive.stat().st_size, repr(name))
import { createHash } from 'node:crypto';
import { mkdir, mkdtemp, readFile, readdir, stat, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { pathToFileURL } from 'node:url';

const checkout = process.argv[2];
const work = process.argv[3];
const source = (path: string) => pathToFileURL(join(checkout, path)).href;
const { FirmwareFileAccess, inventoryFirmwareOutput } = await import(source('src/firmware/FirmwareFiles.ts'));
const { normalizeUnblobReport } = await import(source('src/firmware/FirmwareReports.ts'));
const { publishFirmwareExtraction } = await import(source('src/firmware/FirmwarePublication.ts'));
const { startFirmwareDeadline } = await import(source('src/firmware/FirmwareDeadline.ts'));
const root = await mkdtemp(join(work, 'gpu-firmware-nfd-'));
const sha = (value: Uint8Array) => createHash('sha256').update(value).digest('hex');
const facts = (path: string, bytes: Uint8Array) => [
  { __typename__: 'StatReport', path, size: bytes.length, is_file: true, is_dir: false, is_link: false, link_target: null },
  { __typename__: 'HashReport', sha256: sha(bytes) },
  { __typename__: 'FileMagicReport', magic: 'ASCII text', mime_type: 'text/plain' },
];
const observations = [];
for (const name of ['plain.txt', 'cafe\u0301.txt']) {
  const label = name === 'plain.txt' ? 'ascii' : 'nfd';
  const workspace = join(root, label);
  const staged = join(workspace, 'extracted');
  await mkdir(staged, { recursive: true });
  const inputPath = join(workspace, 'firmware.bin');
  const inputBytes = await readFile(join(work, `gpu-firmware-${label}.tar`));
  await writeFile(inputPath, inputBytes);
  const payload = Buffer.from('alpha\n');
  const extractDir = join(staged, 'firmware.bin_extract');
  await mkdir(extractDir);
  const childPath = join(extractDir, name);
  await writeFile(childPath, payload);
  const child = { path: childPath, depth: 1, blob_id: 'tar-chunk', is_multi_file: false };
  const directory = { path: extractDir, depth: 1, blob_id: 'tar-chunk', is_multi_file: false };
  const rootTask = { path: inputPath, depth: 0, blob_id: '', is_multi_file: false };
  const report = [
    { task: rootTask, reports: [
      ...facts(inputPath, inputBytes),
      { __typename__: 'ChunkReport', id: 'tar-chunk', start_offset: 0, end_offset: inputBytes.length,
        size: inputBytes.length, handler_name: 'tar', is_encrypted: false, extraction_reports: [] },
    ], subtasks: [directory] },
    { task: directory, reports: [{ __typename__: 'StatReport', path: extractDir, size: 4096,
      is_file: false, is_dir: true, is_link: false, link_target: null }], subtasks: [child] },
    { task: child, reports: facts(childPath, payload), subtasks: [] },
  ];
  const selection = { offset: 0, length: inputBytes.length, sha256: sha(inputBytes) };
  const normalized = normalizeUnblobReport(report, { inputPath, outputRoot: staged, ...selection, maxDepth: 3 });
  const entries = await inventoryFirmwareOutput(staged, 4096, 10, 'extract_firmware');
  const files = new FirmwareFileAccess();
  const deadline = startFirmwareDeadline('publication', 120000);
  const outputDirectory = join(workspace, 'published');
  try {
    const result = await publishFirmwareExtraction({
      files, workspacePath: workspace, deadline, analysisTimeoutMs: 120000, outputDirectory,
      entries, normalized, selection, maxDepth: 3,
      engine: { name: 'unblob', version: '26.6.4', executable_path: '/fixture/unblob', executable_sha256: 'a'.repeat(64), source_revision: null, worker_count: 1 },
      signal: deadline.signal,
      retainCleanup: () => { throw new Error('Unexpected cleanup retention'); },
    });
    const published = result.files[0];
    const pathState = await stat(published.path).then(() => 'exists', (error: NodeJS.ErrnoException) => error.code);
    const actualNames = await readdir(join(outputDirectory, 'firmware.bin_extract'));
    const actualBytes = await readFile(join(outputDirectory, 'firmware.bin_extract', actualNames[0]));
    observations.push({ label, fixtureName: name, reportedRelativePath: published.relative_path, reportedPath: published.path,
      reportedPathState: pathState, actualNames, actualSha256: sha(actualBytes), reportedSha256: published.sha256,
      coverage: result.coverage, gaps: result.extraction_gaps, derivations: result.derivations });
  } finally { await files.close(); }
}
const output = { runtime: process.version, platform: process.platform, arch: process.arch, root, observations };
await writeFile(join(work, 'gpu-firmware-nfd-result.json'), JSON.stringify(output, null, 2) + '\n');
console.log(JSON.stringify(output, null, 2));

The source-boundary failure and ASCII control were each observed in the delegated initial run and once in my independent replay of these portable scripts, before drafting. This is a complete publication-boundary reproduction, not an end-to-end Unblob reproduction or a claim of formal minimization.

Expected behavior

Every returned regular-file path should resolve to the payload whose hash was verified. If destination normalization changes a name, preserve an explicit consistent relationship between the producer spelling, returned relative path, lineage, and actual published path. REA's firmware contract promises regular-file paths with independently verified hashes, and its child-analysis workflow passes those paths to subsequent analysis. A successful result should not require callers to discover the real filename by enumerating the destination.

Impact and scope

This is a bounded usability and evidence-identity defect on the tested Linux filesystem: a successfully published child cannot be opened by the exact path REA returns. File content remains intact, and listing the destination and using its actual NFC name is a demonstrated workaround. The probe's one-file case involves no collision, symlink, traversal, special file, failed hash, or cancellation. Other filesystems' normalization behavior and real-engine frequency were not tested; no claim is made that every platform returns ENOENT.

REA version

Package 6.7.0; source revision 642f6571ea9d333ea50114c25e648dbb5b8a3c46. The source package version and HEAD were checked locally; the npm registry latest version was checked before the audit. No installed/published CLI launcher was used.

Runtime and platform

Node.js v24.22.1; locked tsx v4.23.15; Python 3.14.8; Linux x86_64, Omarchy 4.0.6, kernel 7.2.8-9-omarchy. The probe's engine object is a synthetic schema-valid receipt, not evidence that Unblob 26.6.4 was installed or executed. No Binwalk, Unblob, Hopper, or Ghidra was run.

Target and artifact context

Two valid 10,240-byte PAX TAR fixtures containing a single ordinary regular member, plain.txt or cafe\u0301.txt, with content alpha\n. Names are synthetic and relative; the producer-shaped staging tree is within a disposable directory. Default-compatible extraction depth 3 and small nonbinding budgets are used, with fresh absent output directories.

Interface

Other

Earliest failure stage

Artifact or evidence registration

Relevant output or evidence

The ASCII control returns firmware.bin_extract/plain.txt, and stat succeeds. The failing case returns:

{
  "reportedRelativePath": "firmware.bin_extract/cafe\u0301.txt",
  "reportedPathState": "ENOENT",
  "actualNames": ["caf\u00e9.txt"],
  "actualSha256": "b6a98d9ce9a2d9149288fa3df42d377c3e42737afdcdaf714e33c0a100b51060",
  "reportedSha256": "b6a98d9ce9a2d9149288fa3df42d377c3e42737afdcdaf714e33c0a100b51060",
  "coverage": "complete",
  "gaps": []
}

The returned absolute path ends in the reported relative spelling, while the committed directory contains only the NFC name. The derived child path also retains the original spelling.

  • Expected source: The linked firmware regular-file and follow-on analysis contracts above.
  • Failure source: Executed report/inventory/publication result and filesystem readback from the complete inline probe; returned path construction and destination write normalization identify the mismatched owners.
  • Evidence provenance: observed. Public provider reachability and the corresponding real TAR producer path are separately source-derived.
  • Local verification: reproduced.
  • Reproduction completeness: complete at the publication boundary; actual Unblob execution and public CLI/MCP transport were not run.

Valid public trigger and guards: extract-firmware CLI dispatch forwards the selected input and absent output destination; the provider normalizes output, inventories it, then calls this publication function. This report has successful regular-file facts, matching digests and byte counts, an ordinary short relative name, no extraction problems, and a fresh destination. Its six bytes and one member fit default limits. The fixture's PAX path is independently checked by Python. For source-derived real-producer reachability, the inspected Unblob TAR adapter calls SafeTarFile, whose applicable checks admit this regular relative member and pass its spelling to Python extraction without Unicode normalization. This is source evidence, not a claim that the external engine was reproduced.

Audit evidence companions: gpu-firmware-nfd-fixture.py, gpu-firmware-nfd-probe.mts, gpu-firmware-nfd-result.json, and gpu-firmware-nfd-replay.out. Both necessary scripts are inline above; no private fixture is required.

Typed outcome or recovery expected

Return committed, openable regular-file paths with matching verified hashes and consistent lineage or an explicit producer-to-published name mapping. Proposed restoration check: the returned path must open the six expected bytes and match its declared digest for both Unicode and ASCII cases. Preserve the original member spelling as provenance. No code change or restoration run was made.

Analysis or suspected cause

Source-derived: inventory preserves the staged spelling. FirmwareFileAccess.publish returns the safe writer's receipt, whose actual relative path is normalized. However, firmware publication ignores that receipt and reconstructs paths from the staging name. Artifact path normalization uses NFC, explaining the observed mismatch.

Additional context

The initial full open-title index and all-state firmware/Unblob/Binwalk, Unicode/NFC/decomposed-name, and FirmwarePublication searches found no matching firmware-path report. Related #5108 concerns losing original member spelling during artifact inspection. This report instead concerns retaining that spelling but falsely returning it as the published filesystem location; preserving #5108's source-name provenance alone would leave this failure unchanged. Existing firmware coverage/admission reports #3756, #5745, #5746, #5846, and #5953 do not describe this successful-publication path mismatch. Submission-time duplicate searches are performed separately immediately before filing.

AI assistance: the investigation, synthetic fixtures, and report were prepared with AI assistance, including delegated firmware research independently reviewed and replayed before drafting. The report contains only synthetic filenames and bytes; no credentials, personal identifiers, or private paths are included. NOT_ENOUGH_INFO: first affected and last known good releases and external-engine reproduction are unavailable.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: Successful firmware publication returns a nonexistent child path on the tested filesystem, blocking direct follow-on use while retaining the correct published content.
  • Workaround status: available
  • Workaround basis: Enumerating the committed destination and opening the actual NFC filename returned the expected content and matching SHA-256 in both observed runs.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6601 ## 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 The real firmware publication boundary returns an unusable `files[].path` for an ordinary extracted file named `cafe\u0301.txt` (decomposed e plus combining acute). The staging inventory and producer report retain that spelling, while publication creates `caf\u00e9.txt` (NFC). The result still constructs its path with the original spelling: `stat(result.files[0].path)` returns `ENOENT`, although publication succeeds with `coverage: "complete"`, no extraction gaps, and a matching SHA-256 at the differently named actual file. An ASCII filename control returns an existing path and matching content. The executed probe used real report normalization, output inventory, file hashing, exclusive publication, durable digest readback, and commit. It supplied the small producer report and staged extraction tree rather than launching Unblob. Public CLI/MCP and actual firmware-engine execution were not run. ## Reproduction Use the reported checkout and locked dependencies, with pinned Node.js and Python 3. Save the following fixture generator as `$WORK/fixture.py`, and the TypeScript probe below as `$WORK/probe.mts`, in an existing disposable directory. Run `python "$WORK/fixture.py" "$WORK"`, then from the checkout run `node node_modules/tsx/dist/cli.mjs "$WORK/probe.mts" "$PWD" "$WORK"`. The generator creates and independently reads back two valid PAX TAR files, each with a single six-byte regular member. The probe creates producer-shaped private extraction output, normalizes its report, then runs production publication and reads the actual committed output. It invokes no external provider and changes no live configuration. ```python import io import pathlib import tarfile import sys root = pathlib.Path(sys.argv[1]) for label, name in [('ascii', 'plain.txt'), ('nfd', 'cafe\u0301.txt')]: archive = root / f'gpu-firmware-{label}.tar' payload = b'alpha\n' with tarfile.open(archive, 'w', format=tarfile.PAX_FORMAT) as tar: member = tarfile.TarInfo(name) member.size = len(payload) member.mode = 0o644 member.mtime = 0 tar.addfile(member, io.BytesIO(payload)) with tarfile.open(archive, 'r') as tar: members = tar.getmembers() assert len(members) == 1 assert members[0].name == name assert tar.extractfile(members[0]).read() == payload print(label, archive.name, archive.stat().st_size, repr(name)) ``` ```ts import { createHash } from 'node:crypto'; import { mkdir, mkdtemp, readFile, readdir, stat, writeFile } from 'node:fs/promises'; import { join } from 'node:path'; import { pathToFileURL } from 'node:url'; const checkout = process.argv[2]; const work = process.argv[3]; const source = (path: string) => pathToFileURL(join(checkout, path)).href; const { FirmwareFileAccess, inventoryFirmwareOutput } = await import(source('src/firmware/FirmwareFiles.ts')); const { normalizeUnblobReport } = await import(source('src/firmware/FirmwareReports.ts')); const { publishFirmwareExtraction } = await import(source('src/firmware/FirmwarePublication.ts')); const { startFirmwareDeadline } = await import(source('src/firmware/FirmwareDeadline.ts')); const root = await mkdtemp(join(work, 'gpu-firmware-nfd-')); const sha = (value: Uint8Array) => createHash('sha256').update(value).digest('hex'); const facts = (path: string, bytes: Uint8Array) => [ { __typename__: 'StatReport', path, size: bytes.length, is_file: true, is_dir: false, is_link: false, link_target: null }, { __typename__: 'HashReport', sha256: sha(bytes) }, { __typename__: 'FileMagicReport', magic: 'ASCII text', mime_type: 'text/plain' }, ]; const observations = []; for (const name of ['plain.txt', 'cafe\u0301.txt']) { const label = name === 'plain.txt' ? 'ascii' : 'nfd'; const workspace = join(root, label); const staged = join(workspace, 'extracted'); await mkdir(staged, { recursive: true }); const inputPath = join(workspace, 'firmware.bin'); const inputBytes = await readFile(join(work, `gpu-firmware-${label}.tar`)); await writeFile(inputPath, inputBytes); const payload = Buffer.from('alpha\n'); const extractDir = join(staged, 'firmware.bin_extract'); await mkdir(extractDir); const childPath = join(extractDir, name); await writeFile(childPath, payload); const child = { path: childPath, depth: 1, blob_id: 'tar-chunk', is_multi_file: false }; const directory = { path: extractDir, depth: 1, blob_id: 'tar-chunk', is_multi_file: false }; const rootTask = { path: inputPath, depth: 0, blob_id: '', is_multi_file: false }; const report = [ { task: rootTask, reports: [ ...facts(inputPath, inputBytes), { __typename__: 'ChunkReport', id: 'tar-chunk', start_offset: 0, end_offset: inputBytes.length, size: inputBytes.length, handler_name: 'tar', is_encrypted: false, extraction_reports: [] }, ], subtasks: [directory] }, { task: directory, reports: [{ __typename__: 'StatReport', path: extractDir, size: 4096, is_file: false, is_dir: true, is_link: false, link_target: null }], subtasks: [child] }, { task: child, reports: facts(childPath, payload), subtasks: [] }, ]; const selection = { offset: 0, length: inputBytes.length, sha256: sha(inputBytes) }; const normalized = normalizeUnblobReport(report, { inputPath, outputRoot: staged, ...selection, maxDepth: 3 }); const entries = await inventoryFirmwareOutput(staged, 4096, 10, 'extract_firmware'); const files = new FirmwareFileAccess(); const deadline = startFirmwareDeadline('publication', 120000); const outputDirectory = join(workspace, 'published'); try { const result = await publishFirmwareExtraction({ files, workspacePath: workspace, deadline, analysisTimeoutMs: 120000, outputDirectory, entries, normalized, selection, maxDepth: 3, engine: { name: 'unblob', version: '26.6.4', executable_path: '/fixture/unblob', executable_sha256: 'a'.repeat(64), source_revision: null, worker_count: 1 }, signal: deadline.signal, retainCleanup: () => { throw new Error('Unexpected cleanup retention'); }, }); const published = result.files[0]; const pathState = await stat(published.path).then(() => 'exists', (error: NodeJS.ErrnoException) => error.code); const actualNames = await readdir(join(outputDirectory, 'firmware.bin_extract')); const actualBytes = await readFile(join(outputDirectory, 'firmware.bin_extract', actualNames[0])); observations.push({ label, fixtureName: name, reportedRelativePath: published.relative_path, reportedPath: published.path, reportedPathState: pathState, actualNames, actualSha256: sha(actualBytes), reportedSha256: published.sha256, coverage: result.coverage, gaps: result.extraction_gaps, derivations: result.derivations }); } finally { await files.close(); } } const output = { runtime: process.version, platform: process.platform, arch: process.arch, root, observations }; await writeFile(join(work, 'gpu-firmware-nfd-result.json'), JSON.stringify(output, null, 2) + '\n'); console.log(JSON.stringify(output, null, 2)); ``` The source-boundary failure and ASCII control were each observed in the delegated initial run and once in my independent replay of these portable scripts, before drafting. This is a complete publication-boundary reproduction, not an end-to-end Unblob reproduction or a claim of formal minimization. ## Expected behavior Every returned regular-file path should resolve to the payload whose hash was verified. If destination normalization changes a name, preserve an explicit consistent relationship between the producer spelling, returned relative path, lineage, and actual published path. REA's [firmware contract](https://github.com/morluto/rea/blob/642f6571ea9d333ea50114c25e648dbb5b8a3c46/docs/firmware-analysis.md#L96-L103) promises regular-file paths with independently verified hashes, and its [child-analysis workflow](https://github.com/morluto/rea/blob/642f6571ea9d333ea50114c25e648dbb5b8a3c46/docs/firmware-analysis.md#L134-L146) passes those paths to subsequent analysis. A successful result should not require callers to discover the real filename by enumerating the destination. ## Impact and scope This is a bounded usability and evidence-identity defect on the tested Linux filesystem: a successfully published child cannot be opened by the exact path REA returns. File content remains intact, and listing the destination and using its actual NFC name is a demonstrated workaround. The probe's one-file case involves no collision, symlink, traversal, special file, failed hash, or cancellation. Other filesystems' normalization behavior and real-engine frequency were not tested; no claim is made that every platform returns `ENOENT`. ## REA version Package 6.7.0; source revision `642f6571ea9d333ea50114c25e648dbb5b8a3c46`. The source package version and HEAD were checked locally; the npm registry latest version was checked before the audit. No installed/published CLI launcher was used. ## Runtime and platform Node.js v24.22.1; locked tsx v4.23.15; Python 3.14.8; Linux x86_64, Omarchy 4.0.6, kernel 7.2.8-9-omarchy. The probe's `engine` object is a synthetic schema-valid receipt, not evidence that Unblob 26.6.4 was installed or executed. No Binwalk, Unblob, Hopper, or Ghidra was run. ## Target and artifact context Two valid 10,240-byte PAX TAR fixtures containing a single ordinary regular member, `plain.txt` or `cafe\u0301.txt`, with content `alpha\n`. Names are synthetic and relative; the producer-shaped staging tree is within a disposable directory. Default-compatible extraction depth 3 and small nonbinding budgets are used, with fresh absent output directories. ## Interface Other ## Earliest failure stage Artifact or evidence registration ## Relevant output or evidence The ASCII control returns `firmware.bin_extract/plain.txt`, and `stat` succeeds. The failing case returns: ```json { "reportedRelativePath": "firmware.bin_extract/cafe\u0301.txt", "reportedPathState": "ENOENT", "actualNames": ["caf\u00e9.txt"], "actualSha256": "b6a98d9ce9a2d9149288fa3df42d377c3e42737afdcdaf714e33c0a100b51060", "reportedSha256": "b6a98d9ce9a2d9149288fa3df42d377c3e42737afdcdaf714e33c0a100b51060", "coverage": "complete", "gaps": [] } ``` The returned absolute path ends in the reported relative spelling, while the committed directory contains only the NFC name. The derived child path also retains the original spelling. - Expected source: The linked firmware regular-file and follow-on analysis contracts above. - Failure source: Executed report/inventory/publication result and filesystem readback from the complete inline probe; [returned path construction](https://github.com/morluto/rea/blob/642f6571ea9d333ea50114c25e648dbb5b8a3c46/src/firmware/FirmwarePublication.ts#L145-L170) and [destination write normalization](https://github.com/morluto/rea/blob/642f6571ea9d333ea50114c25e648dbb5b8a3c46/src/artifacts/SafeOutputTree.ts#L176-L183) identify the mismatched owners. - Evidence provenance: observed. Public provider reachability and the corresponding real TAR producer path are separately source-derived. - Local verification: reproduced. - Reproduction completeness: complete at the publication boundary; actual Unblob execution and public CLI/MCP transport were not run. Valid public trigger and guards: [extract-firmware CLI dispatch](https://github.com/morluto/rea/blob/642f6571ea9d333ea50114c25e648dbb5b8a3c46/src/cli/firmwareCommands.ts#L63-L99) forwards the selected input and absent output destination; [the provider normalizes output, inventories it, then calls this publication function](https://github.com/morluto/rea/blob/642f6571ea9d333ea50114c25e648dbb5b8a3c46/src/firmware/FirmwareProvider.ts#L484-L525). This report has successful regular-file facts, matching digests and byte counts, an ordinary short relative name, no extraction problems, and a fresh destination. Its six bytes and one member fit default limits. The fixture's PAX path is independently checked by Python. For source-derived real-producer reachability, the inspected [Unblob TAR adapter](https://github.com/onekey-sec/unblob/blob/1fcc7a0a584a70a96c31f5a276c20944d199a089/python/unblob/handlers/archive/tar.py#L90-L94) calls [SafeTarFile](https://github.com/onekey-sec/unblob/blob/1fcc7a0a584a70a96c31f5a276c20944d199a089/python/unblob/handlers/archive/_safe_tarfile.py#L110-L200), whose applicable checks admit this regular relative member and pass its spelling to Python extraction without Unicode normalization. This is source evidence, not a claim that the external engine was reproduced. Audit evidence companions: `gpu-firmware-nfd-fixture.py`, `gpu-firmware-nfd-probe.mts`, `gpu-firmware-nfd-result.json`, and `gpu-firmware-nfd-replay.out`. Both necessary scripts are inline above; no private fixture is required. ## Typed outcome or recovery expected Return committed, openable regular-file paths with matching verified hashes and consistent lineage or an explicit producer-to-published name mapping. Proposed restoration check: the returned path must open the six expected bytes and match its declared digest for both Unicode and ASCII cases. Preserve the original member spelling as provenance. No code change or restoration run was made. ## Analysis or suspected cause Source-derived: [inventory preserves the staged spelling](https://github.com/morluto/rea/blob/642f6571ea9d333ea50114c25e648dbb5b8a3c46/src/firmware/FirmwareFiles.ts#L363-L372). [FirmwareFileAccess.publish](https://github.com/morluto/rea/blob/642f6571ea9d333ea50114c25e648dbb5b8a3c46/src/firmware/FirmwareFiles.ts#L173-L189) returns the safe writer's receipt, whose [actual relative path is normalized](https://github.com/morluto/rea/blob/642f6571ea9d333ea50114c25e648dbb5b8a3c46/src/artifacts/SafeOutputTree.ts#L262). However, [firmware publication ignores that receipt](https://github.com/morluto/rea/blob/642f6571ea9d333ea50114c25e648dbb5b8a3c46/src/firmware/FirmwarePublication.ts#L159-L170) and reconstructs paths from the staging name. [Artifact path normalization](https://github.com/morluto/rea/blob/642f6571ea9d333ea50114c25e648dbb5b8a3c46/src/artifacts/ArtifactPaths.ts#L50-L68) uses [NFC](https://github.com/morluto/rea/blob/642f6571ea9d333ea50114c25e648dbb5b8a3c46/src/domain/artifactIdentity.ts#L15-L16), explaining the observed mismatch. ## Additional context The initial full open-title index and all-state firmware/Unblob/Binwalk, Unicode/NFC/decomposed-name, and `FirmwarePublication` searches found no matching firmware-path report. Related [#5108](https://github.com/morluto/rea/issues/5108) concerns losing original member spelling during artifact inspection. This report instead concerns retaining that spelling but falsely returning it as the published filesystem location; preserving #5108's source-name provenance alone would leave this failure unchanged. Existing firmware coverage/admission reports [#3756](https://github.com/morluto/rea/issues/3756), [#5745](https://github.com/morluto/rea/issues/5745), [#5746](https://github.com/morluto/rea/issues/5746), [#5846](https://github.com/morluto/rea/issues/5846), and [#5953](https://github.com/morluto/rea/issues/5953) do not describe this successful-publication path mismatch. Submission-time duplicate searches are performed separately immediately before filing. AI assistance: the investigation, synthetic fixtures, and report were prepared with AI assistance, including delegated firmware research independently reviewed and replayed before drafting. The report contains only synthetic filenames and bytes; no credentials, personal identifiers, or private paths are included. NOT_ENOUGH_INFO: first affected and last known good releases and external-engine reproduction are unavailable. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: Successful firmware publication returns a nonexistent child path on the tested filesystem, blocking direct follow-on use while retaining the correct published content. - Workaround status: available - Workaround basis: Enumerating the committed destination and opening the actual NFC filename returned the expected content and matching SHA-256 in both observed runs.
Author
Owner

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

Checked the reported fix on main at d87e73f2db.

Publication now takes the absolute output path from the SafeOutputTree writer receipt rather than the producer spelling. The original spelling remains in relative_path and logical lineage, so returned path is explicitly distinct from producer provenance. The regression actually opens the returned NFD-name file and compares its bytes; it does not only assert the name.

Implementation: FirmwarePublication.ts, firmwarePublicationPaths.test.ts.

The relevant regression checks in firmwarePublicationPaths.test.ts passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.

<!-- gh-comment-id:6380747708 --> @coygeek commented on GitHub (Oct 24, 2026): Checked the reported fix on `main` at d87e73f2db50be72d33a4b3c7199af35518b441c. Publication now takes the absolute output path from the SafeOutputTree writer receipt rather than the producer spelling. The original spelling remains in relative_path and logical lineage, so returned path is explicitly distinct from producer provenance. The regression actually opens the returned NFD-name file and compares its bytes; it does not only assert the name. Implementation: [FirmwarePublication.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/firmware/FirmwarePublication.ts#L162), [firmwarePublicationPaths.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/tests/boundary/firmware/firmwarePublicationPaths.test.ts#L64). The relevant regression checks in [firmwarePublicationPaths.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/tests/boundary/firmware/firmwarePublicationPaths.test.ts) passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.
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#3986
No description provided.