You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(serve): stop serving WS token over HTTP and warn on non-loopback bind (H9)
- The per-instance WebSocket token is now printed to the console only, like a Jupyter token.
- handleStatic only injects the token cookie/meta tag when the request includes ?token=<token>.
- A plain GET / returns the UI with an empty token, so unauthenticated visitors cannot retrieve the credential.
- A loud warning is printed when the bind address is not loopback.
- Update tests, AGENTS.md, and docs/SECURITY.md.
-**FD-based API key handoff** (`cmd/odek/subagent_key.go`) — parent writes key to a 0600 tempfile, immediately `unlink()`s, passes the FD via `cmd.ExtraFiles`. Sub-agent reads from `$ODEK_API_KEY_FD` and closes. Key never in `/proc/<pid>/environ`.
115
115
-**Approver friction** (`internal/danger/approver.go`, `cmd/odek/wsapprover.go`) — both TTYApprover and WSApprover engage friction mode after 3 approvals of the same class in 60s: require typing literal `approve`, 1.5s pause. Trust-class shortcut disabled for `destructive` + `blocked` regardless.
116
116
-**Danger classifier bypass resistance** (`internal/danger/classifier.go`) — `normalize()` pre-processes: expand `$IFS` / `${IFS}`, extract `$(...)` / `` `...` `` substitutions, strip `command` / `exec` / `builtin` wrappers, collapse unquoted backslashes, basename absolute paths. `awk`/`gawk`, `sed` (`e` command / `-f`), and editors (`vi`/`vim`/`nvim`/`emacs`/`ed`/`ex`) are classified as `code_execution` when given a script or file operand, closing `awk 'BEGIN{system(...)}'`, `sed 's///e'`, and editor `!` shell escapes. Regression suite in `classifier_bypass_test.go`.
117
-
-**WebSocket CSRF token** (`cmd/odek/serve.go`) — `odek serve` issues a random 256-bit token at startup, injects it into `index.html`, sets an `HttpOnly``SameSite=Strict` cookie, and requires it on `/ws` via cookie, `X-Odek-Ws-Token` header, or `odek.<token>` subprotocol. The localhost origin check remains as defense-in-depth.
117
+
-**WebSocket CSRF token** (`cmd/odek/serve.go`) — `odek serve` issues a random 256-bit token at startupand requires it on `/ws` via cookie, `X-Odek-Ws-Token` header, or `odek.<token>` subprotocol. The token is no longer embedded in every `GET /` response; it is only delivered when the request includes the correct `?token=` query parameter (Jupyter-style), and a non-loopback bind prints a loud warning. The localhost origin check remains as defense-in-depth.
118
118
-**SSRF / DNS-rebinding dial guard** (`cmd/odek/ssrf_guard.go`) — `browser`, `http_batch`, and `web_search` resolve hostnames at dial time and refuse internal IPs, then pin the dial to the validated IP. Operator-configured backends (e.g. `web_search.base_url`) are added to an allowlist so container-internal services such as SearXNG remain reachable.
119
119
-**REST API CSRF protection** (`cmd/odek/serve.go::requireLocalOrigin`) — state-changing HTTP endpoints (POST/PUT/PATCH/DELETE) require a localhost origin or no Origin header, and static responses set `X-Frame-Options: DENY` + `Content-Security-Policy: frame-ancestors 'none'` to block clickjacking.
120
120
-**Browser history cap** (`cmd/odek/browser_tool.go`) — navigation history is capped at 50 snapshots to prevent memory DoS from repeated `browser_navigate` calls.
Copy file name to clipboardExpand all lines: docs/SECURITY.md
+14Lines changed: 14 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -550,6 +550,20 @@ The `/api/resources?q=...&limit=N` autocomplete endpoint previously accepted any
550
550
551
551
This bounds the memory/container blast radius if a local process or malicious page tries to spawn many agent sessions.
552
552
553
+
### 41a. WebSocket token no longer served over plain HTTP
554
+
555
+
`odek serve` previously embedded the per-instance WebSocket token in every `GET /` response (as an HTML meta tag and an HttpOnly cookie). Because `GET /` was not behind any authentication, any network attacker who could reach the port could fetch the token, upgrade to `/ws`, and drive the agent using the operator's model, tools, and API key.
556
+
557
+
The token is now treated like a Jupyter notebook token:
558
+
559
+
- It is printed to the console on startup, together with a URL of the form `http://<addr>/?token=<token>`.
560
+
- The browser only receives the cookie/meta tag when it requests `/` with the correct `?token=` query parameter.
561
+
- A plain `GET /` returns the UI but leaves the token field empty, so it cannot connect.
562
+
- Non-browser clients can supply the token via the `X-Odek-Ws-Token` header or the `odek.<token>` WebSocket subprotocol.
563
+
- A loud warning is printed when the server is bound to a non-loopback address.
564
+
565
+
This removes the unauthenticated token disclosure path while preserving the same browser experience for the operator who has access to the startup console output.
566
+
553
567
### 42. Sub-agent progress stream limits
554
568
555
569
`delegate_tasks` streams NDJSON progress lines from each sub-agent. A runaway or malicious sub-agent could emit an unbounded number of `tool_call`/`tool_result` events, causing unbounded memory growth in the parent. `scanSubagentStream` now caps the total progress stream at 100 000 lines and 100 MiB of data; exceeding either limit aborts the scan and cancels the sub-agent context so the child process is killed instead of continuing to flood stdout.
0 commit comments