fix(server): make JARVIS_PERMISSION_MODE load-bearing on every agent path (#491) - #516
Draft
TYRMars wants to merge 1 commit into
Draft
fix(server): make JARVIS_PERMISSION_MODE load-bearing on every agent path (#491)#516TYRMars wants to merge 1 commit into
TYRMars wants to merge 1 commit into
Conversation
…path (#491) `JARVIS_PERMISSION_MODE` was parsed, printed in the banner, and reported over `GET /v1/server/info`, but never wired to an Approver. Every server agent entry point except the chat WebSocket called `createAgent()` with no approver, and @jarvis/core's contract for that case is "gated tools run unconditionally" — so with a write/exec flag set, `POST /v1/chat/completions` (and the SSE, conversa- tion, workflow, and auto-mode paths) executed approval-gated `shell.exec` / `fs.write` / `fs.edit` / `fs.patch` with no gate at all under `ask`. The whole `/v1/permissions*` rule engine (`RuleApprover`, `MemoryPermissionStore`) was also dead in production because nothing ever set `AppState.permissionStore`. Fix: the composition root now seeds a `MemoryPermissionStore` from `config.permissionMode`, puts it on `AppState.permissionStore`, and wraps EVERY `createAgent` approver in a `RuleApprover` around it. The WS transport's per-socket `ChannelApprover` becomes the "ask" fallback (prompt the socket); every non-interactive path falls back to `AlwaysDeny`, matching the CLI pipe-mode precedent (`ask` with no human ⇒ deny, surfacing "tool denied: …" so the model adapts). `auto`/`bypass` auto-allow. This makes the mode load-bearing on all paths at once and gives `/v1/permissions*` a live store instead of a permanent 503. - MemoryPermissionStore gains a synchronous `currentMode()` so the RuleApprover's ModeHandle reads the live default mode — a `PUT /v1/permissions/mode` takes effect on the next gated tool call with no extra plumbing. - `permissionStore` folded into the AppState interface proper; the routes drop their structural-widening seam. - Regression tests drive one gated `shell.exec` call through the real composition-root agent: denied under `ask`, executed under `auto`, and `GET /v1/permissions` now 200s. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017PCPr9wBwnpQX79ysFh3uX
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.
Summary
Resolves #491.
JARVIS_PERMISSION_MODEwas parsed, printed in the startup banner, and reported overGET /v1/server/info— but never wired to anApprover. Every server agent entry point except the chat WebSocket calledcreateAgent()with no approver, and@jarvis/core's documented contract for that case is "gated tools run unconditionally".Net effect: with
JARVIS_ENABLE_SHELL_EXEC=1(or anyfs.*write flag) set,POST /v1/chat/completions— and the SSE, conversation, workflow-runtime, and auto-mode paths — executed approval-gatedshell.exec/fs.write/fs.edit/fs.patchwith no approval gate at all underask, while the banner and/v1/server/infoboth reportedapproval_mode: "ask". The entire/v1/permissions*rule engine (RuleApprover,MemoryPermissionStore) was dead in production for the same reason: nothing ever setAppState.permissionStore.Fix
The composition root (
packages/jarvis-app/src/state.ts) now:MemoryPermissionStorefromconfig.permissionModeand puts it onAppState.permissionStore.createAgentapprover in aRuleApproveraround that store.The fallback approver is:
ChannelApproveron the WS path (anaskdecision prompts the socket, exactly as before), andAlwaysDenyon every non-interactive path — matching the establishedpackages/jarvis-clipipe-mode precedent (askwith no human ⇒ deny, which surfacestool denied: …so the model adapts).auto/bypassauto-allow on all paths;accept-editsauto-allowswritetools. This makes the mode load-bearing on every path at once and gives/v1/permissions*a live store instead of a permanent 503.Behavioral note
Autonomous paths (auto-mode, workflow-runtime, blocking/SSE chat) now require the operator to set
JARVIS_PERMISSION_MODE=auto(orbypass) for gated tools to run without a human — the same explicit opt-in the CLI already enforces in pipe mode. Under the defaultask, gated tools are denied (surfaced to the model), never silently executed.Changes
packages/jarvis-app/src/state.ts— build + seed theMemoryPermissionStore, wrapcreateAgent's approver in aRuleApprover, exposepermissionStoreonAppState.packages/server/src/permissions-routes.ts— add a synchronousMemoryPermissionStore.currentMode()so theRuleApprover'sModeHandlereads the live default mode (aPUT /v1/permissions/modethen takes effect on the next gated call); drop the structural-wideningPermissionRoutesStateseam now that the field is onAppState.packages/server/src/state.ts— foldpermissionStoreinto theAppStateinterface proper.packages/jarvis-app/src/jarvis-app.test.ts— regression tests driving one gatedshell.execcall through the real composition-root agent: denied underask(andGET /v1/permissionsnow 200s), executed underauto.Testing
pnpm -r typecheck— cleanpnpm lint— clean (0 errors)pnpm -r test— all pass, including 2 new regression tests (jarvis-app 21, server 504)🤖 Generated with Claude Code
Generated by Claude Code