@loicngr/kobo 2.1.25 → 2.1.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/AGENTS.md +30 -3
  2. package/CHANGELOG.md +15 -0
  3. package/CONFIGURATION.md +156 -49
  4. package/README.md +2 -1
  5. package/SECURITY.md +33 -6
  6. package/dist/mcp-server/kobo-tasks-server.js +36 -1021
  7. package/dist/mcp-server/managed-mcp-dispatcher.js +960 -0
  8. package/dist/mcp-server/workspace-dialogue-client.js +26 -1
  9. package/dist/server/index.js +52 -19
  10. package/dist/server/middleware/host-check-middleware.js +8 -5
  11. package/dist/server/middleware/network-auth-middleware.js +51 -29
  12. package/dist/server/middleware/security-headers-middleware.js +19 -0
  13. package/dist/server/middleware/traffic-limits-middleware.js +74 -0
  14. package/dist/server/routes/auth.js +73 -0
  15. package/dist/server/routes/internal-mcp.js +137 -0
  16. package/dist/server/routes/mcp-credentials.js +30 -0
  17. package/dist/server/routes/mcp.js +156 -76
  18. package/dist/server/routes/usage.js +24 -0
  19. package/dist/server/routes/workspaces.js +19 -3
  20. package/dist/server/services/agent/orchestrator.js +27 -17
  21. package/dist/server/services/external-mcp-credentials-service.js +110 -0
  22. package/dist/server/services/forge/github/provider.js +15 -3
  23. package/dist/server/services/forge/types.js +8 -0
  24. package/dist/server/services/internal-mcp-capability-service.js +98 -0
  25. package/dist/server/services/mcp-connection-info-service.js +1 -1
  26. package/dist/server/services/memory-mcp-service.js +9 -7
  27. package/dist/server/services/memory-service.js +15 -0
  28. package/dist/server/services/network-access-service.js +43 -73
  29. package/dist/server/services/network-session-service.js +182 -0
  30. package/dist/server/services/provider-accounts-service.js +202 -0
  31. package/dist/server/services/settings-service.js +29 -7
  32. package/dist/server/services/terminal-websocket-service.js +5 -0
  33. package/dist/server/services/usage/poller.js +44 -3
  34. package/dist/server/services/websocket-service.js +5 -0
  35. package/dist/server/utils/paths.js +2 -1
  36. package/dist/server/utils/startup-display.js +1 -2
  37. package/dist/shared/models.js +6 -0
  38. package/dist/shared/provider-accounts.js +1 -0
  39. package/docker-compose.vps.yml +6 -0
  40. package/docs/deployment-vps.md +24 -12
  41. package/package.json +1 -1
  42. package/src/client/dist/pwa/assets/ActivityFeed-D58VS9o2.js +2 -0
  43. package/src/client/dist/pwa/assets/{AutoLoopFinalReviewSettings-Dafi7Tue.js → AutoLoopFinalReviewSettings-CK3r_fVB.js} +1 -1
  44. package/src/client/dist/pwa/assets/{ChangelogPage-DH-zUtlf.js → ChangelogPage-CAAUEn5X.js} +1 -1
  45. package/src/client/dist/pwa/assets/ComparisonPanel-BCb7vB5E.js +2 -0
  46. package/src/client/dist/pwa/assets/CreatePage-DasH1Uk4.js +2 -0
  47. package/src/client/dist/pwa/assets/{DashboardPage-B8gTooRi.js → DashboardPage-XE52EABP.js} +1 -1
  48. package/src/client/dist/pwa/assets/DiffViewer-CncKlXne.js +8 -0
  49. package/src/client/dist/pwa/assets/{DocumentsPanel-DXjjhGUg.js → DocumentsPanel-L4pbQfz1.js} +1 -1
  50. package/src/client/dist/pwa/assets/{DrawerToggleButton-CiVNMTbq.js → DrawerToggleButton-DiOJm5he.js} +1 -1
  51. package/src/client/dist/pwa/assets/GitPanel-WJlzy2VJ.js +3 -0
  52. package/src/client/dist/pwa/assets/{GitPanel-CRaLJzjc.css → GitPanel-yHvU38dp.css} +1 -1
  53. package/src/client/dist/pwa/assets/{HealthPage-lc2Cuuks.js → HealthPage-hJY5MurK.js} +1 -1
  54. package/src/client/dist/pwa/assets/MainLayout-DgWElK7w.js +2 -0
  55. package/src/client/dist/pwa/assets/{MemoryPanel-SrR94UVW.js → MemoryPanel-DtEvh_b7.js} +1 -1
  56. package/src/client/dist/pwa/assets/{MemoryProposalList-B5uVf3KU.js → MemoryProposalList-kgFCm9zz.js} +1 -1
  57. package/src/client/dist/pwa/assets/{QBtnDropdown-Cxr3lBPB.js → QBtnDropdown-CWPIugs8.js} +1 -1
  58. package/src/client/dist/pwa/assets/{QExpansionItem-xIXDevKz.js → QExpansionItem-D-EGL2b3.js} +1 -1
  59. package/src/client/dist/pwa/assets/{QScrollArea-DeczMHVs.js → QScrollArea-BMrqK-xd.js} +1 -1
  60. package/src/client/dist/pwa/assets/QSelect-DFWyDWCE.js +1 -0
  61. package/src/client/dist/pwa/assets/{QTooltip-be9mqhmB.js → QTooltip-Bk8dLUtU.js} +1 -1
  62. package/src/client/dist/pwa/assets/{SchedulePanel-R6H-D-m5.js → SchedulePanel-CuxmxCKi.js} +1 -1
  63. package/src/client/dist/pwa/assets/{SearchPage-CaeYoIDw.js → SearchPage-DKnQPUqY.js} +1 -1
  64. package/src/client/dist/pwa/assets/{SettingsPage-Dcq1Jeoo.css → SettingsPage-B4rmd2WX.css} +1 -1
  65. package/src/client/dist/pwa/assets/SettingsPage-CO3vIpPd.js +9 -0
  66. package/src/client/dist/pwa/assets/{SplitWorkspacePage-Dw1qUIFU.js → SplitWorkspacePage-WhQyX8fn.js} +1 -1
  67. package/src/client/dist/pwa/assets/{TerminalPanel-Dqw88QqH.js → TerminalPanel-BY0lPq6k.js} +10 -10
  68. package/src/client/dist/pwa/assets/{TerminalPanel-Dz9EE-Ph.css → TerminalPanel-BcbwuFVR.css} +1 -1
  69. package/src/client/dist/pwa/assets/{ToolCallItem-BMn7eela.js → ToolCallItem-DYubi-J0.js} +3 -3
  70. package/src/client/dist/pwa/assets/{TouchPan-BwCpe_Oh.js → TouchPan-DDotyYLN.js} +1 -1
  71. package/src/client/dist/pwa/assets/{TourReplayButton-yeGYf6xT.js → TourReplayButton-kAnEMcy2.js} +1 -1
  72. package/src/client/dist/pwa/assets/{WorkspaceAttentionLabels-BJPw65vU.js → WorkspaceAttentionLabels-DiYt3rP3.js} +1 -1
  73. package/src/client/dist/pwa/assets/WorkspacePage-BEBwwr81.css +1 -0
  74. package/src/client/dist/pwa/assets/WorkspacePage-K-1ce50d.js +4 -0
  75. package/src/client/dist/pwa/assets/{browser-C9U-mSDm.js → browser-DVYLH8vl.js} +1 -1
  76. package/src/client/dist/pwa/assets/{build-path-tree-DYdRW2T4.js → build-path-tree-DEuOdIIk.js} +1 -1
  77. package/src/client/dist/pwa/assets/custom-sounds-Ggw6V9iF.js +1 -0
  78. package/src/client/dist/pwa/assets/de-Bk3zQrin.js +17 -0
  79. package/src/client/dist/pwa/assets/{editor.api-iu5YkEl3.js → editor.api-BC38eXAS.js} +1 -1
  80. package/src/client/dist/pwa/assets/{efforts-ja6uJadn.js → efforts-TGacUPcJ.js} +1 -1
  81. package/src/client/dist/pwa/assets/es-f3xuAWA9.js +17 -0
  82. package/src/client/dist/pwa/assets/{expand-template-BbgnJ8YH.js → expand-template-DM8gq3Mo.js} +1 -1
  83. package/src/client/dist/pwa/assets/{format-CLXLy6Ov.js → format-OwxXBnDJ.js} +1 -1
  84. package/src/client/dist/pwa/assets/{formatters-DeKgxYJe.js → formatters-YgaCZcwJ.js} +1 -1
  85. package/src/client/dist/pwa/assets/fr-B6c21SG3.js +17 -0
  86. package/src/client/dist/pwa/assets/{freemarker2-2IrnDDvu.js → freemarker2-l7R42fI2.js} +1 -1
  87. package/src/client/dist/pwa/assets/{handlebars-CeLN7FCP.js → handlebars-DFmPrgjH.js} +1 -1
  88. package/src/client/dist/pwa/assets/{html-Bn3rhcp3.js → html-CERIAlPM.js} +1 -1
  89. package/src/client/dist/pwa/assets/i18n-WaE4cZK9.js +1 -0
  90. package/src/client/dist/pwa/assets/index-CpWBGKdM.js +20 -0
  91. package/src/client/dist/pwa/assets/it-DUstDpC2.js +17 -0
  92. package/src/client/dist/pwa/assets/{javascript-BMvON8oT.js → javascript-BBYncplF.js} +1 -1
  93. package/src/client/dist/pwa/assets/{liquid-D_AQLvQl.js → liquid-C1HqYyYU.js} +1 -1
  94. package/src/client/dist/pwa/assets/{mdx-DDJzMB8Q.js → mdx-E74dsP22.js} +1 -1
  95. package/src/client/dist/pwa/assets/{monaco-contributions-DFKwR-_X.js → monaco-contributions-DHvm-kp5.js} +1 -1
  96. package/src/client/dist/pwa/assets/{monaco.contribution-4AwFLnjV.js → monaco.contribution-DYdn9CwR.js} +2 -2
  97. package/src/client/dist/pwa/assets/network-auth-DTZUKbny.js +1 -0
  98. package/src/client/dist/pwa/assets/{notify-theme-CMzdjL91.js → notify-theme-FuTL7HuS.js} +1 -1
  99. package/src/client/dist/pwa/assets/{permissionModes-DLWiBfdb.js → permissionModes-D-Nexnoo.js} +1 -1
  100. package/src/client/dist/pwa/assets/{project-color-C5mEKdlH.js → project-color-_snXWYUp.js} +1 -1
  101. package/src/client/dist/pwa/assets/{python-BOqcp9xc.js → python-DNAAGBcX.js} +1 -1
  102. package/src/client/dist/pwa/assets/{razor-BrGwBzd7.js → razor-CB3g5p_T.js} +1 -1
  103. package/src/client/dist/pwa/assets/{typescript-mOGf86uY.js → typescript-0kpz8bqP.js} +1 -1
  104. package/src/client/dist/pwa/assets/{use-search-index-status-P7O7EaV_.js → use-search-index-status-D38yFOD_.js} +1 -1
  105. package/src/client/dist/pwa/assets/{use-tours-CMFPIW6f.js → use-tours-BWkHp34X.js} +2 -2
  106. package/src/client/dist/pwa/assets/{use-worktree-restore-DF6P7h8g.js → use-worktree-restore-DUjZr44N.js} +1 -1
  107. package/src/client/dist/pwa/assets/{workspace-templates-C_bgIQvs.js → workspace-templates-D-lPJQ4G.js} +1 -1
  108. package/src/client/dist/pwa/assets/{xml-YcMBzYAm.js → xml-BbOuqdUe.js} +1 -1
  109. package/src/client/dist/pwa/assets/{yaml-BP_67Sds.js → yaml-gasFQtfk.js} +1 -1
  110. package/src/client/dist/pwa/index.html +1 -1
  111. package/src/client/dist/pwa/sw.js +1 -1
  112. package/src/mcp-server/README.md +13 -19
  113. package/src/mcp-server/kobo-tasks-server.ts +42 -1146
  114. package/src/mcp-server/managed-mcp-dispatcher.ts +1071 -0
  115. package/src/mcp-server/workspace-dialogue-client.ts +26 -1
  116. package/src/client/dist/pwa/assets/ActivityFeed-xJMPnXKA.js +0 -2
  117. package/src/client/dist/pwa/assets/ComparisonPanel-DwaQyEgb.js +0 -2
  118. package/src/client/dist/pwa/assets/CreatePage-DyF_efbs.js +0 -2
  119. package/src/client/dist/pwa/assets/DiffViewer-BivWNITj.js +0 -8
  120. package/src/client/dist/pwa/assets/GitPanel-DQjgGtoN.js +0 -3
  121. package/src/client/dist/pwa/assets/MainLayout-D3pGcTx8.js +0 -2
  122. package/src/client/dist/pwa/assets/QSelect-GQAsf7pB.js +0 -1
  123. package/src/client/dist/pwa/assets/SettingsPage-BCWdQHS5.js +0 -9
  124. package/src/client/dist/pwa/assets/WorkspacePage-BHO1Q8PG.js +0 -4
  125. package/src/client/dist/pwa/assets/WorkspacePage-BnklZAYZ.css +0 -1
  126. package/src/client/dist/pwa/assets/custom-sounds-DPsfYErA.js +0 -1
  127. package/src/client/dist/pwa/assets/de-Bqil7RJE.js +0 -17
  128. package/src/client/dist/pwa/assets/es-CNcMm1Zz.js +0 -17
  129. package/src/client/dist/pwa/assets/fr-BObHEohg.js +0 -17
  130. package/src/client/dist/pwa/assets/i18n-Df_y96N2.js +0 -1
  131. package/src/client/dist/pwa/assets/index-CH4JpI2a.js +0 -20
  132. package/src/client/dist/pwa/assets/it-DDMC7Emk.js +0 -17
  133. package/src/client/dist/pwa/assets/network-auth-CMERg4Qk.js +0 -1
