@loicngr/kobo 2.1.26 → 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.
- package/AGENTS.md +6 -3
- package/CHANGELOG.md +11 -0
- package/CONFIGURATION.md +97 -49
- package/SECURITY.md +33 -6
- package/dist/mcp-server/kobo-tasks-server.js +36 -1021
- package/dist/mcp-server/managed-mcp-dispatcher.js +960 -0
- package/dist/mcp-server/workspace-dialogue-client.js +26 -1
- package/dist/server/index.js +52 -19
- package/dist/server/middleware/host-check-middleware.js +8 -5
- package/dist/server/middleware/network-auth-middleware.js +51 -29
- package/dist/server/middleware/security-headers-middleware.js +19 -0
- package/dist/server/middleware/traffic-limits-middleware.js +74 -0
- package/dist/server/routes/auth.js +73 -0
- package/dist/server/routes/internal-mcp.js +137 -0
- package/dist/server/routes/mcp-credentials.js +30 -0
- package/dist/server/routes/mcp.js +156 -76
- package/dist/server/routes/workspaces.js +19 -3
- package/dist/server/services/agent/orchestrator.js +27 -17
- package/dist/server/services/external-mcp-credentials-service.js +110 -0
- package/dist/server/services/forge/github/provider.js +15 -3
- package/dist/server/services/forge/types.js +8 -0
- package/dist/server/services/internal-mcp-capability-service.js +98 -0
- package/dist/server/services/mcp-connection-info-service.js +1 -1
- package/dist/server/services/memory-mcp-service.js +9 -7
- package/dist/server/services/memory-service.js +15 -0
- package/dist/server/services/network-access-service.js +43 -73
- package/dist/server/services/network-session-service.js +182 -0
- package/dist/server/services/settings-service.js +29 -7
- package/dist/server/services/terminal-websocket-service.js +5 -0
- package/dist/server/services/websocket-service.js +5 -0
- package/dist/server/utils/paths.js +2 -1
- package/dist/server/utils/startup-display.js +1 -2
- package/dist/shared/models.js +6 -0
- package/docker-compose.vps.yml +6 -0
- package/docs/deployment-vps.md +24 -12
- package/package.json +1 -1
- package/src/client/dist/pwa/assets/ActivityFeed-D58VS9o2.js +2 -0
- package/src/client/dist/pwa/assets/{AutoLoopFinalReviewSettings-DOr41E2Y.js → AutoLoopFinalReviewSettings-CK3r_fVB.js} +1 -1
- package/src/client/dist/pwa/assets/{ChangelogPage-DOCgZMpn.js → ChangelogPage-CAAUEn5X.js} +1 -1
- package/src/client/dist/pwa/assets/ComparisonPanel-BCb7vB5E.js +2 -0
- package/src/client/dist/pwa/assets/CreatePage-DasH1Uk4.js +2 -0
- package/src/client/dist/pwa/assets/{DashboardPage-dBpJaQSb.js → DashboardPage-XE52EABP.js} +1 -1
- package/src/client/dist/pwa/assets/DiffViewer-CncKlXne.js +8 -0
- package/src/client/dist/pwa/assets/{DocumentsPanel-AqLx0MzB.js → DocumentsPanel-L4pbQfz1.js} +1 -1
- package/src/client/dist/pwa/assets/{DrawerToggleButton-BLWaFgh0.js → DrawerToggleButton-DiOJm5he.js} +1 -1
- package/src/client/dist/pwa/assets/GitPanel-WJlzy2VJ.js +3 -0
- package/src/client/dist/pwa/assets/{GitPanel-CRaLJzjc.css → GitPanel-yHvU38dp.css} +1 -1
- package/src/client/dist/pwa/assets/{HealthPage-DeryVLyz.js → HealthPage-hJY5MurK.js} +1 -1
- package/src/client/dist/pwa/assets/MainLayout-DgWElK7w.js +2 -0
- package/src/client/dist/pwa/assets/{MemoryPanel-B34DENtz.js → MemoryPanel-DtEvh_b7.js} +1 -1
- package/src/client/dist/pwa/assets/{MemoryProposalList-BpN_OU8J.js → MemoryProposalList-kgFCm9zz.js} +1 -1
- package/src/client/dist/pwa/assets/{QBtnDropdown-BPcK_rVs.js → QBtnDropdown-CWPIugs8.js} +1 -1
- package/src/client/dist/pwa/assets/{QExpansionItem-CpwQ8L_F.js → QExpansionItem-D-EGL2b3.js} +1 -1
- package/src/client/dist/pwa/assets/{QScrollArea-BkNSVl7s.js → QScrollArea-BMrqK-xd.js} +1 -1
- package/src/client/dist/pwa/assets/QSelect-DFWyDWCE.js +1 -0
- package/src/client/dist/pwa/assets/{QTooltip-Dp8fitHV.js → QTooltip-Bk8dLUtU.js} +1 -1
- package/src/client/dist/pwa/assets/{SchedulePanel-Ck8LKDCa.js → SchedulePanel-CuxmxCKi.js} +1 -1
- package/src/client/dist/pwa/assets/{SearchPage-BAUZg1zq.js → SearchPage-DKnQPUqY.js} +1 -1
- package/src/client/dist/pwa/assets/{SettingsPage-Dcq1Jeoo.css → SettingsPage-B4rmd2WX.css} +1 -1
- package/src/client/dist/pwa/assets/SettingsPage-CO3vIpPd.js +9 -0
- package/src/client/dist/pwa/assets/{SplitWorkspacePage-CH8OHmtn.js → SplitWorkspacePage-WhQyX8fn.js} +1 -1
- package/src/client/dist/pwa/assets/{TerminalPanel-CBmZ0IzQ.js → TerminalPanel-BY0lPq6k.js} +10 -10
- package/src/client/dist/pwa/assets/{TerminalPanel-Dz9EE-Ph.css → TerminalPanel-BcbwuFVR.css} +1 -1
- package/src/client/dist/pwa/assets/{ToolCallItem-CLtGTV6w.js → ToolCallItem-DYubi-J0.js} +3 -3
- package/src/client/dist/pwa/assets/{TouchPan-9SH2dx6m.js → TouchPan-DDotyYLN.js} +1 -1
- package/src/client/dist/pwa/assets/{TourReplayButton-Dw4sGf9B.js → TourReplayButton-kAnEMcy2.js} +1 -1
- package/src/client/dist/pwa/assets/{WorkspaceAttentionLabels-wd_sObJa.js → WorkspaceAttentionLabels-DiYt3rP3.js} +1 -1
- package/src/client/dist/pwa/assets/WorkspacePage-K-1ce50d.js +4 -0
- package/src/client/dist/pwa/assets/{browser-CC4W8r76.js → browser-DVYLH8vl.js} +1 -1
- package/src/client/dist/pwa/assets/{build-path-tree-BBuyB-my.js → build-path-tree-DEuOdIIk.js} +1 -1
- package/src/client/dist/pwa/assets/custom-sounds-Ggw6V9iF.js +1 -0
- package/src/client/dist/pwa/assets/de-Bk3zQrin.js +17 -0
- package/src/client/dist/pwa/assets/{editor.api-BwfxvQ88.js → editor.api-BC38eXAS.js} +1 -1
- package/src/client/dist/pwa/assets/{efforts-B3I7w5bs.js → efforts-TGacUPcJ.js} +1 -1
- package/src/client/dist/pwa/assets/es-f3xuAWA9.js +17 -0
- package/src/client/dist/pwa/assets/{expand-template-Ci9UjnxZ.js → expand-template-DM8gq3Mo.js} +1 -1
- package/src/client/dist/pwa/assets/{format-CJIzi3Vf.js → format-OwxXBnDJ.js} +1 -1
- package/src/client/dist/pwa/assets/{formatters-DQ8cP7nO.js → formatters-YgaCZcwJ.js} +1 -1
- package/src/client/dist/pwa/assets/fr-B6c21SG3.js +17 -0
- package/src/client/dist/pwa/assets/{freemarker2-DbpVO-cL.js → freemarker2-l7R42fI2.js} +1 -1
- package/src/client/dist/pwa/assets/{handlebars-BHve0PpA.js → handlebars-DFmPrgjH.js} +1 -1
- package/src/client/dist/pwa/assets/{html-BhUkdFPz.js → html-CERIAlPM.js} +1 -1
- package/src/client/dist/pwa/assets/i18n-WaE4cZK9.js +1 -0
- package/src/client/dist/pwa/assets/index-CpWBGKdM.js +20 -0
- package/src/client/dist/pwa/assets/it-DUstDpC2.js +17 -0
- package/src/client/dist/pwa/assets/{javascript-BLAorPa3.js → javascript-BBYncplF.js} +1 -1
- package/src/client/dist/pwa/assets/{liquid-CJGF-h-f.js → liquid-C1HqYyYU.js} +1 -1
- package/src/client/dist/pwa/assets/{mdx-Cm0oNz_l.js → mdx-E74dsP22.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco-contributions-nBg6wf05.js → monaco-contributions-DHvm-kp5.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco.contribution-hr9s26Ud.js → monaco.contribution-DYdn9CwR.js} +2 -2
- package/src/client/dist/pwa/assets/network-auth-DTZUKbny.js +1 -0
- package/src/client/dist/pwa/assets/{notify-theme-EcbxuH_8.js → notify-theme-FuTL7HuS.js} +1 -1
- package/src/client/dist/pwa/assets/{permissionModes-DLWiBfdb.js → permissionModes-D-Nexnoo.js} +1 -1
- package/src/client/dist/pwa/assets/{project-color-vDuTaTJn.js → project-color-_snXWYUp.js} +1 -1
- package/src/client/dist/pwa/assets/{python-BEjbzFAQ.js → python-DNAAGBcX.js} +1 -1
- package/src/client/dist/pwa/assets/{razor-BJPT7Wxx.js → razor-CB3g5p_T.js} +1 -1
- package/src/client/dist/pwa/assets/{typescript-DPqM46Ef.js → typescript-0kpz8bqP.js} +1 -1
- package/src/client/dist/pwa/assets/{use-search-index-status-BwwrB410.js → use-search-index-status-D38yFOD_.js} +1 -1
- package/src/client/dist/pwa/assets/{use-tours-B0KtCS9w.js → use-tours-BWkHp34X.js} +2 -2
- package/src/client/dist/pwa/assets/{use-worktree-restore-tzd2FESU.js → use-worktree-restore-DUjZr44N.js} +1 -1
- package/src/client/dist/pwa/assets/{workspace-templates-CubzsZUU.js → workspace-templates-D-lPJQ4G.js} +1 -1
- package/src/client/dist/pwa/assets/{xml-C6SVzw5_.js → xml-BbOuqdUe.js} +1 -1
- package/src/client/dist/pwa/assets/{yaml-OpCqnv4g.js → yaml-gasFQtfk.js} +1 -1
- package/src/client/dist/pwa/index.html +1 -1
- package/src/client/dist/pwa/sw.js +1 -1
- package/src/mcp-server/README.md +13 -19
- package/src/mcp-server/kobo-tasks-server.ts +42 -1146
- package/src/mcp-server/managed-mcp-dispatcher.ts +1071 -0
- package/src/mcp-server/workspace-dialogue-client.ts +26 -1
- package/src/client/dist/pwa/assets/ActivityFeed-DfYrVNEh.js +0 -2
- package/src/client/dist/pwa/assets/ComparisonPanel-b5nTBjK6.js +0 -2
- package/src/client/dist/pwa/assets/CreatePage-B7VPHEqm.js +0 -2
- package/src/client/dist/pwa/assets/DiffViewer-BLQz2Qyx.js +0 -8
- package/src/client/dist/pwa/assets/GitPanel-Ba7ltCsM.js +0 -3
- package/src/client/dist/pwa/assets/MainLayout-B2XvjdWi.js +0 -2
- package/src/client/dist/pwa/assets/QSelect-DrLpbvcr.js +0 -1
- package/src/client/dist/pwa/assets/SettingsPage-Cj6kSVT5.js +0 -9
- package/src/client/dist/pwa/assets/WorkspacePage-D4ODPOG-.js +0 -4
- package/src/client/dist/pwa/assets/custom-sounds-BbPNRzAs.js +0 -1
- package/src/client/dist/pwa/assets/de-D9VzPDGF.js +0 -17
- package/src/client/dist/pwa/assets/es-3R4MJuTt.js +0 -17
- package/src/client/dist/pwa/assets/fr-DXwDVnJ4.js +0 -17
- package/src/client/dist/pwa/assets/i18n-CGLQAADf.js +0 -1
- package/src/client/dist/pwa/assets/index-CNcXBpzz.js +0 -20
- package/src/client/dist/pwa/assets/it-T9AjnG42.js +0 -17
- package/src/client/dist/pwa/assets/network-auth-C850jGfI.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
|
|
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 #
|
|
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__/
|
|
@@ -292,7 +295,7 @@ also bypasses the auto-loop next-iteration UI path; normal backend immediate
|
|
|
292
295
|
message delivery pauses a ready loop. Do not flush existing queues on toggle,
|
|
293
296
|
remove compaction/permission/handoff guards, or queue rejected forced sends.
|
|
294
297
|
|
|
295
|
-
`POST /api/mcp` exposes stateless Streamable HTTP discovery/dialogue tools, behind
|
|
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.
|
|
296
299
|
|
|
297
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.
|
|
298
301
|
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,17 @@ 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
|
+
|
|
7
18
|
## 2.1.26
|
|
8
19
|
|
|
9
20
|
- feat: switch Claude and Codex accounts with ccswap
|
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
|
-
|
|
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
|
|
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,
|
|
1062
|
-
|
|
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
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
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
|
|
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 |
|
|
1116
|
+
| Connection type | Authentication |
|
|
1098
1117
|
|---|---|
|
|
1099
|
-
|
|
|
1100
|
-
|
|
|
1101
|
-
|
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
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
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
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
|
|
1169
|
-
|
|
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
|
|
1179
|
-
|
|
1180
|
-
|
|
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,
|
|
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
|
|
1320
|
-
|
|
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>
|
|
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
|
|
1327
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
-
|
|
1459
|
-
|
|
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,
|
|
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
|
|
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.
|
|
21
|
-
per-user authorization.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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.
|
|
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.
|