[GH-ISSUE #287] Define a trusted Windows native authority and packaging boundary #159

Open
opened 2026-10-15 18:21:47 +00:00 by bsakaguchi · 15 comments
Owner

Originally created by @morluto on GitHub (Jul 16, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/287

Problem Statement

REA's Windows Ghidra P0 explicitly does not claim private-DACL enforcement, reparse-point-safe path authority, or Job Object process ownership. Closing those gates requires Win32 handle and process-creation primitives that Node's public filesystem and child-process APIs do not expose as a complete security contract.

The project needs to decide how Windows-native authority is packaged and exposed before implementing the individual controls. Adding isolated PowerShell, icacls, taskkill, or ad hoc native-module calls would leave race windows, duplicate platform policy, and make installation behavior difficult to verify.

Purpose and Benefit

Define one narrow Windows adapter boundary that can:

  • create and verify private filesystem objects using Windows security descriptors;
  • admit files and directories through stable handles without following disallowed reparse points; and
  • launch a process atomically into a dedicated Job Object and retain ownership until cleanup.

One adapter keeps Win32 types out of the domain and application layers, gives doctor and real-Windows verification one capability source, and avoids three unrelated native integration mechanisms.

Architecture Recommendation

Use a platform adapter beneath the existing process and filesystem primitives:

Ghidra/application services
          |
provider-neutral process and filesystem ports
          |
Windows native authority adapter
  - security descriptor creation/readback
  - handle-based path admission and identity
  - atomic job-owned process launch

The adapter should return typed observations and failures. Provider-specific policy remains in the Ghidra adapter; Win32 mechanics and raw handles remain inside the Windows boundary.

Before selecting an implementation, compare:

  1. a prebuilt Node-API addon with reproducible Windows x64 artifacts and package-integrity verification;
  2. a source-built addon, including the installation burden and compiler requirements; and
  3. a source-owned helper executable built in release CI, including protocol, signing, version, and cleanup requirements.

The decision must preserve REA's rule that setup does not install compilers, runtimes, or unrelated software. Committed binaries and shell-command approximations are not acceptable substitutes for a verified packaging design.

Desired Outcome

  • A documented owner boundary and public TypeScript port for the three Windows capabilities.
  • A selected packaging and release path that works in packed npm installations without requiring setup to install build tools.
  • Exact supported Windows/Node architectures and fail-closed behavior when the native capability is unavailable.
  • Package and real-Windows verification for artifact identity, loading, failure diagnostics, and cleanup.
  • No new Win32 details in provider-neutral domain contracts.

Non-goals

  • Implementing the three controls in this decision issue.
  • General Windows process capture or controlled replay.
  • Installing Ghidra, Java, Node.js, or compiler toolchains.
  • Claiming broad Windows parity from the native adapter alone.

Evidence and References

Originally created by @morluto on GitHub (Jul 16, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/287 ## Problem Statement REA's Windows Ghidra P0 explicitly does not claim private-DACL enforcement, reparse-point-safe path authority, or Job Object process ownership. Closing those gates requires Win32 handle and process-creation primitives that Node's public filesystem and child-process APIs do not expose as a complete security contract. The project needs to decide how Windows-native authority is packaged and exposed before implementing the individual controls. Adding isolated PowerShell, icacls, taskkill, or ad hoc native-module calls would leave race windows, duplicate platform policy, and make installation behavior difficult to verify. ## Purpose and Benefit Define one narrow Windows adapter boundary that can: - create and verify private filesystem objects using Windows security descriptors; - admit files and directories through stable handles without following disallowed reparse points; and - launch a process atomically into a dedicated Job Object and retain ownership until cleanup. One adapter keeps Win32 types out of the domain and application layers, gives doctor and real-Windows verification one capability source, and avoids three unrelated native integration mechanisms. ## Architecture Recommendation Use a platform adapter beneath the existing process and filesystem primitives: ```text Ghidra/application services | provider-neutral process and filesystem ports | Windows native authority adapter - security descriptor creation/readback - handle-based path admission and identity - atomic job-owned process launch ``` The adapter should return typed observations and failures. Provider-specific policy remains in the Ghidra adapter; Win32 mechanics and raw handles remain inside the Windows boundary. Before selecting an implementation, compare: 1. a prebuilt Node-API addon with reproducible Windows x64 artifacts and package-integrity verification; 2. a source-built addon, including the installation burden and compiler requirements; and 3. a source-owned helper executable built in release CI, including protocol, signing, version, and cleanup requirements. The decision must preserve REA's rule that setup does not install compilers, runtimes, or unrelated software. Committed binaries and shell-command approximations are not acceptable substitutes for a verified packaging design. ## Desired Outcome - A documented owner boundary and public TypeScript port for the three Windows capabilities. - A selected packaging and release path that works in packed npm installations without requiring setup to install build tools. - Exact supported Windows/Node architectures and fail-closed behavior when the native capability is unavailable. - Package and real-Windows verification for artifact identity, loading, failure diagnostics, and cleanup. - No new Win32 details in provider-neutral domain contracts. ## Non-goals - Implementing the three controls in this decision issue. - General Windows process capture or controlled replay. - Installing Ghidra, Java, Node.js, or compiler toolchains. - Claiming broad Windows parity from the native adapter alone. ## Evidence and References - Parent roadmap: #38 - Current limitations: docs/windows-ghidra-p0.md and docs/roadmap.md - Node/libuv's global job permits silent breakaway and can continue after assignment is denied, so ordinary child_process spawning is not sufficient proof: https://github.com/libuv/libuv/blob/v1.x/src/win/process.c - Win32 supports assigning jobs during process creation with PROC_THREAD_ATTRIBUTE_JOB_LIST on Windows 10/Server 2016 and newer: https://learn.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute - Windows security descriptors and DACLs: https://learn.microsoft.com/en-us/windows/win32/secauthz/security-descriptors - Reparse-point-aware file opening: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
Author
Owner

@morluto commented on GitHub (Jul 16, 2026):

Implementation issues:

  • #288 — Job Object process ownership and kill-on-close
  • #289 — handle-based reparse-safe filesystem authority
  • #290 — private Windows runtime DACL creation and verification
<!-- gh-comment-id:4997461202 --> @morluto commented on GitHub (Jul 16, 2026): Implementation issues: - #288 — Job Object process ownership and kill-on-close - #289 — handle-based reparse-safe filesystem authority - #290 — private Windows runtime DACL creation and verification
Author
Owner

@morluto commented on GitHub (Jul 17, 2026):

Research update and proposed decision (2026-07-17).

Recommendation: use one CI-built Windows x64 Node-API addon distributed as an exact-version, platform-specific optional npm package. The package declares Windows/x64 through npm os/cpu metadata, has no install script, performs no download, and has no node-gyp/source-build fallback. The main package pins the matching optional package version and fails closed with typed capability diagnostics when it is absent (including --omit=optional installations).

Why this fits REA:

  • Node-API provides ABI stability only when the addon stays on Node-API/node-addon-api and avoids direct V8, libuv, and Node internals.
  • REA already verifies packed installation with lifecycle scripts disabled and optional dependencies omitted.
  • A source-built addon violates the compiler-free setup rule.
  • A helper executable adds an authenticated protocol, helper ownership, version negotiation, and cleanup boundary before delivering the three controls.
  • Mature packages such as sharp demonstrate platform-specific optional packages; REA should adopt the selection shape but reject compilation fallback. better-sqlite3's prebuild-install/node-gyp fallback is incompatible with this constraint.

Proposed ordering:

  1. Establish the exact-version platform package, native contract/version handshake, typed loader failures, package verifier cases, and provider-neutral TypeScript port.
  2. Implement #288 independently on that boundary.
  3. Implement #289's shared handle/file-identity primitives.
  4. Implement #290 using #289's handle boundary for protected DACL creation and readback.

A proposed ADR is now drafted in the working tree at docs/adr/0004-windows-native-authority-packaging.md. It keeps #288-#290 unshipped, preserves the limited P0 fallbacks without allowing them to satisfy capability claims, and requires real non-administrator Windows proof.

<!-- gh-comment-id:5001454793 --> @morluto commented on GitHub (Jul 17, 2026): Research update and proposed decision (2026-07-17). Recommendation: use one CI-built Windows x64 Node-API addon distributed as an exact-version, platform-specific optional npm package. The package declares Windows/x64 through npm `os`/`cpu` metadata, has no install script, performs no download, and has no node-gyp/source-build fallback. The main package pins the matching optional package version and fails closed with typed capability diagnostics when it is absent (including `--omit=optional` installations). Why this fits REA: - Node-API provides ABI stability only when the addon stays on Node-API/node-addon-api and avoids direct V8, libuv, and Node internals. - REA already verifies packed installation with lifecycle scripts disabled and optional dependencies omitted. - A source-built addon violates the compiler-free setup rule. - A helper executable adds an authenticated protocol, helper ownership, version negotiation, and cleanup boundary before delivering the three controls. - Mature packages such as sharp demonstrate platform-specific optional packages; REA should adopt the selection shape but reject compilation fallback. better-sqlite3's prebuild-install/node-gyp fallback is incompatible with this constraint. Proposed ordering: 1. Establish the exact-version platform package, native contract/version handshake, typed loader failures, package verifier cases, and provider-neutral TypeScript port. 2. Implement #288 independently on that boundary. 3. Implement #289's shared handle/file-identity primitives. 4. Implement #290 using #289's handle boundary for protected DACL creation and readback. A proposed ADR is now drafted in the working tree at `docs/adr/0004-windows-native-authority-packaging.md`. It keeps #288-#290 unshipped, preserves the limited P0 fallbacks without allowing them to satisfy capability claims, and requires real non-administrator Windows proof.
Author
Owner

@morluto commented on GitHub (Jul 17, 2026):

The proposed packaging ADR was tightened after a Devin CLI audit and current Node documentation review. The platform-package shape remains viable, but #287 is not decided until the process port specifies a complete asynchronous stdio and lifetime design: an addon that calls CreateProcessW must bridge bounded stdio without private V8/libuv APIs, while a helper adds its own authenticated protocol and ownership lifecycle. The delivery order is now #289 handle authority, then #290 private DACLs, then #288 Job Object launch. #288 may be developed separately but cannot advertise the Windows native authority boundary while executable identity or the runtime token root remains unprotected. The ADR stays Proposed and this issue remains open.

<!-- gh-comment-id:5002483989 --> @morluto commented on GitHub (Jul 17, 2026): The proposed packaging ADR was tightened after a Devin CLI audit and current Node documentation review. The platform-package shape remains viable, but #287 is not decided until the process port specifies a complete asynchronous stdio and lifetime design: an addon that calls CreateProcessW must bridge bounded stdio without private V8/libuv APIs, while a helper adds its own authenticated protocol and ownership lifecycle. The delivery order is now #289 handle authority, then #290 private DACLs, then #288 Job Object launch. #288 may be developed separately but cannot advertise the Windows native authority boundary while executable identity or the runtime token root remains unprotected. The ADR stays Proposed and this issue remains open.
Author
Owner

@morluto commented on GitHub (Jul 17, 2026):

The proposed Windows native-authority ADR has been tightened after an independent Devin CLI review. It now requires an application-owned TypeScript port contract and tagged failure taxonomy before native implementation, explicitly blocks pathname reuse after handle admission, requires #287 to choose either protected-copy/revalidation or a digest-admitted helper launch binding for CreateProcessW, and defines the artifact digest as release-generated main-package metadata while limiting its claim to drift detection. It also keeps the process helper versus addon decision open until asynchronous stdio and lifetime behavior are specified, and records delivery order #289 then #290 then #288. The current capability probe now explicitly reports reparse-safe admission unavailable on Windows, and runtime-root documentation no longer implies POSIX mode bits prove Windows privacy. npm run check passes. #287 remains open until the exact exported port schemas and process I/O/lifetime design are accepted.

<!-- gh-comment-id:5002688953 --> @morluto commented on GitHub (Jul 17, 2026): The proposed Windows native-authority ADR has been tightened after an independent Devin CLI review. It now requires an application-owned TypeScript port contract and tagged failure taxonomy before native implementation, explicitly blocks pathname reuse after handle admission, requires #287 to choose either protected-copy/revalidation or a digest-admitted helper launch binding for CreateProcessW, and defines the artifact digest as release-generated main-package metadata while limiting its claim to drift detection. It also keeps the process helper versus addon decision open until asynchronous stdio and lifetime behavior are specified, and records delivery order #289 then #290 then #288. The current capability probe now explicitly reports reparse-safe admission unavailable on Windows, and runtime-root documentation no longer implies POSIX mode bits prove Windows privacy. npm run check passes. #287 remains open until the exact exported port schemas and process I/O/lifetime design are accepted.
Author
Owner

@morluto commented on GitHub (Jul 17, 2026):

Implemented the provider-neutral, fail-closed TypeScript prerequisite on the current working tree; this does not close #287 or enable any Windows native-authority claim.

Delivered in this slice:

  • Added a provisional WindowsNativeAuthority application boundary for admitted file/directory capabilities, verified private roots, and Job-owned processes without exposing Win32 DTOs.
  • Scoped retained backend resources to the authority instance that issued them. Foreign, forged, and already-closed values fail before backend dispatch; close is idempotent.
  • Kept file and directory admission distinct at the TypeScript boundary and limited the initial policy to reparsePolicy: "reject_all" / reparseDisposition: "absent".
  • Applied one absolute deadline/cancellation control to admission, private-root creation, and process startup.
  • Required continuously drained, byte-bounded stdout/stderr with detached snapshots reporting retained bytes, total bytes, and truncation. Cancelling waitForExit cancels only that wait, not process ownership.
  • Added a built-in unavailable backend with no fabricated identity and a separate native_authority Windows capability outcome that remains unavailable.
  • Added ADR-0004 covering the compiler-free optional platform-package shape, exact-version/digest loading checks, real-Windows verification gates, and the dependency order for #289 → #290 → #288.
  • Added deterministic tests for fail-closed absence, authority-instance isolation, closed-resource rejection, deadline propagation, bounded snapshots, and idempotent cleanup.

Validation on the final tree:

  • npm run docs:generate
  • npm run check
  • git diff --check

npm run check passed typecheck, lint (existing warnings only), formatting, generated-document checks, and the full Vitest suite. Coverage remained above repository thresholds.

Remaining before #287 can be accepted:

  1. Choose and specify addon-only versus packaged-helper process creation, including complete lifetime and I/O behavior.
  2. Choose the admitted-executable launch binding; CreateProcessW cannot launch from the retained admission handle, and falling back to the original untrusted path is not acceptable.
  3. Implement and publish the exact Windows x64 optional package with no install script or source-build fallback, then validate version/contract/Node-API/artifact-digest rejection paths.
  4. Wire environment cleanup as a backstop and prove the published package plus real Windows behavior on the self-hosted non-administrator runner.

The ADR therefore remains Proposed, the host capability remains fail-closed, and #288–#290 remain blocked on this design/packaging prerequisite.

<!-- gh-comment-id:5003408227 --> @morluto commented on GitHub (Jul 17, 2026): Implemented the provider-neutral, fail-closed TypeScript prerequisite on the current working tree; this does **not** close #287 or enable any Windows native-authority claim. Delivered in this slice: - Added a provisional `WindowsNativeAuthority` application boundary for admitted file/directory capabilities, verified private roots, and Job-owned processes without exposing Win32 DTOs. - Scoped retained backend resources to the authority instance that issued them. Foreign, forged, and already-closed values fail before backend dispatch; close is idempotent. - Kept file and directory admission distinct at the TypeScript boundary and limited the initial policy to `reparsePolicy: "reject_all"` / `reparseDisposition: "absent"`. - Applied one absolute deadline/cancellation control to admission, private-root creation, and process startup. - Required continuously drained, byte-bounded stdout/stderr with detached snapshots reporting retained bytes, total bytes, and truncation. Cancelling `waitForExit` cancels only that wait, not process ownership. - Added a built-in unavailable backend with no fabricated identity and a separate `native_authority` Windows capability outcome that remains unavailable. - Added ADR-0004 covering the compiler-free optional platform-package shape, exact-version/digest loading checks, real-Windows verification gates, and the dependency order for #289 → #290 → #288. - Added deterministic tests for fail-closed absence, authority-instance isolation, closed-resource rejection, deadline propagation, bounded snapshots, and idempotent cleanup. Validation on the final tree: - `npm run docs:generate` - `npm run check` - `git diff --check` `npm run check` passed typecheck, lint (existing warnings only), formatting, generated-document checks, and the full Vitest suite. Coverage remained above repository thresholds. Remaining before #287 can be accepted: 1. Choose and specify addon-only versus packaged-helper process creation, including complete lifetime and I/O behavior. 2. Choose the admitted-executable launch binding; `CreateProcessW` cannot launch from the retained admission handle, and falling back to the original untrusted path is not acceptable. 3. Implement and publish the exact Windows x64 optional package with no install script or source-build fallback, then validate version/contract/Node-API/artifact-digest rejection paths. 4. Wire environment cleanup as a backstop and prove the published package plus real Windows behavior on the self-hosted non-administrator runner. The ADR therefore remains **Proposed**, the host capability remains fail-closed, and #288–#290 remain blocked on this design/packaging prerequisite.
Author
Owner

@morluto commented on GitHub (Jul 17, 2026):

Design review update: the initially considered retained share-lock launch binding has been rejected, and ADR-0004 remains Proposed.

The exact-diff review found two substitution gaps:

  • FILE_SHARE_READ blocks later conflicting opens but cannot revoke a writable mapped view established before admission. The mapped view can mutate bytes after hashing even after its write handle closes.
  • Binding only lpApplicationName does not bind the Windows Ghidra runtime closure: the command interpreter, batch script, Java executable/runtime, Ghidra JARs/native libraries/configuration, and DLL search inputs can load code after the primary image is admitted.

The ADR now requires either (a) a complete protected runtime projection built from admitted handles with constrained DLL/runtime search, or (b) an explicitly narrower same-user threat boundary backed by verified immutable installation ACLs and filesystem identity for the entire closure. The real-Windows verifier must cover pre-existing writable mappings, hard-link alias writes, ordinary/POSIX rename-delete, reparse and DLL-search substitution, and post-admission script/JAR/native/config changes. There is no pathname fallback.

The process design was also tightened:

  • exact minimum is Windows 10 / Windows Server 2016 x64, with runtime probing and no post-creation Job assignment fallback;
  • one attribute list carries both PROC_THREAD_ATTRIBUTE_HANDLE_LIST and PROC_THREAD_ATTRIBUTE_JOB_LIST;
  • process notification uses a size-one nonblocking TSFN with coalescing, explicit acquire/release, abort-on-teardown, and an async cleanup hook;
  • parent duplicates of child pipe ends close immediately after launch; readers drain to EOF after Job close, with CancelSynchronousIo only at the cleanup deadline; read handles and accumulators remain owned until their reader threads join.

Validation after the corrections:

  • npm run docs:check
  • git diff --check

Both pass. The optional prebuilt Node-API platform-package shape remains selected, but #287 cannot be accepted until the complete Ghidra/Java launch authority is selected and proven.

<!-- gh-comment-id:5003539463 --> @morluto commented on GitHub (Jul 17, 2026): Design review update: the initially considered retained share-lock launch binding has been rejected, and ADR-0004 remains **Proposed**. The exact-diff review found two substitution gaps: - `FILE_SHARE_READ` blocks later conflicting opens but cannot revoke a writable mapped view established before admission. The mapped view can mutate bytes after hashing even after its write handle closes. - Binding only `lpApplicationName` does not bind the Windows Ghidra runtime closure: the command interpreter, batch script, Java executable/runtime, Ghidra JARs/native libraries/configuration, and DLL search inputs can load code after the primary image is admitted. The ADR now requires either (a) a complete protected runtime projection built from admitted handles with constrained DLL/runtime search, or (b) an explicitly narrower same-user threat boundary backed by verified immutable installation ACLs and filesystem identity for the entire closure. The real-Windows verifier must cover pre-existing writable mappings, hard-link alias writes, ordinary/POSIX rename-delete, reparse and DLL-search substitution, and post-admission script/JAR/native/config changes. There is no pathname fallback. The process design was also tightened: - exact minimum is Windows 10 / Windows Server 2016 x64, with runtime probing and no post-creation Job assignment fallback; - one attribute list carries both `PROC_THREAD_ATTRIBUTE_HANDLE_LIST` and `PROC_THREAD_ATTRIBUTE_JOB_LIST`; - process notification uses a size-one nonblocking TSFN with coalescing, explicit acquire/release, abort-on-teardown, and an async cleanup hook; - parent duplicates of child pipe ends close immediately after launch; readers drain to EOF after Job close, with `CancelSynchronousIo` only at the cleanup deadline; read handles and accumulators remain owned until their reader threads join. Validation after the corrections: - `npm run docs:check` - `git diff --check` Both pass. The optional prebuilt Node-API platform-package shape remains selected, but #287 cannot be accepted until the complete Ghidra/Java launch authority is selected and proven.
Author
Owner

@morluto commented on GitHub (Jul 17, 2026):

Resolved the remaining architecture question in ADR-0004 without claiming implementation completion.

The selected binding is a complete protected runtime projection, not share locks or immutable admission of the operator's BYO installation:

  • project the entire Ghidra 12.1.2 installation root, entire admitted JDK 21 root, REA bridge/session inputs, and target snapshot through handle-relative, reparse-rejecting native I/O;
  • hash during copy, reopen/read back every destination, and commit a canonical (relative path, size, SHA-256) tree digest;
  • seal executable/classpath/native-library inputs with a protected DACL including an OWNER RIGHTS restriction, while REA retains non-inherited authority/cleanup handles opened before sealing;
  • launch projected java.exe directly as ghidra.Ghidra ghidra.app.util.headless.AnalyzeHeadless, eliminating cmd.exe, analyzeHeadless.bat, launch.bat, and LaunchSupport from the runtime closure;
  • sanitize Java/Ghidra option variables and search paths, use only the projected JDK/native directories plus canonical System32, and apply/prove the image-load mitigation policy;
  • prove source mapped-write/hard-link/rename attacks, DACL owner rewrite, handle inheritance, reparse substitution, DLL search substitution, and original-tree mutation on the real Windows runner.

This research also exposed a real port gap: the current TypeScript contract accepts a source WindowsAdmittedFile plus an unsealed WindowsPrivateRoot at spawn. The ADR now requires a type-state revision where handle-relative projection is built and read back, sealing returns an opaque WindowsRuntimeProjection and projected entry point, and process spawn accepts only that sealed pair. #289 owns projection/readback, #290 sealing/DACL proof, and #288 consumes the sealed result.

Independent Devin CLI review agreed with the full-projection, OWNER RIGHTS, retained-handle, direct-Java, and search-closure design. I rejected its proposed Windows 1809 floor: the Microsoft SDK defines IMAGE_LOAD_NO_REMOTE, NO_LOW_LABEL, and PREFER_SYSTEM32 in bits 52–61 of the first DWORD64; the Windows 10 1703 rule applies to the optional two-element policy array. Runtime probing and fail-closed real-runner proof remain required for the documented Windows 10/Server 2016 floor.

Validation: npm run docs:check, repository formatting, and git diff --check pass. ADR-0004 remains Proposed and #287 remains open until the type-state port revision, package loader/artifact verification, and real-Windows proof exist.

<!-- gh-comment-id:5003826783 --> @morluto commented on GitHub (Jul 17, 2026): Resolved the remaining architecture question in ADR-0004 without claiming implementation completion. The selected binding is a complete protected runtime projection, not share locks or immutable admission of the operator's BYO installation: - project the entire Ghidra 12.1.2 installation root, entire admitted JDK 21 root, REA bridge/session inputs, and target snapshot through handle-relative, reparse-rejecting native I/O; - hash during copy, reopen/read back every destination, and commit a canonical `(relative path, size, SHA-256)` tree digest; - seal executable/classpath/native-library inputs with a protected DACL including an `OWNER RIGHTS` restriction, while REA retains non-inherited authority/cleanup handles opened before sealing; - launch projected `java.exe` directly as `ghidra.Ghidra ghidra.app.util.headless.AnalyzeHeadless`, eliminating `cmd.exe`, `analyzeHeadless.bat`, `launch.bat`, and `LaunchSupport` from the runtime closure; - sanitize Java/Ghidra option variables and search paths, use only the projected JDK/native directories plus canonical System32, and apply/prove the image-load mitigation policy; - prove source mapped-write/hard-link/rename attacks, DACL owner rewrite, handle inheritance, reparse substitution, DLL search substitution, and original-tree mutation on the real Windows runner. This research also exposed a real port gap: the current TypeScript contract accepts a source `WindowsAdmittedFile` plus an unsealed `WindowsPrivateRoot` at spawn. The ADR now requires a type-state revision where handle-relative projection is built and read back, sealing returns an opaque `WindowsRuntimeProjection` and projected entry point, and process spawn accepts only that sealed pair. #289 owns projection/readback, #290 sealing/DACL proof, and #288 consumes the sealed result. Independent Devin CLI review agreed with the full-projection, `OWNER RIGHTS`, retained-handle, direct-Java, and search-closure design. I rejected its proposed Windows 1809 floor: the Microsoft SDK defines `IMAGE_LOAD_NO_REMOTE`, `NO_LOW_LABEL`, and `PREFER_SYSTEM32` in bits 52–61 of the first `DWORD64`; the Windows 10 1703 rule applies to the optional two-element policy array. Runtime probing and fail-closed real-runner proof remain required for the documented Windows 10/Server 2016 floor. Validation: `npm run docs:check`, repository formatting, and `git diff --check` pass. ADR-0004 remains Proposed and #287 remains open until the type-state port revision, package loader/artifact verification, and real-Windows proof exist.
Author
Owner

@morluto commented on GitHub (Jul 17, 2026):

Implemented the provider-neutral TypeScript port revision required by the selected ADR design, while keeping this issue open for the native package and real-Windows proof.

Current tree now commits these boundary properties:

  • path admission returns authority-scoped file/directory capabilities;
  • private-root creation consumes admitted handles and produces an immutable sealed projection whose launch arguments/environment are committed before sealing;
  • process spawn accepts only that sealed projection (no source executable, path, argv, environment, or entrypoint can be substituted at spawn time);
  • resource leases defer native close across in-flight projection and spawn calls and reject new work once close begins;
  • every mutable projection/launch/control/limit container is synchronously copied before the first asynchronous boundary;
  • backend resource-token identity is enforced across authority wrappers, including duplicate and cross-kind rejection;
  • malformed aliased root/job results are poisoned and synchronously cleaned before returning native_contract_mismatch;
  • the unavailable adapter remains fail closed.

Regression coverage includes foreign authority rejection, post-seal source closure, root closure, in-flight close races, caller mutation after invocation, duplicate/cross-kind tokens, cross-authority token aliasing, and aliased root/job cleanup.

Validation on the final tree:

  • npm run typecheck
  • npx vitest run --coverage.enabled=false tests/windowsNativeAuthority.test.ts (5/5)
  • npx oxlint src/application/WindowsNativeAuthority.ts tests/windowsNativeAuthority.test.ts (only the already-tracked file-length inventory remains)
  • npm run docs:generate
  • npm run check (full build, lint, formatting, generated-doc checks, and test suite passed)

Remaining acceptance work is still the native Windows implementation and package/runtime proof described by ADR-0004: reparse-safe handle admission/projection, protected DACL construction, direct projected-JDK launch, Job Object ownership, mitigations, packaging/signing, and the adversarial real-Windows verifier.

<!-- gh-comment-id:5004140656 --> @morluto commented on GitHub (Jul 17, 2026): Implemented the provider-neutral TypeScript port revision required by the selected ADR design, while keeping this issue open for the native package and real-Windows proof. Current tree now commits these boundary properties: - path admission returns authority-scoped file/directory capabilities; - private-root creation consumes admitted handles and produces an immutable sealed projection whose launch arguments/environment are committed before sealing; - process spawn accepts only that sealed projection (no source executable, path, argv, environment, or entrypoint can be substituted at spawn time); - resource leases defer native close across in-flight projection and spawn calls and reject new work once close begins; - every mutable projection/launch/control/limit container is synchronously copied before the first asynchronous boundary; - backend resource-token identity is enforced across authority wrappers, including duplicate and cross-kind rejection; - malformed aliased root/job results are poisoned and synchronously cleaned before returning native_contract_mismatch; - the unavailable adapter remains fail closed. Regression coverage includes foreign authority rejection, post-seal source closure, root closure, in-flight close races, caller mutation after invocation, duplicate/cross-kind tokens, cross-authority token aliasing, and aliased root/job cleanup. Validation on the final tree: - npm run typecheck - npx vitest run --coverage.enabled=false tests/windowsNativeAuthority.test.ts (5/5) - npx oxlint src/application/WindowsNativeAuthority.ts tests/windowsNativeAuthority.test.ts (only the already-tracked file-length inventory remains) - npm run docs:generate - npm run check (full build, lint, formatting, generated-doc checks, and test suite passed) Remaining acceptance work is still the native Windows implementation and package/runtime proof described by ADR-0004: reparse-safe handle admission/projection, protected DACL construction, direct projected-JDK launch, Job Object ownership, mitigations, packaging/signing, and the adversarial real-Windows verifier.
Author
Owner

@morluto commented on GitHub (Jul 17, 2026):

Follow-up package-loader research found a security dependency that the earlier ADR text did not state precisely.

A pre-hash, pathname require, post-hash loader cannot prove which native image executed:

  • the .node path can be replaced after the first read, loaded, and restored before the second read;
  • a post-load mismatch is detected only after unverified native initializer code has run; and
  • the CommonJS native-module cache can return an older in-memory image unrelated to the bytes currently at the resolved path.

I prototyped and independently reviewed that loader shape, then removed it because presenting it as execution authorization would be unsafe. ADR-0004 now explicitly rejects this construction. The loader remains part of #287, but it must wait for the #289/#290 protected projection and retained authority, reject any pre-existing foreign cache entry, and then establish one process-global loaded identity. npm integrity remains the installation authenticity boundary; digest comparison is drift evidence, not a substitute for binding the bytes that execute.

The sealed provider-neutral port and its tests are unchanged. npm run docs:generate and the full npm run check pass after the ADR correction.

<!-- gh-comment-id:5004315665 --> @morluto commented on GitHub (Jul 17, 2026): Follow-up package-loader research found a security dependency that the earlier ADR text did not state precisely. A pre-hash, pathname require, post-hash loader cannot prove which native image executed: - the .node path can be replaced after the first read, loaded, and restored before the second read; - a post-load mismatch is detected only after unverified native initializer code has run; and - the CommonJS native-module cache can return an older in-memory image unrelated to the bytes currently at the resolved path. I prototyped and independently reviewed that loader shape, then removed it because presenting it as execution authorization would be unsafe. ADR-0004 now explicitly rejects this construction. The loader remains part of #287, but it must wait for the #289/#290 protected projection and retained authority, reject any pre-existing foreign cache entry, and then establish one process-global loaded identity. npm integrity remains the installation authenticity boundary; digest comparison is drift evidence, not a substitute for binding the bytes that execute. The sealed provider-neutral port and its tests are unchanged. npm run docs:generate and the full npm run check pass after the ADR correction.
Author
Owner

@morluto commented on GitHub (Jul 17, 2026):

Resolved the circular native-addon bootstrap requirement in ADR-0004 and moved the ADR to Accepted. This is an architecture decision, not a native-capability claim, and #287 remains open for implementation and package proof.

The previous text was unimplementable: it required the Windows addon to project itself through #289/#290 before first load, but only the unloaded addon could perform the needed handle-relative Win32 operations.

The accepted boundary is now explicit:

  • npm registry/package-lock integrity authenticates the installed release set;
  • release-generated main-package metadata binds the exact platform package version and addon SHA-256 for runtime drift/release-pair detection;
  • the loader verifies digest, package/version, native contract, Node-API level, and exact export set before native evaluation;
  • it rejects a pre-existing require.cache entry for the exact canonical resolved addon path, then pins the accepted module/path/identity tuple process-wide;
  • REA completes this bootstrap before constructing Hopper/Ghidra/provider clients, admitting a target, or running provider/target code;
  • after bootstrap, #289/#290 project and seal every non-system provider/runtime byte before direct projected-Java launch;
  • the addon is not recursively required to project itself.

The limitation is equally explicit: a hash/path/load handshake cannot resist an already-running process under the same Windows user that can race package replacement, inject into REA, or preload an alias outside the exact observable cache entry. REA does not claim that stronger bootstrap boundary. A future claim against that attacker requires a signed/protected launcher or native main executable and is outside #287–#290.

Package-verifier acceptance now requires mismatch-before-evaluation, zero provider/target activity on failure, idempotent same-tuple loading, rejection of another observable path/identity, omitted-package fail-closed behavior, and sealed projection before launch. It also keeps npm authenticity distinct from the release digest's drift-detection role.

Research used current Node 22 Node-API/addon loading documentation plus DeepWiki comparisons of sharp and esbuild's platform-specific optional-package patterns. REA adopts exact-version os/cpu selection while rejecting source builds, install scripts, downloads, or fallback compilation.

Independent follow-up review: clean. Formatting and git diff --check pass.

Remaining #287 work:

  1. choose the final platform package name and Node-API level;
  2. add release-generated native artifact metadata and the fail-closed loader;
  3. add the no-script/no-download Windows x64 package skeleton and package verifier cases;
  4. build the native #289 → #290 → #288 implementation and obtain the real non-administrator Windows proof.
<!-- gh-comment-id:5008282880 --> @morluto commented on GitHub (Jul 17, 2026): Resolved the circular native-addon bootstrap requirement in ADR-0004 and moved the ADR to **Accepted**. This is an architecture decision, not a native-capability claim, and #287 remains open for implementation and package proof. The previous text was unimplementable: it required the Windows addon to project itself through #289/#290 before first load, but only the unloaded addon could perform the needed handle-relative Win32 operations. The accepted boundary is now explicit: - npm registry/package-lock integrity authenticates the installed release set; - release-generated main-package metadata binds the exact platform package version and addon SHA-256 for runtime drift/release-pair detection; - the loader verifies digest, package/version, native contract, Node-API level, and exact export set before native evaluation; - it rejects a pre-existing require.cache entry for the exact canonical resolved addon path, then pins the accepted module/path/identity tuple process-wide; - REA completes this bootstrap before constructing Hopper/Ghidra/provider clients, admitting a target, or running provider/target code; - after bootstrap, #289/#290 project and seal every non-system provider/runtime byte before direct projected-Java launch; - the addon is not recursively required to project itself. The limitation is equally explicit: a hash/path/load handshake cannot resist an already-running process under the same Windows user that can race package replacement, inject into REA, or preload an alias outside the exact observable cache entry. REA does not claim that stronger bootstrap boundary. A future claim against that attacker requires a signed/protected launcher or native main executable and is outside #287–#290. Package-verifier acceptance now requires mismatch-before-evaluation, zero provider/target activity on failure, idempotent same-tuple loading, rejection of another observable path/identity, omitted-package fail-closed behavior, and sealed projection before launch. It also keeps npm authenticity distinct from the release digest's drift-detection role. Research used current Node 22 Node-API/addon loading documentation plus DeepWiki comparisons of sharp and esbuild's platform-specific optional-package patterns. REA adopts exact-version os/cpu selection while rejecting source builds, install scripts, downloads, or fallback compilation. Independent follow-up review: clean. Formatting and git diff --check pass. Remaining #287 work: 1. choose the final platform package name and Node-API level; 2. add release-generated native artifact metadata and the fail-closed loader; 3. add the no-script/no-download Windows x64 package skeleton and package verifier cases; 4. build the native #289 → #290 → #288 implementation and obtain the real non-administrator Windows proof.
Author
Owner

@morluto commented on GitHub (Jul 17, 2026):

Implemented and validated the fail-closed Windows native-authority package loader slice on the current working tree.

Delivered in this slice:

  • release-generated package identity metadata for the exact Windows x64 package tuple;
  • Windows x64 and Node-API admission checks before package resolution;
  • strict platform-manifest, canonical addon path, require-cache, SHA-256, contract-version, identity, and exact-export validation;
  • one process-owned production loader with concurrent-load coalescing, idempotent success, and pinned failure;
  • tests proving unsupported/uncommitted/omitted/drifted packages fail closed and duplicate concurrent evaluation cannot occur;
  • ADR wording that distinguishes checks performed before addon evaluation from runtime export/identity checks performed immediately afterward.

Validation: npm run docs:generate && npm run check passed (160 test files, 1,089 tests; 82.03% statement coverage). The independent exact-diff follow-up review was clean within the ADR’s accepted bootstrap threat boundary.

This is intentionally dormant: the release artifact commitment remains null, so the loader fails before package resolution. #287 remains open for the exact optional platform package, native addon implementation, release build/publish and digest-injection integration, packed-install verification, and real non-administrator Windows proof. No capability is advertised by this slice.

<!-- gh-comment-id:5008404687 --> @morluto commented on GitHub (Jul 17, 2026): Implemented and validated the fail-closed Windows native-authority package loader slice on the current working tree. Delivered in this slice: - release-generated package identity metadata for the exact Windows x64 package tuple; - Windows x64 and Node-API admission checks before package resolution; - strict platform-manifest, canonical addon path, require-cache, SHA-256, contract-version, identity, and exact-export validation; - one process-owned production loader with concurrent-load coalescing, idempotent success, and pinned failure; - tests proving unsupported/uncommitted/omitted/drifted packages fail closed and duplicate concurrent evaluation cannot occur; - ADR wording that distinguishes checks performed before addon evaluation from runtime export/identity checks performed immediately afterward. Validation: npm run docs:generate && npm run check passed (160 test files, 1,089 tests; 82.03% statement coverage). The independent exact-diff follow-up review was clean within the ADR’s accepted bootstrap threat boundary. This is intentionally dormant: the release artifact commitment remains null, so the loader fails before package resolution. #287 remains open for the exact optional platform package, native addon implementation, release build/publish and digest-injection integration, packed-install verification, and real non-administrator Windows proof. No capability is advertised by this slice.
Author
Owner

@morluto commented on GitHub (Jul 18, 2026):

The accepted provider-neutral boundary and dormant package loader are now proposed in #305. The loader validates exact package metadata, platform/Node-API/manifest identity, canonical path, digest, export shape, concurrency, and pinned failure; malformed native process observations are runtime-parsed and fail closed. artifactSha256 remains null, so no native capability is advertised. The issue remains open for the native package, release integration, and real non-admin Windows proof required by #288–#290.

<!-- gh-comment-id:5008813792 --> @morluto commented on GitHub (Jul 18, 2026): The accepted provider-neutral boundary and dormant package loader are now proposed in #305. The loader validates exact package metadata, platform/Node-API/manifest identity, canonical path, digest, export shape, concurrency, and pinned failure; malformed native process observations are runtime-parsed and fail closed. `artifactSha256` remains `null`, so no native capability is advertised. The issue remains open for the native package, release integration, and real non-admin Windows proof required by #288–#290.
Author
Owner

@morluto commented on GitHub (Jul 20, 2026):

Split into focused child issues:

These replace the broad remaining scope in this issue. Closing this parent can happen once all children are closed.

<!-- gh-comment-id:5023380249 --> @morluto commented on GitHub (Jul 20, 2026): Split into focused child issues: - [ ] #318 - [ ] #319 These replace the broad remaining scope in this issue. Closing this parent can happen once all children are closed.
Author
Owner

@tony-sebastian commented on GitHub (Oct 5, 2026):

Hi — I've been working on exactly this boundary and would like to contribute the implementation upstream. Happy to take it on if you'd like to assign it.

A bit of context: I hunt bugs on Android/Windows apps, and when a target ships a native component there was no way to inspect it through REA on Windows, because the Ghidra provider is gated off (the contradiction in #527). So I implemented the missing controls and verified them end to end.

What I built (draft branch, verified on Windows x64 + Ghidra 12.1.4 + JDK 21):

  • bridge/windows/ReaWindowsAuthority.cs — a native authority helper shipped as C# source, following this project's bridge precedent (Python for Hopper, Java for Ghidra, Swift for macOS). It compiles on first use with the Windows system compiler (csc.exe), caches under ~/.rea/cache, and is pinned to the source SHA-256 — no binary in the repo, no new toolchain required. REA_WINAUTH_HELPER accepts a prebuilt binary for CI/enterprise pinning.
  • spawn — kill-on-close Job Object, child assigned between CREATE_SUSPENDED and ResumeThread; the helper holds the only job handle, so terminating it terminates the whole tree (addresses #288).
  • dacl create|verify — protected current-user-only DACL verified by security-descriptor readback (addresses #290).
  • pathcheck — handle-based reparse-safe admission, with a positive junction-rejection test (addresses #289).
  • capabilities — self-test emitting native-authority proofs; the adapter fails closed unless all three controls prove in-process (resolves the gate contradiction in #527).
  • Runtime wiring: owned spawns route through the helper, PrivateRuntimeRoot uses the verified DACL, and the launcher DACL-protects runtime dirs and admits analyzeHeadless/bridge/target paths pre-spawn.
  • One latent launch bug surfaced on the first real Windows run: the quoted GHIDRA_HEADLESS_JAVA_OPTIONS form breaks analyzeHeadless.bat's parser ("The syntax of the command is incorrect"); the unquoted form is the only batch-safe spelling.

Verification: differential against published rea-agents@3.2.1 — same command, same environment, same PE target: the published package rejects with unsupported_host, my branch returns a real Ghidra 12.1.4 evidence overview. 120/120 tests, including win32-gated integration tests for job-spawn routing and private runtime roots.

Packaging questions for maintainers (the core of #287): I chose compile-on-first-use to avoid committing binaries; if you prefer a signed release artifact or a different trust anchor, the TS side accepts any binary at the REA_WINAUTH_HELPER override and can land independently.

I'd like to work on this upstream — happy to split or rework the branch however you prefer (TS-side only, helper as a release artifact, etc.) and will open a PR from a fork on your go-ahead.

<!-- gh-comment-id:5999018247 --> @tony-sebastian commented on GitHub (Oct 5, 2026): Hi — I've been working on exactly this boundary and would like to contribute the implementation upstream. Happy to take it on if you'd like to assign it. A bit of context: I hunt bugs on Android/Windows apps, and when a target ships a native component there was no way to inspect it through REA on Windows, because the Ghidra provider is gated off (the contradiction in #527). So I implemented the missing controls and verified them end to end. **What I built (draft branch, verified on Windows x64 + Ghidra 12.1.4 + JDK 21):** - `bridge/windows/ReaWindowsAuthority.cs` — a native authority helper shipped as C# source, following this project's bridge precedent (Python for Hopper, Java for Ghidra, Swift for macOS). It compiles on first use with the Windows system compiler (`csc.exe`), caches under `~/.rea/cache`, and is pinned to the source SHA-256 — no binary in the repo, no new toolchain required. `REA_WINAUTH_HELPER` accepts a prebuilt binary for CI/enterprise pinning. - `spawn` — kill-on-close Job Object, child assigned between CREATE_SUSPENDED and ResumeThread; the helper holds the only job handle, so terminating it terminates the whole tree (addresses #288). - `dacl create|verify` — protected current-user-only DACL verified by security-descriptor readback (addresses #290). - `pathcheck` — handle-based reparse-safe admission, with a positive junction-rejection test (addresses #289). - `capabilities` — self-test emitting `native-authority` proofs; the adapter fails closed unless all three controls prove in-process (resolves the gate contradiction in #527). - Runtime wiring: owned spawns route through the helper, `PrivateRuntimeRoot` uses the verified DACL, and the launcher DACL-protects runtime dirs and admits analyzeHeadless/bridge/target paths pre-spawn. - One latent launch bug surfaced on the first real Windows run: the quoted `GHIDRA_HEADLESS_JAVA_OPTIONS` form breaks `analyzeHeadless.bat`'s parser ("The syntax of the command is incorrect"); the unquoted form is the only batch-safe spelling. **Verification:** differential against published `rea-agents@3.2.1` — same command, same environment, same PE target: the published package rejects with `unsupported_host`, my branch returns a real Ghidra 12.1.4 evidence overview. 120/120 tests, including win32-gated integration tests for job-spawn routing and private runtime roots. **Packaging questions for maintainers (the core of #287):** I chose compile-on-first-use to avoid committing binaries; if you prefer a signed release artifact or a different trust anchor, the TS side accepts any binary at the `REA_WINAUTH_HELPER` override and can land independently. I'd like to work on this upstream — happy to split or rework the branch however you prefer (TS-side only, helper as a release artifact, etc.) and will open a PR from a fork on your go-ahead.
Author
Owner

@N0zoM1z0 commented on GitHub (Oct 5, 2026):

Thanks for the detailed write-up — the real-Windows validation and adversarial test cases are especially useful.

The remaining work under #287 is now split into #318 and #319, and #288–#290 are already assigned. If you're happy to align with ADR-0004, #318 would be the best place to start. A focused PR there, carrying over the relevant Windows proof/test cases you've already built, would be very welcome.

<!-- gh-comment-id:5999146536 --> @N0zoM1z0 commented on GitHub (Oct 5, 2026): Thanks for the detailed write-up — the real-Windows validation and adversarial test cases are especially useful. The remaining work under #287 is now split into #318 and #319, and #288–#290 are already assigned. If you're happy to align with ADR-0004, #318 would be the best place to start. A focused PR there, carrying over the relevant Windows proof/test cases you've already built, would be very welcome.
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#159
No description provided.