package/AGENTS.md CHANGED
@@ -6,7 +6,9 @@ Guidance for AI coding agents (Claude Code, Cursor, etc.) working on this reposi
6
6
 
7
7
  **Kōbō** (工房, Japanese for "workshop") orchestrates Claude Code and OpenAI Codex agents across isolated git worktrees. Each "workspace" is a self-contained mission with its own worktree, branch, agent sessions, optional dev server, optional Notion/Sentry context, and a dedicated MCP tools server. A Vue 3 UI lets the human track progress, read live agent output, review Git changes, and manage the lifecycle. A workspace can switch engines through a handoff to a fresh session while preserving its worktree and history.
8
8
 
9
- Single-user dev tool, local by default. The server binds `127.0.0.1` unless the opt-in "network access" setting is enabled, which binds all interfaces and gates non-loopback API/WS requests behind a shared token. Behind-a-reverse-proxy mode also requires the token for loopback API/WS requests; `/api/health` stays exempt. Host/Origin checks apply separately. No multi-tenant concerns.
9
+ Single-user dev tool, local by default. The server binds `127.0.0.1` unless the opt-in "network access" setting is enabled. Operator HTTP access uses the network token or a 12-hour HttpOnly browser session; browsers use 60-second single-use path-bound tickets for WebSocket upgrades, never the operator token in the URL or localStorage. Behind-a-reverse-proxy mode also gates loopback operator requests. Rotation, network disable and proxy-mode changes revoke sessions, tickets and active WebSockets. `/api/health` stays exempt. Host/Origin checks apply separately; public proxy origin must be declared explicitly. No multi-tenant concerns.
10
+
11
+ External `/api/mcp` and global stdio require dedicated hashed credentials with tool/workspace grants, expiry and revocation, even on loopback. Manage them through operator-only `/api/mcp-credentials`; global stdio uses `KOBO_MCP_TOKEN`. Managed workspace stdio uses per-launch `KOBO_MCP_SESSION_TOKEN` through OS-loopback-only `/api/internal-mcp`, never the operator token or direct SQLite writes. The backend enforces Plan/read-only permissions and revokes capabilities when stopping begins. Keep report/progress exceptions narrowly scoped. See [SECURITY.md](./SECURITY.md).
10
12
 
