fix(terminal): derive stable write token so operate links survive worker restart#581
Open
47seek wants to merge 2 commits into
Open
fix(terminal): derive stable write token so operate links survive worker restart#58147seek wants to merge 2 commits into
47seek wants to merge 2 commits into
Conversation
…ker restart
The read-only view token was migrated to a stable per-session HMAC
(deriveTerminalViewToken), but the write/operate token was still a
per-process `randomBytes(16)` minted at worker boot. A silent daemon
restart re-forks every worker, regenerating that token — so any
already-issued 「操作链接」/write link (`?token=`) immediately 403s
("Forbidden") even though the session is alive and reachable via its
(stable) view link.
Fix: derive the write token the same way as the view token, from the
host-only dashboard secret, using a DISTINCT domain separator
(`botmux-terminal-write-v1`) so the two capabilities can never collide —
knowing the read token must never yield the write token. A restarted
worker now re-mints the same operate token, keeping previously-issued
write links valid. Falls back to the random boot token when the secret
is unavailable (standalone/test), and the secret stays masked from
sandboxed CLIs, so a sandboxed CLI still cannot mint its own write link.
Tests: write token is stable per session, domain/secret-bound, and
distinct from the view token for the same session+secret.
Co-authored-by: TRAE CLI <noreply@bytedance.com>
Lock the worker init → refreshTerminalWriteToken → ready.token path so a regression back to a per-process random write token (which 403s every already-issued 操作链接 on worker restart) is caught. Mirrors the existing ready.viewToken assertion.
Owner
Follow-up: integration assertion for the stable write token (relay)Per maintainer request, added the non-blocking test hardening discussed in review: the existing worker integration test already spawns a real worker with a deterministic host-only secret and asserts expect(ready.token).toBe(deriveTerminalWriteToken(secret, sessionId));This locks the real Scope: single test file, +5/-1. No production code touched. Verification (local,
Commit: 2387ec5 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The web-terminal 「🔑 操作链接」/write link (
http://<host>:<port>/s/<sessionId>?token=...) starts returning 403 Forbidden after a while, even though the session is still alive and its read-only 「🖥️ 打开 Web 终端」link opens fine.Root cause
The read-only view token was already migrated to a stable per-session HMAC (
deriveTerminalViewToken), so read links survive restarts. But the write/operate token is still a per-process value minted at worker boot:A silent daemon restart re-forks every worker, regenerating that token — so every previously-issued write link immediately 403s.
Reproduced on a live session:
?token=…→ HTTP 403i.e. the session is reachable; only the operate token went stale after the worker restart.
Fix
Derive the write token the same way as the view token — from the host-only dashboard secret — but with a distinct domain separator
botmux-terminal-write-v1so the two capabilities can never collide (knowing the read token must never yield the write token, and vice-versa). A restarted worker re-mints the same operate token, keeping already-issued write links valid.Tests
test/terminal-write-auth.test.tsadds: write token is stable per session, domain/secret-bound, and distinct from the view token for the same session+secret.npx tsc --noEmit✅vitest runterminal-write-auth (31) + worker read-auth integration (1) all green ✅