fix(mcp): grant Deno --allow-net for the execute control socket - #43
Open
Miyamura80 wants to merge 2 commits into
Open
fix(mcp): grant Deno --allow-net for the execute control socket#43Miyamura80 wants to merge 2 commits into
Miyamura80 wants to merge 2 commits into
Conversation
The execute tool runs model-supplied code in a Deno subprocess, and
@valtown/deno-http-worker talks to that subprocess over a Unix socket served
with `Deno.serve({ path })`. Since Deno 2.9, binding a Unix socket requires net
access in addition to read/write access, so the worker died at startup with:
NotCapable: Requires net access to "unix:/.../<uuid>-deno-http.sock",
run again with the --allow-net flag
which surfaced as "Deno exited before being ready" on every execute call.
The socket path is generated inside the worker library and is not exposed to
callers, so grant net access by patching the argv the library builds: find the
socket path it passes to the bootstrap script and append `unix:<path>` to the
existing --allow-net allowlist. Deno's allowlist matches a Unix socket only by
its full path, so this is as tight as the permission can be scoped, and the
sandbox is otherwise unchanged — code running in the worker still reaches only
the API base URL host.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H7CWSE6BSsshnh7rZNgomB
yargs treats a single argument to .version() as the version string, so .version(true) made `mcp-server --version` print "true". Calling .version() with no arguments lets yargs read the version out of package.json. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H7CWSE6BSsshnh7rZNgomB
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📜 Recent review details🔇 Additional comments (2)
📝 WalkthroughSummary by CodeRabbit
WalkthroughThe Deno worker now receives scoped network access for its Unix control socket. The yargs configuration now uses default version output behavior. ChangesDeno control-socket access
Yargs version output
Estimated code review effort: 2 (Simple) | ~10 minutes Sequence Diagram(s)sequenceDiagram
participant codeTool
participant spawnFunc
participant DenoWorker
codeTool->>spawnFunc: provide generated worker arguments
spawnFunc->>spawnFunc: append scoped unix control-socket access
spawnFunc->>DenoWorker: launch with patched arguments
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Author
|
@batuhan would appreciate it if you could please review! Thank you |
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.
Fixes #42
Root cause
The
executetool runs model-supplied code in a Deno subprocess via@valtown/deno-http-worker. That library opens its control channel withDeno.serve({ path: <socket under $TMPDIR> }). Since Deno 2.9, binding a Unix socket requires net access on top of read/write access — and the library appends only--allow-read=<sock>/--allow-write=<sock>to the caller'srunFlags, never--allow-net. So the subprocess died before it was ready:which surfaced to callers as
Deno exited before being readyon everyexecutecall.search_docswas unaffected. Consistent with a regression from 5.0.0'sfix(mcp): remove Stainless sandbox execution mode.The fix
The socket path is generated inside the library (
path.join(os.tmpdir(), \${crypto.randomUUID()}-deno-http.sock`)) and is never exposed to callers — there is no option or getter for it, in 0.0.21 or in any later version. So the grant is applied throughspawnFunc, a documented public option inDenoWorkerOptions: recover the socket path from the argv the library just built (the only bare, non-flag argument ending in the socket suffix) and appendunix:to the existing--allow-net` allowlist.Building the flag from the argv rather than guessing guarantees it always names the socket actually being served.
The sandbox is otherwise untouched. No
-A/--allow-all, no other permission widened. Verified against Deno 2.9.4 that the allowlist matches a Unix socket by full path only —--allow-net=unix:<dir>is rejected — so this is as tightly as the permission can be scoped, and the fallback to a bare--allow-netwas not needed.Verification
Tested against Deno 2.9.4 (
deno 2.9.4 (stable, release, x86_64-unknown-linux-gnu)).Standalone repro —
Deno.serve({ path })under--allow-read --allow-writethrows the exactNotCapableabove; adding--allow-net=unix:<path>binds successfully.End to end through the real
executehandler (stub client, no Beeper Client API needed):return 1 + 1NotCapable ... unix:/tmp/<uuid>-deno-http.sock→Deno exited before being ready"2",console.logoutput capturedfetch("https://example.com")NotCapable: Requires net access to "example.com:443"That last row is the important one: arbitrary outbound network from sandboxed code remains blocked. Only the API base-URL host and the one control socket are reachable.
I could not exercise this against a running Beeper Client API, so the evidence is the standalone repro plus the end-to-end handler run above.
Checks — full suite per CONTRIBUTING, all green:
yarn lint—prettier --check .,eslint ., build,tsc, Are The Types Wrong?, publint.yarn testagainst the./scripts/mocksteady server — 448 tests / 18 suites pass.packages/mcp-server—yarn lint,tsc --noEmit,jest(2/2 pass).No version bumps or CHANGELOG edits: release-please owns those, and it treats
packages/mcp-server/{package.json,manifest.json,yarn.lock}as version-bump targets. Both commits use conventional-commitfix(...)titles so they land under "Bug Fixes" in the generated changelog.Note for maintainers: this patches generated code
packages/mcp-server/src/code-tool.tsandsrc/options.tsboth carry the StainlessFile generated from our OpenAPI specheader, andpackages/mcp-serverhas nosrc/lib/escape hatch — only the root SDK package does.spawnFuncis the only seam that can reach the argv, so there is no hand-editable override to use instead.CONTRIBUTING states modifications to generated code are persisted across regenerations (with possible merge conflicts) rather than silently reverted, and the preceding commit
1c50f9falready patches this same file directly — so I landed it in place. Please route it through the generator config if that's the house preference. My read is that it's better kept here: it's a workaround for a Deno-runtime behavior change, ideally temporary, and the real long-term home is upstream in@valtown/deno-http-worker, which owns both the socket path and the existing "extend the caller's permission flags" logic. Keeping the patch visible in the generated file means the next regeneration conflict forces someone to re-read it.Worth filing upstream at val.town separately; note that
^0.0.21pins exactly for a0.0.xrange, so absorbing any upstream fix needs a deliberate bump regardless.Minor aside (separate commit)
npx @beeper/desktop-mcp --versionprintedtrue. yargs treats a single argument to.version()as the version string, so.version(true)printedtrue. Changed to.version(), which reads the version frompackage.json— now prints5.0.0.🤖 Generated with Claude Code
https://claude.ai/code/session_01H7CWSE6BSsshnh7rZNgomB