11
13
  ## Tech stack
12
14
 
@@ -103,7 +105,8 @@ src/
103
105
  │ ├── pages/ # WorkspacePage, CreatePage, SettingsPage
104
106
  │ └── router/
105
107
  ├── mcp-server/ # standalone MCP server spawned per workspace
106
- │ ├── kobo-tasks-server.ts # entrypoint, registers tools
108
+ │ ├── kobo-tasks-server.ts # stdio bridge; forwards discovery/calls to authenticated backend
109
+ │ ├── managed-mcp-dispatcher.ts # workspace tool definitions/dispatch, executed in backend
107
110
  │ └── kobo-tasks-handlers.ts # pure handlers (list_tasks, mark_task_done)
108
111
  ├── shared/ # model catalogues, prompts, dialogue contracts, shared utilities
109
112
  └── __tests__/ # backend Vitest suites; client tests live under client/src/__tests__/
@@ -149,6 +152,30 @@ src/
149
152
 
150
153
  Tasks have `role` (`work | finalization`) and structured `verification`. MCP and HTTP share transactional task mutations. An auto-loop task cannot become done without passing reported checks. Finalization runs after all other tasks/criteria, is invalidated by scope changes, and is mandatory before completion. The proof is agent-reported evidence, not server-side execution of arbitrary commands.
151
154
 
