Skip to content

feat: move desktop runtime behind app server - #192

Merged
oratis merged 19 commits into
mainfrom
codex/desktop-protocol-runtime
Aug 2, 2026
Merged

feat: move desktop runtime behind app server#192
oratis merged 19 commits into
mainfrom
codex/desktop-protocol-runtime

Conversation

@oratis

@oratis oratis commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Summary

  • make the supervised app-server protocol the desktop chat runtime and remove the renderer-side provider/tool loop
  • expose status-only credentials, remove native mutation commands, and keep plaintext provider credentials inside the sidecar
  • load only trusted user settings in the desktop runtime while retaining session-backed file snapshots without a second message writer
  • project structured turns, tools, usage, approvals, and user input into the existing UI, including fast-terminal buffering and stale-turn isolation
  • split dependency-free model metadata from the provider implementation so provider/OpenAI SDK code is absent from renderer bundles

Validation

  • pre-commit: format, lint, typecheck, 952 tests passed; 12 environment-dependent tests skipped
  • Rust: 35 tests passed
  • pnpm build
  • frozen lockfile check and docs consistency check
  • production renderer and source maps: zero matches for provider implementation, browser credential flag, legacy runtime names, or credential env names
  • real arm64 DeepCode.app build with bundled Node 22 runtime and 229,175-byte app-server
  • nested runtime plus outer app ad-hoc signing; codesign --verify --deep --strict passed
  • bundled sidecar no-credential lifecycle E2E produced ordered turn failure events

Follow-up

Developer ID signing/notarization remains a release gate. Browser fixture and visual desktop journeys remain in the next stacked PR.

@oratis
oratis force-pushed the codex/canonical-thread-store branch from 801b4de to 1a92767 Compare August 1, 2026 07:05
@oratis
oratis force-pushed the codex/desktop-protocol-runtime branch from a9d45f7 to 06654f2 Compare August 1, 2026 07:05
@oratis
oratis force-pushed the codex/canonical-thread-store branch from 1a92767 to 033f7e6 Compare August 1, 2026 07:20
@oratis
oratis force-pushed the codex/desktop-protocol-runtime branch from 06654f2 to e838092 Compare August 1, 2026 07:20
@oratis
oratis changed the base branch from codex/canonical-thread-store to main August 2, 2026 06:36
@oratis
oratis marked this pull request as ready for review August 2, 2026 06:41
@oratis

oratis commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

Review: approved ✅ — closes threat #1 for the desktop app

This is where the renderer stops being a trusted runtime. Per docs/security-model.md, threat #1 is "model exfiltrates the DeepSeek API key", and until now the desktop renderer held provider credentials and ran the agent loop inside a WebView. That's fixed here.

Verified in the Rust diff

  • read_credentialscredential_status, returning presence/base_url only. There is a test asserting the serialized status never contains api_key or auth_token — that's the assertion that keeps this from regressing.
  • reject_credentials_path() blocks the renderer's read-only file preview from resolving to the backend credentials file, with tests for both the credential path and an ordinary workspace path. Without this, "read-only file preview" would have been a complete bypass of everything else in this PR.
  • Native mutation commands removed; the renderer's only mutation route is the versioned protocol.

Also good

  • Only trusted user settings load in the desktop runtime — an untrusted project can no longer influence desktop runtime config, consistent with the trust work landing in feat: add trust-aware config diagnostics #197.
  • Model metadata split from the provider implementation so the OpenAI SDK and provider code are absent from renderer bundles. This is the part that makes "no credentials in the renderer" structurally true rather than merely currently true — you can't exfiltrate through a client that doesn't contain the code.
  • Fast-terminal buffering and stale-turn isolation preserve the UI behaviour the previous in-renderer loop provided, so this isn't a functionality regression dressed as a security fix.

Note: −1036 lines. Deleting the second agent loop is the real win — two implementations of the same policy is how the VS Code/LSP gate bypass in #181 happened in the first place.

Validation: CI green; desktop 62 tests pass locally at the stack tip.

@oratis
oratis merged commit 761fa98 into main Aug 2, 2026
4 checks passed
oratis added a commit that referenced this pull request Aug 2, 2026
…#214)

Reworks #166, which was correct in approach but is now incomplete and
carries stale docs.

`permissions.additionalDirectories` has been declared in the settings
schema and read by /permissions for display, but consumed for
enforcement nowhere — /add-dir only printed "recorded ... (effective in
M3)". Users could reasonably believe it did something.

Why the sandbox is the right boundary: the file tools (Read/Write/Edit/
Glob/Grep) already accept any absolute path, so there is no cwd
containment to widen. The only thing that actually restricts writes is
the OS sandbox wrapping Bash. Enforcing /add-dir therefore means adding
those directories to `filesystem.allowWrite`.

