Skip to content

feat(cli): persist /sandbox toggle across new sessions - #11912

Merged
marius-kilocode merged 1 commit into
mainfrom
exuberant-archer
Jul 3, 2026
Merged

feat(cli): persist /sandbox toggle across new sessions#11912
marius-kilocode merged 1 commit into
mainfrom
exuberant-archer

Conversation

@marius-kilocode

Copy link
Copy Markdown
Collaborator

What

The CLI /sandbox toggle was session-scoped: each new session reset to the static experimental.sandbox config default (or secure()-forced ON in authless mode where KILO_SERVER_PASSWORD is unset). The VS Code extension's sandbox button, by contrast, persists the last choice via kilo.sandbox.newSessionDefault workspace state, so new sessions inherit it. This PR makes the CLI match VS Code.

Why

Users who toggle the sandbox off (or on) for a project expect that choice to stick the next time they start a session in the same directory. Forgetting the toggle on every new session is surprising and inconsistent with the extension, where the same control is persistent. The only existing "persistent" CLI option was the static config flag, which is a fixed default rather than "remember what I last chose."

How it works

A new per-directory persisted store (SandboxPreference) records the last toggled boolean under ~/.local/state/kilo-sandbox-preference/<sha256(directory)>.json (mirroring the existing SandboxStore layout). SandboxPolicy resolves a new session's sandbox state with precedence:

  1. create-time kilocode.sandbox session metadata (existing)
  2. the persisted per-directory preference (new)
  3. the config default, with secure() (authless force-ON) only applying when neither explicit choice nor preference exists

toggle() persists the new state to the per-directory preference on every toggle. Secure-by-default is preserved for directories with no prior toggle (the very first session still starts sandboxed in authless mode).

Security tradeoff

In authless (local TUI) mode, once a user toggles the sandbox off, new sessions in that project stay off until toggled back, since secure() no longer overrides an existing preference. This weakens the secure-by-default guarantee but matches VS Code's behavior, and the new preference store is protected by the sandbox profile's denyWrite list so a sandboxed process cannot plant a false preference to disable confinement for later sessions.

Tests

Rewrote the two tests that encoded the old per-session isolation property to assert the new persistence, plus a round-trip test (toggle off, then on, new session inherits on). A third pre-existing test that exercises retire was adjusted since retire now re-seeds from the directory preference. Added a denyWrite assertion for SandboxPreference.root in policy.test.ts. All 64 sandbox tests pass.

if (!status.enabled && !status.available) return status
const next: Snapshot = { ...current, enabled: !status.enabled, version: status.version + 1 }
yield* Effect.promise(() => SandboxStore.write(directory, sessionID, next))
yield* Effect.promise(() => SandboxPreference.write(directory, next.enabled))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: New preference write can desync the in-memory snapshot cache from the persisted store on failure

snapshots.set(key(directory, sessionID), next) on line 211 only runs after this SandboxPreference.write call resolves. If the preference write throws (disk full, permission error, etc.) after the preceding SandboxStore.write(directory, sessionID, next) on line 209 already succeeded, change() fails and the cache update is skipped — but the session's persisted SandboxStore snapshot has already been updated to the new toggled value. Any subsequent read(directory, sessionID) call first checks snapshots.get(id) (policy.ts:143) and returns the stale cached value without re-reading disk, so the in-process state and the on-disk SandboxStore state can diverge for the remainder of the process's life, even though the toggle appeared to fail to the caller.

Consider updating snapshots.set(...) before the (best-effort) preference write, or treating a SandboxPreference.write failure as non-fatal (log and continue) so it can't leave the primary session snapshot cache out of sync with what was already durably persisted.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

The previously flagged WARNING (preference write could desync the in-memory snapshot cache from the persisted store) is fixed: snapshots.set(...) now runs before the SandboxPreference.write call, and that write is wrapped in Effect.catch(() => Effect.void) so a failure there is treated as best-effort and can no longer fail the toggle or leave the cache out of sync.

Files Reviewed (1 file)
  • packages/opencode/src/kilocode/sandbox/policy.ts
Previous Review Summary (commit d94328a)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit d94328a)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
packages/opencode/src/kilocode/sandbox/policy.ts 210 New SandboxPreference.write call can fail after SandboxStore.write already succeeded, skipping the snapshots.set(...) cache update and desyncing the in-memory cache from the persisted store
Files Reviewed (5 files)
  • .changeset/persist-cli-sandbox-toggle.md
  • packages/opencode/src/kilocode/sandbox/policy.ts - 1 issue
  • packages/opencode/src/kilocode/sandbox/preference.ts
  • packages/opencode/test/kilocode/sandbox/policy.test.ts
  • packages/opencode/test/kilocode/sandbox/state.test.ts

All changes are confined to Kilo-owned paths (packages/opencode/src/kilocode/, packages/opencode/test/kilocode/), so no kilocode_change markers or upstream fork-hygiene concerns apply. The changeset is present and user-facing. The documented security tradeoff (authless secure-by-default no longer overriding an existing per-directory preference) is explicitly called out and mitigated via the denyWrite list in the PR description, so no separate finding was raised for it.

Fix these issues in Kilo Cloud


Reviewed by claude-sonnet-5-20260630 · Input: 32 · Output: 5.9K · Cached: 654.2K

Review guidance: REVIEW.md from base branch main

The CLI /sandbox toggle was session-scoped: each new session reset to
the static config default (or secure()-forced ON in authless mode),
while the VS Code extension's sandbox button persisted the last choice.

Add a per-directory persisted preference (SandboxPreference) that new
sessions resolve after create-time metadata and before the config
default, so /sandbox now remembers the last toggled state per project.
secure()-by-default still applies when neither an explicit choice nor
a preference exists. Add SandboxPreference.root to the sandbox
denyWrite list so a sandboxed process cannot plant a false preference
to disable confinement for later sessions.
@marius-kilocode
marius-kilocode enabled auto-merge July 3, 2026 12:50
@marius-kilocode
marius-kilocode merged commit 6325559 into main Jul 3, 2026
27 checks passed
@marius-kilocode
marius-kilocode deleted the exuberant-archer branch July 3, 2026 12:51
t7tran pushed a commit to t7tran/kilocode that referenced this pull request Aug 14, 2026
feat(cli): persist /sandbox toggle across new sessions
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.

2 participants