[PR #1824] [MERGED] fix(artifacts): mark Windows launchable files as host executables #4840

Closed
opened 2026-10-31 00:55:45 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/1824
Author: @dollarsmoney
Created: 10/11/2026
Status: ✅ Merged
Merged: 10/11/2026
Merged by: @morluto

Base: main ← Head: fix/windows-host-executable


📝 Commits (2)

  • cc1d50d fix(artifacts): mark Windows launchable files as host executables
  • fd660f8 fix(artifacts): resolve Windows executable marking conflicts

📊 Changes

1 file changed (+15 additions, -0 deletions)

View changed files

📝 src/artifacts/hostExecutableMarking.test.ts (+15 -0)

📄 Description

Summary

Fixes #1819.

On a Windows host, every occurrence built from a host file reported executable: false, including real PE images. The same occurrence also reported artifact_kind: executable and artifact_format: pe, so the record contradicted itself. Host-file occurrences now report executable: true for files Windows launches directly. POSIX hosts behave as before.

Problem and expected behavior

DirectoryArtifactReader (every directory entry) and scanCanonical (the root occurrence) both derived the fact as (metadata.mode & 0o111) !== 0. Windows has no execute permission bit. libuv fills stat.mode from file attributes only (0o666, or 0o444 when read-only), so the test can never be true on Windows.

Expected: executable states whether the file carries the host's executable marking. On POSIX that is the execute bits, as before. On Windows it is the file extension that the shell and CreateProcess launch directly.

Change and scope

  • New pure helper src/artifacts/hostExecutable.ts, hostExecutable(path, metadata, platform = process.platform):
    • POSIX: (mode & 0o111) !== 0 (unchanged).
    • win32: a regular file whose case-insensitive extension is .exe, .com, .bat or .cmd. These are the extensions proposed in the issue.
  • Both host-file call sites now use the helper, so the rule is derived in one place.
  • The extension set is fixed. PATHEXT is deliberately not read, because it is ambient environment configuration.
  • Not included: archive-derived facts (ZIP/ASAR/Mach-O slices). They keep their stored or format-derived metadata. PATHEXT script-host extensions (.vbs, .js, .wsf, …) are not launched directly and are out of scope.

Contract and boundary impact

  • Semantic owner and earliest changed stage: artifact inventory, host-file occurrence construction (src/artifacts/).
  • CLI and MCP/tool-catalog contract: none. The field and its schema are unchanged; on Windows hosts its value is now accurate.
  • Provider, bridge, target-format, or platform compatibility: Windows hosts only. POSIX behavior is unchanged.
  • Evidence, artifact, provenance, or reconstruction contract: the executable occurrence fact on Windows-hosted inventories. compare_artifacts can now report permission differences for Windows-hosted inventories (src/domain/artifactComparison.ts).
  • Process execution, authorization, cleanup, or containment impact: none.
  • Generated metadata, package, or installation impact: none.

Evidence and regression coverage

  • Tests added or updated: src/artifacts/hostExecutable.test.ts, which exercises the derivation with an explicit platform so the Windows branch runs on any CI host:
    • POSIX: execute bits set and unset, including a .exe without execute bits staying false.
    • Windows .exe / .EXE / .Cmd / .bat / .com with mode 0o666 and read-only 0o444 → true.
    • Windows .dll, .txt, extensionless, archive.exe.bak and a directory named bundle.exe → false.
  • Base reproduction or other evidence: source-derived from the two cited call sites and the issue's Windows reproduction. With the base derivation, every Windows case above evaluates to false because the mode carries no execute bits.
  • User-visible CLI/MCP output: on macOS, rea inspect-artifact still reports the root occurrence as "executable":true for a 0755 copy of /bin/ls and "executable":false for a 0644 copy named plain.exe. The POSIX behavior is unchanged.
  • Remaining proof gaps: not run on a real Windows host. The windows-curated CI lane would provide that coverage.

For evidence-bearing changes:

  • Observed, derived, and inferred claims remain distinguishable.
  • Artifact identity, source provenance, and failed attempts remain preserved.
  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

All on macOS 15 (Darwin 24.3.0, arm64), Node 26.9.0:

  • npm run test:local -- src/artifacts/hostExecutable.test.ts — 3 tests passed
  • npm run test:focused -- tests/boundary/filesystem/artifactInventorySnapshot.test.ts tests/boundary/filesystem/directoryArtifactReaderOpen.test.ts tests/boundary/filesystem/directoryArtifactReaderOwnership.test.ts tests/boundary/filesystem/artifactMzInventory.test.ts — 17 tests passed
  • npm run verify:test-discovery — discovered all 835 test files exactly once
  • npm run check:fast — typecheck and lint passed (also ran as the pre-push hook)
  • npm run build:cached, then node scripts/rea.mjs inspect-artifact <file> --json on the two macOS copies above — results as listed

Compatibility, safety, and release

  • Breaking changes or migration steps: none.
  • Real Hopper/Ghidra, browser, or OS coverage: macOS only. No real Windows host run. No providers involved.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: reads only the existing lstat metadata and path; no new filesystem access or environment reads.

Review checklist

  • This PR addresses a concrete problem or an agreed enhancement.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/1824 **Author:** [@dollarsmoney](https://github.com/dollarsmoney) **Created:** 10/11/2026 **Status:** ✅ Merged **Merged:** 10/11/2026 **Merged by:** [@morluto](https://github.com/morluto) **Base:** `main` ← **Head:** `fix/windows-host-executable` --- ### 📝 Commits (2) - [`cc1d50d`](https://github.com/morluto/rea/commit/cc1d50d30ef2cb42fbe5d18c850260257b0afb46) fix(artifacts): mark Windows launchable files as host executables - [`fd660f8`](https://github.com/morluto/rea/commit/fd660f836f9751502325b51ef6e9f0569c8fd242) fix(artifacts): resolve Windows executable marking conflicts ### 📊 Changes **1 file changed** (+15 additions, -0 deletions) <details> <summary>View changed files</summary> 📝 `src/artifacts/hostExecutableMarking.test.ts` (+15 -0) </details> ### 📄 Description ## Summary Fixes #1819. On a Windows host, every occurrence built from a host file reported `executable: false`, including real PE images. The same occurrence also reported `artifact_kind: executable` and `artifact_format: pe`, so the record contradicted itself. Host-file occurrences now report `executable: true` for files Windows launches directly. POSIX hosts behave as before. ## Problem and expected behavior `DirectoryArtifactReader` (every directory entry) and `scanCanonical` (the root occurrence) both derived the fact as `(metadata.mode & 0o111) !== 0`. Windows has no execute permission bit. libuv fills `stat.mode` from file attributes only (`0o666`, or `0o444` when read-only), so the test can never be true on Windows. Expected: `executable` states whether the file carries the host's executable marking. On POSIX that is the execute bits, as before. On Windows it is the file extension that the shell and `CreateProcess` launch directly. ## Change and scope - New pure helper `src/artifacts/hostExecutable.ts`, `hostExecutable(path, metadata, platform = process.platform)`: - POSIX: `(mode & 0o111) !== 0` (unchanged). - `win32`: a regular file whose case-insensitive extension is `.exe`, `.com`, `.bat` or `.cmd`. These are the extensions proposed in the issue. - Both host-file call sites now use the helper, so the rule is derived in one place. - The extension set is fixed. `PATHEXT` is deliberately not read, because it is ambient environment configuration. - Not included: archive-derived facts (ZIP/ASAR/Mach-O slices). They keep their stored or format-derived metadata. `PATHEXT` script-host extensions (`.vbs`, `.js`, `.wsf`, …) are not launched directly and are out of scope. ## Contract and boundary impact - Semantic owner and earliest changed stage: artifact inventory, host-file occurrence construction (`src/artifacts/`). - CLI and MCP/tool-catalog contract: none. The field and its schema are unchanged; on Windows hosts its value is now accurate. - Provider, bridge, target-format, or platform compatibility: Windows hosts only. POSIX behavior is unchanged. - Evidence, artifact, provenance, or reconstruction contract: the `executable` occurrence fact on Windows-hosted inventories. `compare_artifacts` can now report permission differences for Windows-hosted inventories (`src/domain/artifactComparison.ts`). - Process execution, authorization, cleanup, or containment impact: none. - Generated metadata, package, or installation impact: none. ## Evidence and regression coverage - Tests added or updated: `src/artifacts/hostExecutable.test.ts`, which exercises the derivation with an explicit platform so the Windows branch runs on any CI host: - POSIX: execute bits set and unset, including a `.exe` without execute bits staying `false`. - Windows `.exe` / `.EXE` / `.Cmd` / `.bat` / `.com` with mode `0o666` and read-only `0o444` → `true`. - Windows `.dll`, `.txt`, extensionless, `archive.exe.bak` and a directory named `bundle.exe` → `false`. - Base reproduction or other evidence: source-derived from the two cited call sites and the issue's Windows reproduction. With the base derivation, every Windows case above evaluates to `false` because the mode carries no execute bits. - User-visible CLI/MCP output: on macOS, `rea inspect-artifact` still reports the root occurrence as `"executable":true` for a `0755` copy of `/bin/ls` and `"executable":false` for a `0644` copy named `plain.exe`. The POSIX behavior is unchanged. - Remaining proof gaps: not run on a real Windows host. The `windows-curated` CI lane would provide that coverage. For evidence-bearing changes: - [x] Observed, derived, and inferred claims remain distinguishable. - [x] Artifact identity, source provenance, and failed attempts remain preserved. - [x] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ## Validation performed All on macOS 15 (Darwin 24.3.0, arm64), Node 26.9.0: - `npm run test:local -- src/artifacts/hostExecutable.test.ts` — 3 tests passed - `npm run test:focused -- tests/boundary/filesystem/artifactInventorySnapshot.test.ts tests/boundary/filesystem/directoryArtifactReaderOpen.test.ts tests/boundary/filesystem/directoryArtifactReaderOwnership.test.ts tests/boundary/filesystem/artifactMzInventory.test.ts` — 17 tests passed - `npm run verify:test-discovery` — discovered all 835 test files exactly once - `npm run check:fast` — typecheck and lint passed (also ran as the pre-push hook) - `npm run build:cached`, then `node scripts/rea.mjs inspect-artifact <file> --json` on the two macOS copies above — results as listed ## Compatibility, safety, and release - Breaking changes or migration steps: none. - Real Hopper/Ghidra, browser, or OS coverage: macOS only. No real Windows host run. No providers involved. - Package or release metadata impact: none. - Security, privacy, process, or containment review: reads only the existing `lstat` metadata and path; no new filesystem access or environment reads. ## Review checklist - [x] This PR addresses a concrete problem or an agreed enhancement. - [x] The PR has one focused outcome and the title follows `type(scope): outcome`. - [x] Related issue is linked, or the reason for not linking one is stated above. - [x] Tests cover changed observable behavior and meaningful failure paths. - [x] Owning docs, contracts, and generated metadata are updated where needed. - [x] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-31 00:55:45 +00:00
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#4840
No description provided.