The part #166 could not have covered: it predates the app-server, and
wired only CLI REPL + headless. Since #192/#195/#196, desktop, VS Code
and LSP all run through apps/server, so that wiring would have enforced
the setting in the CLI and silently ignored it everywhere else — for a
security-relevant setting, partial enforcement is worse than none,
because it is indistinguishable from full enforcement at the UI. All 7
sandbox assembly sites across 4 files now route through the helper.

- core `withAdditionalWritableDirs(sandbox, dirs, cwd?)`: pure, never
  mutates input, no-op when the sandbox is disabled (never silently
  enables it), dedupes, and drops non-absolute entries rather than
  handing them to profile writers that require absolute paths
- /add-dir validates the path is an existing directory, stores it
  absolute (resolved against cwd), refuses duplicates, and lists the
  current set with no args
- BEHAVIOR_PARITY: only the /add-dir row changes. #166 rewrote the whole
  table with June-era content that would have regressed /btw, /voice,
  /tasks and /background back to unshipped. (Prettier reflows the table
  columns; `git diff -w` shows the 2 real lines.)

tsc -b --force, lint (--max-warnings=0), format:check, docs:check clean;
1048 tests pass (9 new helper + 6 new /add-dir); build clean.

Co-authored-by: t <t@t>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
oratis pushed a commit that referenced this pull request Aug 2, 2026
Supersedes #138 and #139, which are coupled (plugin-react 6 requires
vite 7+) and were deferred in June because `vite build` failed with
`Rolldown failed to resolve import "openai"` — vite 8 swaps in the
rolldown bundler, and the renderer was transitively pulling
core/dist/providers/deepseek.js.

That blocker is gone: #192 split dependency-free model metadata from the
provider implementation specifically so provider/OpenAI SDK code is
absent from renderer bundles. The import rolldown could not resolve no
longer reaches the renderer graph, so no `build.rolldownOptions.external`
migration is needed.

Also replaces `__dirname` with `import.meta.dirname` in vite.config.ts.
vite 8 warns that `__dirname` is unsupported by `configLoader: 'native'`,
which is planned to become the default in a future major. The repo
requires Node >=22, so `import.meta.dirname` is available.

Verified on top of React 19 (#211), forced rebuild throughout:
- tsc -b --force, lint (--max-warnings=0), format:check: clean
- 1033 tests across 8 packages: pass
- pnpm build + full build:tauri-assets pipeline: pass
  (app-server.cjs 599,395 bytes — within the 768 KiB #208 budget)
- desktop Playwright protocol journey (4 tests): pass

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
oratis added a commit that referenced this pull request Aug 2, 2026
Supersedes #138 and #139, which are coupled (plugin-react 6 requires
vite 7+) and were deferred in June because `vite build` failed with
`Rolldown failed to resolve import "openai"` — vite 8 swaps in the
rolldown bundler, and the renderer was transitively pulling
core/dist/providers/deepseek.js.

That blocker is gone: #192 split dependency-free model metadata from the
provider implementation specifically so provider/OpenAI SDK code is
absent from renderer bundles. The import rolldown could not resolve no
longer reaches the renderer graph, so no `build.rolldownOptions.external`
migration is needed.

Also replaces `__dirname` with `import.meta.dirname` in vite.config.ts.
vite 8 warns that `__dirname` is unsupported by `configLoader: 'native'`,
which is planned to become the default in a future major. The repo
requires Node >=22, so `import.meta.dirname` is available.

Verified on top of React 19 (#211), forced rebuild throughout:
- tsc -b --force, lint (--max-warnings=0), format:check: clean
- 1033 tests across 8 packages: pass
- pnpm build + full build:tauri-assets pipeline: pass
  (app-server.cjs 599,395 bytes — within the 768 KiB #208 budget)
- desktop Playwright protocol journey (4 tests): pass

Co-authored-by: t <t@t>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
oratis added a commit that referenced this pull request Aug 2, 2026
…ajors (#215)

Covers the 31-PR Codex alignment stack (#180-#210) plus the dependency
work (#211-#214, #155, #177-#179).

Leads with the security section because that is what this release is:
the central tool gate could be skipped entirely when `mode` was omitted
(#181), and the desktop renderer held provider credentials (#192).

Upgrade notes call out the two things a user could otherwise be
surprised by — the ~6.7 MB → ~115 MB desktop size increase from the Node
sidecar, and that the plugin capability RPC is not an OS boundary.

Co-authored-by: t <t@t>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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