155
+ ## Provider account switching
156
+
157
+ The usage popover integrates optional `ccswap` for Claude Code and Codex. Select
158
+ accounts for the viewed session's engine, falling back to the workspace engine.
159
+ Missing executable hides account controls; it must not hide existing Claude
160
+ usage. Codex account support is separate from quota snapshot support.
161
+
162
+ `provider-accounts-service.ts` owns subprocess invocation and sanitization. Use
163
+ provider allowlists, numeric slots and bounded `execFile`, never shell text. Keep
164
+ credential mutations serialized with their validation. Return only display
165
+ metadata; do not forward raw CLI JSON, stderr, tokens or credential paths. Inherit
166
+ the Kōbō process user, HOME, PATH and credential-directory environment: in Docker,
167
+ the CLI must run inside that container, not on the host. Do not install tools or
168
+ perform real switches in tests.
169
+
170
+ A switch changes the provider-wide credential store, not a workspace identity.
171
+ Do not automatically stop agents, migrate conversations, copy settings/skills or
172
+ promise that an existing process hot-reloads authentication. Invalidate Claude
173
+ quota data around switches and reject results from earlier in-flight polls.
174
+ Keep frontend requests scoped across provider/session changes and handle missing,
175
+ empty, disabled, unrecognized and failed account states without automatic retries.
176
+ See [configuration](./CONFIGURATION.md#switch-claude-code-and-codex-accounts-with-ccswap)
177
+ and [VPS setup](./docs/vps-projects-and-skills.md#switch-engine-accounts-inside-the-container).
178
+
152
179
  ## Persistent memory
153
180
 
154
181
  Kōbō memory is independent of provider chat history/native memory and shared by Claude Code and Codex. `memoryMode` is a global setting defaulting to `hybrid`: Manual permits reads only; Hybrid applies workspace writes and proposes project/global writes; Automatic applies writes to the explicitly targeted scope. Mode is checked per operation, so a setting change affects existing agent sessions; pending/rejected proposals are never automatically approved or revived. Plan/review/report-only capabilities cannot write.
@@ -268,7 +295,7 @@ also bypasses the auto-loop next-iteration UI path; normal backend immediate
268
295
  message delivery pauses a ready loop. Do not flush existing queues on toggle,
269
296
  remove compaction/permission/handoff guards, or queue rejected forced sends.
270
297
 
271
- `POST /api/mcp` exposes stateless Streamable HTTP discovery/dialogue tools, behind the existing Host/Origin and network-token gates. The global stdio server forwards dialogue calls through this backend so agent runtime ownership remains centralized. `workspace-message-service.ts` shares delivery with WebSocket chat.
298
+ `POST /api/mcp` exposes stateless Streamable HTTP discovery/dialogue tools, behind Host/Origin, network enablement and dedicated MCP credential gates. The global stdio server forwards dialogue calls through this backend so agent runtime ownership remains centralized. `workspace-message-service.ts` shares delivery with WebSocket chat.
272
299
 
273
300
  Optional `send_workspace_message.idempotency_key` uses `mcp_message_requests` (migration v43). Reserve before dispatch, preserve fingerprints and terminal outcomes, and never automatically resend an unfinished request after restart. The user event and accepted receipt commit in one SQLite transaction; broadcast follows commit. Dispatch may precede persistence, so interrupted requests are explicitly `delivery_unknown`, not an exactly-once execution promise. The backend alone calls `reconcileMessageRequests` on startup. History retention does not delete the ledger; workspace deletion cascades it.
274
301
 
package/CHANGELOG.md CHANGED
@@ -4,6 +4,21 @@ All notable changes to Kōbō are documented here. The format is based on
4
4
  [Keep a Changelog](https://keepachangelog.com/). Each release is an `## <version>`
5
5
  section — the in-app "What's new" dialog reads this file.
6
6
 
7
+ ## 2.1.28
8
+
9
+ - fix(ci): authenticate packaged mcp smoke tests
10
+ - fix(security): harden authentication and mcp access
11
+ - feat(models): Add Haiku 5.5
12
+
13
+ ## 2.1.27
14
+
15
+ - fix(security): harden authentication and mcp access
16
+ - feat(models): Add Haiku 5.5
17
+
18
+ ## 2.1.26
19
+
20
+ - feat: switch Claude and Codex accounts with ccswap
21
+
7
22
  ## 2.1.25
8
23
 
9
24
  - feat: improve merge options, diff controls and progress reporting
package/CONFIGURATION.md CHANGED
@@ -887,11 +887,28 @@ destination worktree and sends file/image references to the agent and chat.
887
887
  Keyed retries include attachment identity and never upload or send twice. See
888
888
  [Sending files and images](src/mcp-server/README.md#sending-files-and-images).
889
889
 
890
- Remote HTTP clients follow the [network access](#network-access) settings and supply the Kōbō token as `Authorization: Bearer <token>` or `X-Kobo-Token`. The endpoint uses the same Host/Origin and reverse-proxy policy as the rest of the backend. No network setting is changed automatically.
890
+ Every external MCP client, including loopback and global stdio clients, must use
891
+ a dedicated MCP key. The operator token and browser session cookie are not accepted
892
+ by `/api/mcp`. Send the MCP key as `Authorization: Bearer <key>`. The endpoint retains the same Host/Origin, network-access and
893
+ reverse-proxy policy. No network setting changes automatically.
894
+
895
+ Keys are independent, stored only as hashes and have explicit tool and workspace
896
+ grants, optional expiration and individual revocation. The default tool grant is
897
+ read-only; writes and destructive tools require an explicit grant. Each key must
898
+ select workspace IDs or deliberately grant all workspaces. An MCP key cannot
899
+ authenticate to operator REST, terminal or credential-management endpoints.
900
+
901
+ An authenticated operator manages credentials through `/api/mcp-credentials`:
902
+ GET lists metadata and available tools; POST accepts `name`, optional `tools`,
903
+ `workspaceIds` or `allWorkspaces: true`, and optional ISO `expiresAt`; DELETE
904
+ `/api/mcp-credentials/<id>` revokes one key. Creation returns the raw `token` once.
905
+ Do not send that token to logs, commit it, or reuse it as the operator credential.
891
906
 
892
907
  See [Connect an external LLM](src/mcp-server/README.md#connect-an-external-llm) for connection examples, tool arguments, cursor polling and authentication details.
893
908
 
894
- The **External LLM access (MCP)** panel within global Network access settings provides copyable URLs and HTTP/stdio configurations for this running installation. Previews use token placeholders; copying a configuration with the real token is a separate action. Set a client name to identify its messages and question answers in workspace history.
909
+ The **External LLM access (MCP)** panel within global Network access settings
910
+ provides HTTP/stdio configuration templates. Supply the dedicated MCP key for the
911
+ intended client. Client names are display labels, not permission identities.
895
912
 
896
913
  External senders can supply `idempotency_key` once per intended message and reuse it on retries. The durable receipt survives backend restarts and event retention. An uncertain delivery is explicitly reported and never automatically sent again; see the MCP guide for response codes and limitations.
897
914
 
@@ -1058,8 +1075,9 @@ To disable network access, toggle it off and restart again.
1058
1075
 
1059
1076
  ### The network access token
1060
1077
 
1061
- When network access is enabled, every non-loopback HTTP request and every
1062
- non-loopback WebSocket connection must supply a shared token.
1078
+ When network access is enabled, remote API and WebSocket access requires
1079
+ operator authentication. The shared operator token opens a browser session;
1080
+ external MCP clients use separate scoped credentials.
1063
1081
 
1064
1082
  - **Auto-generated**: Kōbō generates a random token the first time you enable
1065
1083
  the feature. You never have to type one in.
@@ -1075,11 +1093,12 @@ non-loopback WebSocket connection must supply a shared token.
1075
1093
 
1076
1094
  **Option A: QR code (recommended for phones)**
1077
1095
 
1078
- The Settings panel shows a QR code encoding
1079
- `http://<lan-ip>:<port>/?token=<token>`. Scan it with your phone's camera
1080
- while on the same Wi-Fi network; the browser opens directly to a pre-authenticated
1081
- session. The token is stored in the browser's `localStorage` under
1082
- `kobo:network-token` so subsequent visits don't require re-scanning.
1096
+ The Settings panel shows a QR code using a URL fragment (`#token=<token>`),
1097
+ which browsers do not send in HTTP requests. The client immediately removes the
1098
+ fragment and exchanges the token for a 12-hour HttpOnly, SameSite=Strict session
1099
+ cookie (Secure for a configured HTTPS proxy). The operator token is not retained in localStorage.
1100
+ Previously stored tokens are removed during migration. Use QR links only on
1101
+ trusted devices; do not publish or bookmark a credential-bearing link.
1083
1102
 
1084
1103
  **Option B: manual URL**
1085
1104
 
@@ -1087,22 +1106,27 @@ session. The token is stored in the browser's `localStorage` under
1087
1106
  `http://<lan-ip>:<port>/`).
1088
1107
  2. Open it on the remote device.
1089
1108
  3. A login dialog appears: paste the token from the Settings panel and confirm.
1090
- 4. The token is stored in `localStorage`; subsequent visits auto-authenticate.
1109
+ 4. The HttpOnly session cookie authenticates subsequent visits until expiry or revocation.
1091
1110
 
1092
1111
  **Host machine (localhost)**: connect to `http://localhost:<port>` as usual.
1093
1112
  No token is needed unless **Behind a reverse proxy** is enabled.
1094
1113
 
1095
1114
  ### Token transport
1096
1115
 
1097
- | Connection type | How to supply the token |
1116
+ | Connection type | Authentication |
1098
1117
  |---|---|
1099
- | HTTP requests | `X-Kobo-Token: <token>` header |
1100
- | MCP HTTP requests (`/api/mcp`) | `X-Kobo-Token` or `Authorization: Bearer <token>` |
1101
- | WebSocket connections | `?token=<token>` query parameter on the upgrade URL |
1102
-
1103
- The Kōbō browser client handles both automatically once the token is stored in
1104
- `localStorage`. Third-party scripts or API consumers must supply the header or
1105
- query param manually.
1118
+ | Browser API requests | HttpOnly session cookie after login |
1119
+ | Operator scripts / REST | `X-Kobo-Token: <operator-token>` header |
1120
+ | External MCP HTTP (`/api/mcp`) | Dedicated MCP key as `Authorization: Bearer <key>` |
1121
+ | Browser WebSocket connections | Short-lived, single-use ticket in the upgrade URL, bound to its WS path |
1122
+
1123
+ The browser handles session and ticket exchanges automatically. Tickets expire
1124
+ after 60 seconds and cannot be replayed, reused for a different terminal, or used
1125
+ after their session expires. The permanent operator token is no longer accepted
1126
+ in a WebSocket query string. Rotation, disabling network access or changing proxy
1127
+ mode invalidates browser sessions, pending tickets and existing main/terminal
1128
+ WebSocket connections. Agent processes are not stopped by credential revocation.
1129
+ Server restart also expires browser sessions.
1106
1130
 
1107
1131
  The IP address used for the loopback exemption is taken **from the OS socket**,
1108
1132
  never from forwarded headers (`X-Forwarded-For` etc.), so a remote client cannot
@@ -1154,19 +1178,15 @@ Practical consequences:
1154
1178
  count as the real interface. In development the Quasar dev server is the one
1155
1179
  exception, since it proxies to the backend and so never matches the `Host`;
1156
1180
  the `dev` npm script declares it through `KOBO_DEV_CLIENT_ORIGIN`.
1157
- - **Behind-a-reverse-proxy mode accepts any host by default**, because only the
1158
- operator knows the expected domain. Declare it with
1159
- `KOBO_NETWORK_ACCESS_PROXY_HOST` (see below) to close that; the bundled
1160
- `docker-compose.example.yml` and `docker-compose.local-traefik.yml` do. That
1161
- mode also disables the loopback exemption, so every `/api/*` request has to
1162
- carry the token — but the SPA itself is served outside `/api/*` and behind no
1163
- token, so without a declared hostname a rebinding page can be served the real
1164
- interface and ask the user to paste their token into it.
1181
+ - **Behind-a-reverse-proxy mode requires a declared public hostname.** Set
1182
+ `KOBO_NETWORK_ACCESS_PROXY_HOST`; missing or invalid configuration rejects
1183
+ public requests. Internal loopback clients remain reachable but still require
1184
+ authentication. The bundled proxy Compose files configure this explicitly.
1165
1185
 
1166
1186
  #### `KOBO_NETWORK_ACCESS_PROXY_HOST`
1167
1187
 
1168
- Set it to the domain your proxy serves Kōbō under, and the host check is
1169
- enforced in behind-a-reverse-proxy mode instead of being skipped:
1188
+ Set it to the public origin your proxy serves Kōbō under. This is required in
1189
+ behind-a-reverse-proxy mode:
1170
1190
 
1171
1191
  ```yaml
1172
1192
  environment:
@@ -1175,9 +1195,33 @@ environment:
1175
1195
  - KOBO_NETWORK_ACCESS_PROXY_HOST=kobo.example.com
1176
1196
  ```
1177
1197
 
1178
- A scheme and a port are accepted and ignored (`https://kobo.example.com:8443`
1179
- reads as `kobo.example.com`). Loopback stays reachable so a local healthcheck
1180
- keeps working. Leaving it unset preserves the previous behaviour.
1198
+ A bare hostname means HTTPS. For a nonstandard port, set the complete origin,
1199
+ e.g. `https://kobo.example.com:8443`. Browser Origin checks compare the scheme,
1200
+ hostname and port exactly. Local HTTP proxy staging must explicitly declare
1201
+ `http://kobo.localhost`. Paths, credentials, query strings and fragments are
1202
+ invalid. Loopback healthchecks and authenticated internal calls remain reachable.
1203
+ Leaving this variable unset rejects public proxy traffic.
1204
+
1205
+ ### Browser response protections
1206
+
1207
+ The server sends `Content-Security-Policy: frame-ancestors 'self'` and
1208
+ `X-Frame-Options: SAMEORIGIN`, preserving Kōbō's split panes while blocking
1209
+ embedding by another site. It also sends `X-Content-Type-Options: nosniff`,
1210
+ `Referrer-Policy: no-referrer` and `Cache-Control: no-store` for API responses.
1211
+ The public HTTPS Compose examples add one-year HSTS at Traefik, without applying
1212
+ it to subdomains or HTTP-only local staging. Kōbō does not trust forwarded headers
1213
+ to decide whether a connection is HTTPS. A broader script/style CSP is not enabled;
1214
+ it needs separate compatibility testing with Quasar and Monaco.
1215
+
1216
+ Application limits apply per OS peer address (forwarded addresses are not trusted):
1217
+ 3,000 API requests per minute and 20 login attempts per minute. Requests exceeding
1218
+ these limits receive HTTP 429. MCP permits 32 concurrent requests, with at most
1219
+ four bodies larger than 1 MiB or of unknown/chunked length; ordinary attachment
1220
+ uploads also have four slots. Authentication runs before body buffering. Main
1221
+ WebSocket messages are capped at 1 MiB, terminal messages at 128 KiB; sockets
1222
+ have per-peer and global caps and inbound message-rate limits. Behind a proxy,
1223
+ clients share the proxy peer's bucket, so configure client-IP limits at the proxy
1224
+ for deployments with many simultaneous clients.
1181
1225
 
1182
1226
  ### Security caveats
1183
1227
 
@@ -1300,7 +1344,7 @@ retrieved once — see [Token / login flow](#token--login-flow).
1300
1344
  | `${HOME}/.claude.json:/root/.claude.json` | RW | Claude Code auth + MCP server registrations. **Must already exist as a file on the host** (run `claude /login` locally first if it doesn't) — Docker silently creates an empty *directory* instead of erroring on a missing bind-mount source, which breaks auth in a confusing way. |
1301
1345
  | `${HOME}/.claude:/root/.claude` | RW | Claude Code's full state directory (skills/plugins cache, session transcripts, memory) — Claude Code writes into this tree during normal operation. |
1302
1346
  | `${HOME}/.codex:/root/.codex` | RW | Codex's full state directory (`auth.json`, `config.toml`). Harmless to mount even if you only use Claude Code. |
1303
- | `/var/run/docker.sock:/var/run/docker.sock` | RW, enabled in `example.yml`, opt-in in `vps.yml` | Lets the dev-server panel start/stop/tail logs for your projects' own `docker compose` stacks. **DANGER**: grants the container root-equivalent control over the host — anyone who can run a command inside `kobo` can spawn a privileged container that mounts the host filesystem. Remove this line if you don't need the dev-server panel. |
1347
+ | `/var/run/docker.sock:/var/run/docker.sock` | RW, opt-in in both `example.yml` and `vps.yml` | Lets the dev-server panel start/stop/tail logs for your projects' own `docker compose` stacks. **DANGER**: grants the container root-equivalent control over the host — anyone who can run a command inside `kobo` can spawn a privileged container that mounts the host filesystem. Leave this mount commented out unless required. |
1304
1348
 
1305
1349
  ### Ports
1306
1350
 
@@ -1316,25 +1360,32 @@ reachable, matching the real production posture.
1316
1360
 
1317
1361
  ### Token / login flow
1318
1362
 
1319
- All four files pre-enable network access via env vars, so there's no interactive Settings-UI step.
1320
- Retrieve the auto-generated token from the container's boot log, then bookmark the token-bearing URL:
1363
+ All four files pre-enable network access via environment variables. Startup logs
1364
+ never contain the token. Retrieve it deliberately from a trusted local terminal:
1321
1365
 
1322
1366
  ```bash
1323
- docker compose -f <compose-file> logs kobo | grep Token
1367
+ docker compose -f <compose-file> exec kobo node -e 'const fs = require("node:fs"); console.log(JSON.parse(fs.readFileSync(process.env.KOBO_HOME + "/settings.json", "utf8")).global.networkAccessToken)'
1324
1368
  ```
1325
1369
 
1326
- Open `http://<host>/?token=<token>` (exact host depends on the file — see the walkthroughs below).
1327
- The token is stored in the browser's `localStorage`, so you only paste it once per browser.
1370
+ Open the plain application URL and paste the token into its login dialog. Do not
1371
+ put the token in a URL, command argument, log collector or shared transcript.
1372
+ For an npm installation, read `networkAccessToken` from your private
1373
+ `$KOBO_HOME/settings.json` (default `~/.config/kobo/settings.json`).
1374
+ Kōbō restricts its home to mode `0700` and settings, temporary settings and
1375
+ settings backups to `0600`, including existing files on startup or the next disk read.
1376
+
1377
+ Existing logs from older releases may still contain a valid token. After upgrading,
1378
+ rotate it from Settings and apply your log-retention policy to retained copies.
1379
+ Kōbō does not delete historical logs automatically.
1328
1380
 
1329
1381
  ### Quick local test — `docker-compose.local.yml`
1330
1382
 
1331
1383
  ```bash
1332
1384
  docker build -t kobo:local . # once
1333
1385
  docker compose -f docker-compose.local.yml up -d
1334
- docker compose -f docker-compose.local.yml logs kobo | grep Token
1335
1386
  ```
1336
1387
 
1337
- Open `http://localhost:3000/?token=<token from above>`.
1388
+ Open `http://localhost:3000/`; retrieve the token using [Token / login flow](#token--login-flow) if prompted.
1338
1389
 
1339
1390
  By default the file bind-mounts `./kobo-projects` (next to it) at `/projects` — put a real git repo
1340
1391
  in there (or set `KOBO_PROJECTS_DIR` to point elsewhere) before creating a workspace. When you get
@@ -1353,10 +1404,9 @@ and lose all Kōbō state).
1353
1404
  ```bash
1354
1405
  docker build -t kobo:local . # once, if not already built
1355
1406
  docker compose -f docker-compose.local-traefik.yml up -d
1356
- docker compose -f docker-compose.local-traefik.yml logs kobo | grep Token
1357
1407
  ```
1358
1408
 
1359
- Open `http://kobo.localhost/?token=<token from above>` — `.localhost` resolves to `127.0.0.1` on
1409
+ Open `http://kobo.localhost/` and sign in using [Token / login flow](#token--login-flow) — `.localhost` resolves to `127.0.0.1` on
1360
1410
  most OSes without touching `/etc/hosts`. This exercises the exact routing/auth/mount setup used by
1361
1411
  `docker-compose.example.yml`, minus a real domain and TLS certificate.
1362
1412
 
@@ -1386,17 +1436,16 @@ Before using it:
1386
1436
  2. Replace `kobo.example.com` with your real domain, and `you@example.com` with a real email (Let's Encrypt account).
1387
1437
  3. Populate a `.env` file next to the compose file with `GH_TOKEN` / `GITLAB_TOKEN` if you use the forge integration.
1388
1438
  4. Point every bind mount (`~/.ssh`, `~/.gitconfig`, `~/.claude.json`, `~/.claude`, `~/.codex`) at real paths on the host — see [Volumes](#volumes) for what each one is for, in particular the "`~/.claude.json` must already exist as a file" gotcha.
1389
- 5. Decide whether you need the Docker-socket passthrough (dev-server panel). If not, delete that volume line — see the DANGER note in [Volumes](#volumes).
1439
+ 5. Leave the Docker socket unmounted unless your project workflows require it; opting in grants host-level control. See [Volumes](#volumes).
1390
1440
  6. Network access + behind-a-reverse-proxy are already pre-enabled via the `KOBO_NETWORK_ACCESS_*` env vars in the file — no manual bootstrap step.
1391
1441
 
1392
- Bring it up, grab the token, bookmark the URL:
1442
+ Start it, then retrieve the token using the login flow above:
1393
1443
 
1394
1444
  ```bash
1395
1445
  docker compose -f docker-compose.example.yml up -d
1396
- docker compose -f docker-compose.example.yml logs kobo | grep Token
1397
1446
  ```
1398
1447
 
1399
- Open `https://kobo.example.com/?token=<token>`. SSH access: `ssh -p 2222 root@<host>` (key-based
1448
+ Open `https://kobo.example.com/` and sign in using [Token / login flow](#token--login-flow). SSH access: `ssh -p 2222 root@<host>` (key-based
1400
1449
  only). See [Ports](#ports) and [Volumes](#volumes) above for the full rationale on the Docker socket
1401
1450
  and SSH tradeoffs before deploying this to a real machine.
1402
1451
 
@@ -1455,9 +1504,8 @@ An old image alone is not a complete rollback after database migrations.
1455
1504
  — check `docker inspect <container> --format '{{json .State.Health}}'`. This should not happen on
1456
1505
  a current image (`/api/health` is exempt from the token gate, see [The image](#the-image)); if you
1457
1506
  see a 401 in the health log on a custom-built image, you're missing that exemption.
1458
- - **`docker compose logs kobo | grep Token` returns nothing** — network access wasn't enabled at
1459
- boot. Confirm `KOBO_NETWORK_ACCESS_ENABLED=true` is actually set in the `environment:` block for
1460
- the `kobo` service.
1507
+ - **The token is absent from startup logs** — this is intentional. Use the local
1508
+ [token retrieval command](#token--login-flow), then the browser login dialog.
1461
1509
 
1462
1510
  ## Agent runtimes
1463
1511
 
@@ -1473,7 +1521,7 @@ claude /login
1473
1521
 
1474
1522
  Kōbō talks to the embedded [`@anthropic-ai/claude-agent-sdk`](https://github.com/anthropics/claude-agent-sdk-typescript), which reuses the same login. The `claude` CLI is **not** required at runtime; you only need it for `/login`, `mcp add`, and other one-off setup commands. As a fallback you can export `ANTHROPIC_API_KEY` instead.
1475
1523
 
1476
- The model selector is populated from [`src/shared/models.ts`](./src/shared/models.ts), also used by backend validation. This checkout includes Fable 5.1, Mythos 5, Opus 5, Opus 4.8, Sonnet 5 and older variants. Pick the default in **Settings → Agents**. Catalogue entries describe what Kōbō offers; provider access still depends on your account. `auto` leaves model selection to the runtime.
1524
+ The model selector is populated from [`src/shared/models.ts`](./src/shared/models.ts), also used by backend validation. This checkout includes Fable 5.1, Mythos 5.1/5, Opus 5.5, Sonnet 5.5, Haiku 5.5 (`claude-haiku-5-5`) and older variants. Pick the default in **Settings → Agents**. Catalogue entries describe what Kōbō offers; provider access still depends on your account. `auto` leaves model selection to the runtime.
1477
1525
 
1478
1526
  #### Immediate chat messages
1479
1527
 
@@ -1549,6 +1597,65 @@ When a Codex session is already working, **Send now** sends a queued chat messag
1549
1597
 
1550
1598
  The selector reads [`src/shared/codex-models.ts`](./src/shared/codex-models.ts). This checkout lists GPT-6 Astra, GPT-5.6 Sol/Terra/Luna, GPT-5.5 and older variants; availability depends on your account and authentication. Kōbō exposes effort values `auto`, `none`, `low`, `medium`, `high`, `xhigh`, and `max`, as declared in the engine's capabilities. The runtime must support the selected model/effort combination. Both selectors switch when you change the engine.
1551
1599
 
1600
+ ### Switch Claude Code and Codex accounts with ccswap
1601
+
1602
+ The usage popover in the chat footer can show the current **managed account**
1603
+ and switch to another account registered in [ccswap](https://github.com/errhythm/ccswap).
1604
+ It follows the selected session's engine, falling back to the workspace engine.
1605
+ Choose an account and explicitly click **Switch account**. Claude Code and Codex
1606
+ have separate account lists; switching one does not switch the other.
1607
+
1608
+ ccswap is optional and is not bundled with Kōbō. Without its executable, Kōbō
1609
+ shows no account selector or installation notice; existing Claude usage indicators
1610
+ continue to work. Codex account controls do not imply that Claude-style quota
1611
+ buckets are available for Codex.
1612
+
1613
+ Install and register accounts **as the same operating-system user and in the
1614
+ same environment as the Kōbō server**. The integration uses ccswap's JSON CLI
1615
+ (tested with 0.37.0). With `uv` already installed:
1616
+
1617
+ ```bash
1618
+ uv tool install ccswap
1619
+ # After logging into Claude Code with each account:
1620
+ ccswap add
1621
+ # After `codex login` with each Codex account:
1622
+ ccswap codex add
1623
+ ccswap list --provider claude
1624
+ ccswap codex list
1625
+ ```
1626
+
1627
+ Register the current login before switching so ccswap can preserve it. Account
1628
+ registration, login renewal, aliases and removal remain CLI operations. A disabled
1629
+ account cannot be selected in Kōbō. If no managed account matches the current
1630
+ login, the panel reports an unknown account rather than guessing from a slot.
1631
+
1632
+ **Scope:** switching changes that provider's active credentials for the server
1633
+ user, shared by all Kōbō workspaces and other tools using that same login store.
1634
+ It is not a per-workspace account assignment. Kōbō keeps its worktrees, conversation
1635
+ history, settings and skills; it does not copy profiles or automatically restart
1636
+ agents. The displayed account identifies the active credential store, not proof
1637
+ that an already-running process has adopted it. Stop and resume the affected
1638
+ sessions if they still use the previous account, especially with Codex. Existing
1639
+ API-key or custom-provider authentication can take precedence over a saved login.
1640
+
1641
+ The adapter inherits absolute `CLAUDE_CONFIG_DIR` on Linux and `CODEX_HOME`.
1642
+ Empty or relative authentication-directory overrides are refused: server and workspace
1643
+ processes can have different working directories. Claude switching is also refused
1644
+ with `CLAUDE_SECURESTORAGE_CONFIG_DIR` set, or with any nonempty `CLAUDE_CONFIG_DIR`
1645
+ on macOS (even the explicit default path), because the corresponding Keychain
1646
+ write is not verified by this integration. Use the native login workflow for
1647
+ those configurations.
1648
+
1649
+ Claude usage is invalidated and refreshed after a switch. A failed refresh is
1650
+ not shown as the new account's old quota. Switching failures do not trigger an
1651
+ automatic retry: refresh the account list to check the actual state before trying
1652
+ again. The server exposes display metadata only, never ccswap's credential files
1653
+ or raw command output.
1654
+
1655
+ For Docker/VPS, installing ccswap on your PC or on the VPS host is insufficient:
1656
+ it must be reachable on **Kōbō's container PATH**, with the same runtime home and
1657
+ credential-directory environment. See [VPS account setup](./docs/vps-projects-and-skills.md#switch-engine-accounts-inside-the-container).
1658
+
1552
1659
  ### Permission modes
1553
1660
 
1554
1661
  Claude Code uses SDK permission modes; Codex uses sandbox and approval flags. These controls differ by engine. A Git worktree itself is not a security boundary.
package/README.md CHANGED
@@ -64,7 +64,8 @@ Kōbō's production build is an installable PWA — use your browser's **Install
64
64
  - **Interactive Q&A**: an agent can pause mid-session to ask you a question through the UI instead of guessing.
65
65
 
66
66
  ![Agent asking a clarifying question, awaiting the user's answer](docs/assets/images/agent-question.png)
67
- - **Quota-aware**: 5-hour / 7-day Claude usage and Codex rate-limit buckets live in the footer; auto-loops can resume after a reset. Explicit cron/wakeup schedules can also resume work; a manual workspace does not restart solely because its quota resets.
67
+ - **Quota-aware**: 5-hour / 7-day Claude usage is shown in the footer; engine-reported rate limits guide quota waits; auto-loops can resume after a reset. Explicit cron/wakeup schedules can also resume work; a manual workspace does not restart solely because its quota resets.
68
+ - **Account switching**: with optional [ccswap](https://github.com/errhythm/ccswap), view and switch the active Claude Code or Codex account from the usage popover. Credentials are shared across workspaces using that engine; [setup and session behavior](./CONFIGURATION.md#switch-claude-code-and-codex-accounts-with-ccswap).
68
69
  - **Disk-space purge**: reclaim a merged workspace's `node_modules`/`vendor` weight without losing its chat history — see [`CONFIGURATION.md`](./CONFIGURATION.md#auto-purge-worktree-on-pr-merged).
69
70
  - **Lifecycle scripts**: shell scripts run on setup, cleanup, archive, session end, PR merge, or auto-loop stop, with output streamed into the chat.
70
71
  - **Observability**: a per-session timeline (duration, tools, tokens, errors) and a downloadable redacted diagnostic JSON.
package/SECURITY.md CHANGED
@@ -17,11 +17,37 @@ the account running Kōbō and the selected engine's permission mode. A Git work
17
17
  isolates a checkout, not processes, credentials or the filesystem. Only open
18
18
  projects and configure executable hooks/integrations you trust.
19
19
 
20
- The default listener is loopback. LAN access uses one shared token; it is not
21
- per-user authorization. Behind a reverse proxy, enable the corresponding setting
22
- so loopback API/WebSocket requests also require that token. Health checks are
23
- exempt. Host and Origin validation remain separate protections. Use HTTPS or a
24
- trusted private network; do not offer this installation as a shared public service.
20
+ The default listener is loopback. Network access uses an operator token; it is not
21
+ per-user authorization. Browsers exchange it for a 12-hour HttpOnly, SameSite=Strict
22
+ cookie (Secure behind HTTPS proxies), never persist it in localStorage, and obtain
23
+ 60-second single-use tickets for each WebSocket path. Rotation, disabling network
24
+ access, or changing proxy mode revokes sessions, pending tickets and active sockets,
25
+ including terminals. Restarting Kōbō also expires sessions. A settings change may
26
+ establish a new session for the browser that performed it using the returned token.
27
+
28
+ Behind a reverse proxy, enable the corresponding setting and declare the exact
29
+ public origin with `KOBO_NETWORK_ACCESS_PROXY_HOST` (bare domains default to HTTPS).
30
+ Health checks are exempt. Host and Origin validation remain separate protections.
31
+ Use HTTPS or a trusted private network; do not offer this installation as a shared
32
+ public service. Configure HSTS at the HTTPS terminator. The supplied public Compose
33
+ examples include it. Socket and request size/rate/concurrency limits are bounded
34
+ in-process; proxy limits should complement them. Client IPs come from OS sockets,
35
+ so callers behind the same proxy share limits.
36
+
37
+ External MCP requires a separately issued Bearer credential, including on loopback.
38
+ Each key has explicit tool and workspace grants, optional expiry, and independent
39
+ revocation; only its hash is stored. The UI defaults to read-only tools, selected
40
+ workspaces and 30-day expiry. Operator cookies/tokens cannot authenticate `/api/mcp`,
41
+ and an MCP key cannot authenticate operator APIs or terminal upgrades. Global stdio
42
+ forwards to that endpoint using `KOBO_MCP_TOKEN`; it does not read the database offline.
43
+
44
+ Managed workspace stdio uses a per-launch `KOBO_MCP_SESSION_TOKEN` through the local
45
+ `/api/internal-mcp` dispatcher. The backend owns authorization and database writes;
46
+ Plan and read-only review launches cannot invoke mutating managed tools even if the
47
+ provider approves a tool. Launch capabilities are revoked when stopping begins.
48
+ Operational progress and narrowly scoped review/handoff reports retain their own
49
+ capabilities. This tool boundary does not sandbox arbitrary code executed by an
50
+ agent or other integrations running as the same OS account.
25
51
 
26
52
  Git workflow preferences express which automatic actions are wanted. They are not
27
53
  a sandbox for arbitrary shell commands or external MCP tools. Claude and Codex
@@ -33,7 +59,8 @@ network token.
33
59
 
34
60
  KOBO_HOME stores conversations, workspaces, backups and settings. Direct integration
35
61
  commands and credentials live in `integrations.json`, written atomically with mode
36
- 0600 on Unix. They are plaintext local secrets: protect the host account and backups.
62
+ 0600 on Unix. Settings and their temporary files/backups are also private (0600),
63
+ and new Kōbō home directories use 0700. They are plaintext local secrets: protect the host account and backups.
37
64
  They are excluded from settings exports and are never returned by the connection
38
65
  status API. A replacement connection must be entered in full. MCP error text and
39
66
  stderr are withheld because third-party processes may echo secrets.