Repository navigation
feat(kernel-platforms)!: vend fs as a narrowable exo - #1135
Merged
Merged
Conversation
This was referenced Oct 1, 2026
Contributor
Coverage Report
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ci-belphegor
marked this pull request as ready for review
October 2, 2026 16:27
ci-belphegor
force-pushed
the
grypez/narrowing-fs
branch
from
October 2, 2026 17:53
c75c2f6 to
fd25e23
Compare
ci-belphegor
force-pushed
the
grypez/narrowing-lib
branch
from
October 2, 2026 17:53
f6172fa to
e0ec8cb
Compare
ci-belphegor
force-pushed
the
grypez/narrowing-fs
branch
2 times, most recently
from
October 6, 2026 20:50
367d565 to
7cfdfda
Compare
rekmarks-consensys-1
self-requested a review
October 7, 2026 16:39
rekmarks-consensys-1
requested changes
Oct 7, 2026
rekmarks-consensys-1
left a comment
Contributor
There was a problem hiding this comment.
This is known, but we should plug the symlink-in-root vulnerability: https://claude.ai/code/artifact/165dcb1f-32a4-49bc-a77d-479fc6b30369
ci-belphegor
force-pushed
the
grypez/narrowing-fs
branch
from
October 7, 2026 21:49
7cfdfda to
9d25e85
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 9d25e85. Configure here.
Remove `existsSync` from the fs capability, its config field, and the synchronous operation wrapper it was the only user of. Narrowed methods always forward through `E()` and every derived guard is `M.callWhen`, so a synchronous method cannot survive narrowing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replace the hardened record of caveated functions with an exo built from explicit `M.interface` method guards, and flatten the `promises.readFile` config nesting into a flat `methods` list to match the exo's flat method namespace. Paths stay strings. Guards are explicit rather than `makeDefaultExo`'s `defaultGuards: 'passable'` because narrowing conjoins a delta onto a per-argument guard, so there has to be one to conjoin onto. They are `M.callWhen` because narrowed methods forward through `E()` and every derived guard is asyncified. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Methods take absolute segment arrays, guarded as `M.arrayOf(M.string())`, and `rootDir` becomes a segment `root` checked as a prefix. `makeRootCaveat` moves to the shared module now that it needs no path APIs. A guard can only require strings, so `['srv', 'data/../../etc']` would arrive intact and resolve to `/etc`. Segments carrying a separator, a traversal, or nothing at all are therefore rejected before the prefix check, which is what makes that check sufficient. An empty `root` is a config error. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The capability factory builds the full fs exo and narrows it by a delta compiled from the config, so `root` and the method set are enforced by the same `narrow` a holder would use. A holder narrowing further flattens onto that base instead of stacking a second mechanism on it. `makeRootCaveat` and the hand-rolled method selection are gone; `narrowInterfaceGuard` already drops methods absent from the delta. `assertPlainSegments` stays and is load-bearing: `pathUnder` matches segment by segment and cannot see inside one, so `['srv', 'x/../../etc']` satisfies the config's pattern and is caught only by the base's well-formedness check, which the narrowing inherits by forwarding. `readFile` now requires an encoding. Without one Node resolves a `Buffer`, and no typed array is Passable even frozen, so the result could never cross the exo boundary — a limit only real lockdown reveals. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Grant the narrowing vat a config-scoped fs, and check that the vat can read through it, that the config's scope holds, that the capability's segment check survives a pattern that admits the segment, and that the vat can narrow the fs further. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ition The Node.js caveat checked only the final component with `lstat`, so a directory symlink inside the root let a holder read outside it. Require each path to be its own `realpath` instead, which refuses a symlink at any position and a case alias. The base cannot see which prefix a holder was narrowed to, so it refuses every alias rather than re-checking one root. `readFile` opens with `O_NOFOLLOW`, re-checks, and requires the path to still name the opened file, closing the race with a writer who swaps in a symlink after the check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Node.js kernel no longer boots on Windows (#1157), so `toPath` joins on `/` rather than resolving a leading drive segment, and the docs and changelog stop describing `['C:', 'srv']` roots. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`pathUnder([])` admits every path, and the config struct's `nonempty` does not run when `capabilityFactory` is called directly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ci-belphegor
force-pushed
the
grypez/narrowing-fs
branch
from
October 8, 2026 15:05
d0890d0 to
d2af93a
Compare
rekmarks-consensys-1
approved these changes
Oct 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Explanation
Reworks the
fsplatform capability in@metamask/kernel-platformsinto a narrowable exo, then enforces its config as a narrowing. This is one breaking change:fsis an exo with explicit per-method guards, called asawait E(fs).readFile(['srv', 'data', 'x'], 'utf8'). It replaces thenode:fslookalike record.{ root: ['srv', 'data'], methods: ['readFile'] }, replacing{ rootDir, promises: { readFile } }.existsSyncincluded, are gone, since a narrowed method always forwards throughE().narrow(base, { readFile: [pathUnder(root)] }). The configured bound is then the root of the narrowing tree, and a vat that narrowsfsfurther composes with it.The commits are kept as review steps: promises-only, exo shape and guards, segment paths, config as a narrowing, then the fs end-to-end cases. Intermediate states (e.g. the segment-based
makeRootCaveatthat the last step deletes) never reachmain.Notes for reviewers
['srv', 'data/../../etc']passesM.arrayOf(M.string())and the['srv']prefix, then resolves to/etc. Patterns cannot see inside a string, soassertPlainSegmentsrejects empty,.,.., and any segment containing/or\at the one chokepoint shared by every platform. A narrowing inherits the check by forwarding, and an e2e case proves it through the config pattern.readFilerequires an encoding and resolves a string. This was forced:@endo/pass-stylerejectsBufferandUint8Arrayas mutable, so a byte result cannot cross an exo boundary.harden(buf.transferToImmutable())passes asbyteArray, but it isn't guaranteed atengines: >=22, so raw reads are deferred.makeDefaultExo. Narrowing needs a per-argument guard to conjoin onto.defaultGuardsis omitted, so an unlisted method is uncallable.M.any()admits only Passables. An options record carrying anAbortSignalis now rejected. No caller in this repo passes one, and the changelog names it.toPathusesresolve(sep, ...segments), so a platform prefix is a leading segment (['C:', 'srv']). SinceassertPlainSegmentsruns first,resolvenever has anything to collapse.lstatfollows intermediate components, somakeNoSymlinksCaveatcatches only a symlink in final position.mock-endoify(sameHandledPromiseissue asnarrow).makeFsBaseis therefore split out and unit-tested in-package, and the composition is tested in@ocap/kernel-test.References
Checklist
🤖 Generated with Claude Code
Note
High Risk
Breaking filesystem platform API and async factory wiring for all vats using
platformConfig.fs, plus security-sensitive path/symlink handling where misconfiguration or edge cases could block legitimate reads or leave residual alias races onaccess.Overview
Breaking redesign of the
fsplatform capability in@metamask/kernel-platforms: it is now an@endo/exowith explicit method guards, called viaE(fs)with absolute path segment arrays (e.g.readFile(['srv', 'data', 'x'], 'utf8')). The oldnode:fs-shaped API (rootDir,promises.*,existsSync, sync helpers) is removed;readFilerequires an encoding and returns a string for Passable exo boundaries; capability factories andmakePlatformare async because config is applied withnarrowand a compiledpathUnder(root)delta instead of ad-hoc root/symlink caveats.Security and validation move to shared
assertPlainSegments(reject.,.., separators inside segments) plus Noderealpathcanonical checks and a hardenedreadFile(O_NOFOLLOW, re-check after open). Docs and changelog describe remaining limits (e.g.accessTOCTOU, hard links).Tests and integration: unit tests target
makeFsBasewheremock-endoifycannot runnarrow;@ocap/kernel-testadds vat probes for config-scopedfs, further narrowing, symlink refusal, and traversal-in-segment rejection. Dependencies add@endo/exo,@endo/patterns, and@metamask/kernel-utils.Reviewed by Cursor Bugbot for commit d2af93a. Bugbot is set up for automated code reviews on this repo. Configure here.