Skip to content

fix(journey): on Windows the org scope reads a file id through a handle — on Node 22.16.x (libuv 1.49.2) acme read beta's file beside a workspace named through a share - #172

Merged
MoebiusX merged 1 commit into
developfrom
codex/journey-scope-through-handle
Oct 20, 2026
Merged

MoebiusX merged 1 commit into
developfrom
codex/journey-scope-through-handle

Conversation

@MoebiusX

Copy link
Copy Markdown
Owner

Follow-up to #165: crawlScopeRefusal in tools/lib/journey.mjs, which server/store/paths.mjs calls its twin, read file ids with statSync. On Windows they are now read through a handle.

The bug: it failed open

While the workspace is named through a share this host serves (OBSERVOGRAM_WORKSPACE=\myhost\ws, or a drive mapped to \localhost\C$\ws), a local path outside the workspace is placed by its nearest directory that has the workspace's file id. libuv 1.49.0 to 1.50.x (Node 22.16.x ships 1.49.2) stat a local path by name and report dev 0 on NTFS. The workspace, reached through its share, is opened instead and reports the volume serial. So no local directory ever matched the workspace, placed() returned null, and the path counted as "outside the workspace, on a local drive", which nobody owns, so it was read. Result: acme's journey read beta's C:\ws\orgs\beta\packs\b1.pack.yaml. That is a fail-open, not the false refusal the original brief expected; I checked it on the ntfs fixture with fastDev: 0n.

Fix

Tests

  • tools/test-journey-scope.mjs now passes the fixture's handles (onVolume(volume)) to every scope it builds.
  • New test: it loops libuv 1.51.0 (fastDev: null) and 1.49.2 (fastDev: 0n), with the workspace named \myhost\ws or Z:\. It checks that beta's file in every local spelling is refused to acme, that acme's own files and C:\repo\… are read, and that every handle is closed. The host also offers a by-name statSync, so a regression back to a by-name read would fail. At the parent commit this test fails on the 1.49.2 iteration only.

Gates (local, Windows, Node 22.16.0)

🤖 Generated with Claude Code

…le — on Node 22.16.x (libuv 1.49.2) a local path beside a workspace named through a share was never placed, and acme read beta's file

crawlScopeRefusal placed a local path in a workspace named through a share
(\myhost\ws, a drive mapped to \localhost\C$\ws) by a file id read with
statSync. libuv answers a stat of a local path by its name, and 1.49.0 to
1.50.x report there the upper half of the file's 128-bit id as the device,
0 on NTFS, while the workspace through its share is opened and reports the
serial: no local directory was the workspace's own, so beta's
C:\ws\orgs\beta\... read as outside the workspace, nobody's part, and an
acme journey loaded it (fails open).

A file id is now read through a handle (openSync, fstatSync, closeSync),
as server/store/paths.mjs does since #165; the three are injectable in
place of statSync. tools/test-journey-scope.mjs injects the ntfs fixture's
handles, and a new test loops libuv 1.51.0 and 1.49.2; at the parent it
fails on 1.49.2 alone. VENDOR-MANIFEST.json is unchanged (exports are).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MoebiusX
MoebiusX merged commit acad640 into develop Oct 20, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant