[GH-ISSUE #5611] [Bug] Kuna readiness can pair a reported version with different executable bytes #3403

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

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

Before submitting

  • I searched open and closed issues for a related report. No matching version/hash-pairing report was found. #5557 and #5587 cover a distinct output-drain timeout.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Provider or capability discovery

Observed behavior

At source revision 90e433df06, inspectKunaInstallation runs the selected executable with --version, then hashes the executable path after the child completes. A test-owned executable that atomically replaces itself between those steps returned success with version 1.833 and SHA-256 of replacement bytes whose version output is 1.900. The result also had verified=true because 1.833 is the configured verified release string. This flag means the version string matches that release value; it does not authenticate the distribution.

A stable executable positive control returned version 1.833 and a digest matching the same bytes before and after probing.

Reproduction

From the repository root with pinned Node 24.18.0, save the following as .cache/codebase-audit/kuna-pairing-probe.mjs and run:

node --import tsx .cache/codebase-audit/kuna-pairing-probe.mjs

It uses only temporary fake executables, production source inspection, and the existing Kuna file-close owner test helper. The temporary fixture/cache directory is removed in finally.

import { createHash } from "node:crypto";
import { chmod, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { inspectKunaInstallation } from "../../src/kuna/KunaInstallation.ts";
import { withKunaFileCloseOwner } from "../../tests/boundary/providers/kuna/kunaFileCloseOwner.ts";

const hash = (bytes) => createHash("sha256").update(bytes).digest("hex");
const root = await mkdtemp(join(tmpdir(), "rea-kuna-pairing-"));
try {
  const interpreter = process.execPath;
  const replacement = `#!${interpreter}\nconsole.log("kuna 1.900");\n`;
  const cases = [
    ["stable", `#!${interpreter}\nconsole.log("kuna 1.833");\n`],
    ["changing", [
      `#!${interpreter}`,
      'const fs = require("node:fs");',
      `const next = ${JSON.stringify(replacement)};`,
      'console.log("kuna 1.833");',
      'fs.writeFileSync(process.argv[1] + ".new", next, { mode: 0o700 });',
      'fs.renameSync(process.argv[1] + ".new", process.argv[1]);',
      "",
    ].join("\n")],
  ];
  for (const [name, source] of cases) {
    const executable = join(root, name);
    await writeFile(executable, source, { mode: 0o700 });
    await chmod(executable, 0o700);
    const before = hash(await readFile(executable));
    const result = await withKunaFileCloseOwner((fileCloseResources) =>
      inspectKunaInstallation(
        { REA_KUNA_COMMAND: executable, REA_KUNA_CACHE_DIR: join(root, name + "-cache") },
        { fileCloseResources },
      ),
    );
    const after = hash(await readFile(executable));
    console.log(JSON.stringify({
      name,
      result: result.ok ? "success" : result.error.code,
      version: result.ok ? result.value.version : null,
      verified: result.ok ? result.value.verified : null,
      reported_sha256: result.ok ? result.value.sha256 : null,
      pre_sha256: before,
      post_sha256: after,
      bytes_changed: before !== after,
    }));
  }
} finally {
  await rm(root, { recursive: true, force: true });
}

Observed in two independent source-boundary runs on Node v24.18.0, macOS arm64 (same stable and replacement results):

  • Stable control: success, version 1.833, verified true, digest unchanged and matching the file.
  • Self-replacing case: success, version 1.833, verified true, but pre-probe digest f347c71a634cc44211be5cc8024d19f88a20b71bca9e76833af9708b4d22884e changed to the reported post-probe digest 4bcfa8b9da647c2ba190015061b2a1a8881b741519acb8d9f1122d58cb739a54.

Expected behavior

Readiness should not return a successful installation/profile when the executable identity changes across the version probe. It should return a typed unresolved/unavailable result (or preserve the identity as unknown) and let the caller retry after the selected executable is stable. This expectation follows the current source description of one installation being identified by both version and bytes; automatic retry behavior is not established.

Impact and scope

The provider profile and Kuna project-cache identity combine the version and digest. A mismatch can label the current executable's analysis/cache identity with a version reported by different bytes. KunaCommand's later check accepts the current executable because it matches the post-probe digest. This reproduction establishes the behavior for a caller-selected self-replacing fake script; it does not establish that a real Kuna release or common package-manager shim self-replaces during --version. Prevalence is NOT_ENOUGH_INFO.

REA version

Package 6.7.0; source revision 90e433df06.

Runtime and platform

Node v24.18.0; macOS arm64.

Target and artifact context

No analysis target. The executable is a test-owned temporary Node shebang script only.

Interface

Other (direct source-boundary probe of the provider installation inspector).

Earliest failure stage

Setup or provider admission (installation/profile inspection).

Relevant output or evidence

The complete executed probe and raw result are retained in .cache/codebase-audit/kuna-version-fingerprint-race-refresh46/ (probe.mjs, probe.stdout.json, probe.stderr.log). The probe imports the exact production inspector and uses the existing withKunaFileCloseOwner helper. The temporary executable directory is removed in a finally block.

Source at the tested commit:

Typed outcome or recovery expected

Return a typed version_unresolved/unavailable outcome rather than successful identity when the probe's reported version cannot be bound to the executable bytes inspected. A caller may rerun readiness after the executable settles.

Analysis or suspected cause

Source-derived: version output is captured at KunaInstallation.ts:216-242; the path is then read and hashed at lines 250-275. The stable-file hash checks that its own read is stable, not that it refers to the file content which produced the earlier version output. The controlled replacement demonstrates the gap. The intended behavior of an actual auto-updating Kuna shim is NOT_ENOUGH_INFO.

Additional context

Open issue #5557 and PR #5587 address descendant-held stdout causing a delayed version-probe timeout; that work changes output draining and does not cover version/hash identity pairing. No fix or real Kuna-distribution check has been performed.

Originally created by @morluto on GitHub (Oct 20, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5611 ## Before submitting - [x] I searched open and closed issues for a related report. No matching version/hash-pairing report was found. #5557 and #5587 cover a distinct output-drain timeout. - [x] I removed credentials, tokens, private paths, and sensitive target data. ## Area Provider or capability discovery ## Observed behavior At source revision 90e433df06b3f83417176b8db1b0a905f9df1d3a, inspectKunaInstallation runs the selected executable with --version, then hashes the executable path after the child completes. A test-owned executable that atomically replaces itself between those steps returned success with version 1.833 and SHA-256 of replacement bytes whose version output is 1.900. The result also had verified=true because 1.833 is the configured verified release string. This flag means the version string matches that release value; it does not authenticate the distribution. A stable executable positive control returned version 1.833 and a digest matching the same bytes before and after probing. ## Reproduction From the repository root with pinned Node 24.18.0, save the following as .cache/codebase-audit/kuna-pairing-probe.mjs and run: node --import tsx .cache/codebase-audit/kuna-pairing-probe.mjs It uses only temporary fake executables, production source inspection, and the existing Kuna file-close owner test helper. The temporary fixture/cache directory is removed in finally. ~~~js import { createHash } from "node:crypto"; import { chmod, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { join } from "node:path"; import { tmpdir } from "node:os"; import { inspectKunaInstallation } from "../../src/kuna/KunaInstallation.ts"; import { withKunaFileCloseOwner } from "../../tests/boundary/providers/kuna/kunaFileCloseOwner.ts"; const hash = (bytes) => createHash("sha256").update(bytes).digest("hex"); const root = await mkdtemp(join(tmpdir(), "rea-kuna-pairing-")); try { const interpreter = process.execPath; const replacement = `#!${interpreter}\nconsole.log("kuna 1.900");\n`; const cases = [ ["stable", `#!${interpreter}\nconsole.log("kuna 1.833");\n`], ["changing", [ `#!${interpreter}`, 'const fs = require("node:fs");', `const next = ${JSON.stringify(replacement)};`, 'console.log("kuna 1.833");', 'fs.writeFileSync(process.argv[1] + ".new", next, { mode: 0o700 });', 'fs.renameSync(process.argv[1] + ".new", process.argv[1]);', "", ].join("\n")], ]; for (const [name, source] of cases) { const executable = join(root, name); await writeFile(executable, source, { mode: 0o700 }); await chmod(executable, 0o700); const before = hash(await readFile(executable)); const result = await withKunaFileCloseOwner((fileCloseResources) => inspectKunaInstallation( { REA_KUNA_COMMAND: executable, REA_KUNA_CACHE_DIR: join(root, name + "-cache") }, { fileCloseResources }, ), ); const after = hash(await readFile(executable)); console.log(JSON.stringify({ name, result: result.ok ? "success" : result.error.code, version: result.ok ? result.value.version : null, verified: result.ok ? result.value.verified : null, reported_sha256: result.ok ? result.value.sha256 : null, pre_sha256: before, post_sha256: after, bytes_changed: before !== after, })); } } finally { await rm(root, { recursive: true, force: true }); } ~~~ Observed in two independent source-boundary runs on Node v24.18.0, macOS arm64 (same stable and replacement results): - Stable control: success, version 1.833, verified true, digest unchanged and matching the file. - Self-replacing case: success, version 1.833, verified true, but pre-probe digest f347c71a634cc44211be5cc8024d19f88a20b71bca9e76833af9708b4d22884e changed to the reported post-probe digest 4bcfa8b9da647c2ba190015061b2a1a8881b741519acb8d9f1122d58cb739a54. ## Expected behavior Readiness should not return a successful installation/profile when the executable identity changes across the version probe. It should return a typed unresolved/unavailable result (or preserve the identity as unknown) and let the caller retry after the selected executable is stable. This expectation follows the current source description of one installation being identified by both version and bytes; automatic retry behavior is not established. ## Impact and scope The provider profile and Kuna project-cache identity combine the version and digest. A mismatch can label the current executable's analysis/cache identity with a version reported by different bytes. KunaCommand's later check accepts the current executable because it matches the post-probe digest. This reproduction establishes the behavior for a caller-selected self-replacing fake script; it does not establish that a real Kuna release or common package-manager shim self-replaces during --version. Prevalence is NOT_ENOUGH_INFO. ## REA version Package 6.7.0; source revision 90e433df06b3f83417176b8db1b0a905f9df1d3a. ## Runtime and platform Node v24.18.0; macOS arm64. ## Target and artifact context No analysis target. The executable is a test-owned temporary Node shebang script only. ## Interface Other (direct source-boundary probe of the provider installation inspector). ## Earliest failure stage Setup or provider admission (installation/profile inspection). ## Relevant output or evidence The complete executed probe and raw result are retained in .cache/codebase-audit/kuna-version-fingerprint-race-refresh46/ (probe.mjs, probe.stdout.json, probe.stderr.log). The probe imports the exact production inspector and uses the existing withKunaFileCloseOwner helper. The temporary executable directory is removed in a finally block. Source at the tested commit: - KunaInstallation.ts runs --version before hashing the path: https://github.com/morluto/rea/blob/90e433df06b3f83417176b8db1b0a905f9df1d3a/src/kuna/KunaInstallation.ts#L213-L275 - The installation result returns parsed version and later fingerprint together: https://github.com/morluto/rea/blob/90e433df06b3f83417176b8db1b0a905f9df1d3a/src/kuna/KunaInstallation.ts#L323-L330 - Provider analysis profile commits version and SHA-256: https://github.com/morluto/rea/blob/90e433df06b3f83417176b8db1b0a905f9df1d3a/src/kuna/KunaProvider.ts#L212-L231 - Project cache key commits both: https://github.com/morluto/rea/blob/90e433df06b3f83417176b8db1b0a905f9df1d3a/src/kuna/KunaProjectCache.ts#L397-L398 - Execution verifies only that current bytes still match the recorded digest: https://github.com/morluto/rea/blob/90e433df06b3f83417176b8db1b0a905f9df1d3a/src/kuna/KunaCommand.ts#L140-L168 ## Typed outcome or recovery expected Return a typed version_unresolved/unavailable outcome rather than successful identity when the probe's reported version cannot be bound to the executable bytes inspected. A caller may rerun readiness after the executable settles. ## Analysis or suspected cause Source-derived: version output is captured at KunaInstallation.ts:216-242; the path is then read and hashed at lines 250-275. The stable-file hash checks that its own read is stable, not that it refers to the file content which produced the earlier version output. The controlled replacement demonstrates the gap. The intended behavior of an actual auto-updating Kuna shim is NOT_ENOUGH_INFO. ## Additional context Open issue #5557 and PR #5587 address descendant-held stdout causing a delayed version-probe timeout; that work changes output draining and does not cover version/hash identity pairing. No fix or real Kuna-distribution check has been performed.
bsakaguchi 2026-10-31 00:40:00 +00:00
  • closed this issue
  • added the
    triage
    bug
    labels
Author
Owner

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

Cross-referencing with canonical issue #5105 and siblings #4624, #5554, #5568. Addresses Kuna provider executable hash pairing and readiness verification. Cataloged under Cluster BD in consolidation tracker #4330.

<!-- gh-comment-id:6295985563 --> @matbrgz commented on GitHub (Oct 20, 2026): Cross-referencing with canonical issue #5105 and siblings #4624, #5554, #5568. Addresses Kuna provider executable hash pairing and readiness verification. Cataloged under Cluster BD in consolidation 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#3403
No description provided.