feat(server): add bearer-token auth and safe host exposure - #1006
Merged
Conversation
🦋 Changeset detectedLatest commit: 097b17c The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
commit: |
Boot startServer on port 0 and snapshot the documented v1 route table derived from /openapi.json paths, plus the reachability of doc/meta endpoints (/healthz, /openapi.json, /asyncapi.json, /). Gives later auth/--host phases an intentional diff when routes change. M0 makes no production behavior change.
Add test/helpers/serverHarness.ts: boot() wraps startServer with an isolated lock + home dir and returns a handle (server, address, baseUrl, wsUrl, token, close) plus authedFetch/authedWs that carry Authorization: Bearer <token> (and the kimi-code.bearer.<token> WS subprotocol). serviceOverrides is the generic DI seam later phases use to inject a fixed-token auth service; IAuthTokenService is not referenced yet. closeAll() tears down every booted server and socket. M0 makes no production behavior change; typecheck-only gate.
- Replace native @node-rs/bcrypt with pure-JS bcryptjs so the ESM CLI bundle and the SEA native bundle both build without native-addon require issues (node-rs/bcrypt broke the ESM smoke and the SEA check-bundle allowlist). - Remove dead cleanup references (stopSpinner, authLogoBlinkTimer) in apps/kimi-web App.vue that failed vue-tsc. - Fix lint: drop empty spread fallbacks in the e2e auth-header merge, void the intentionally-async WS upgrade listener, add missing assertions to satisfy jest/expect-expect, and convert a ternary statement to if/else. - Send the bearer token in the snapshot perf/smoke tests so they pass under the new global auth hook. - Refresh the pnpmDeps hash in flake.nix for the updated lockfile.
- persist the server bearer token in <home>/server.token (0600) and reuse it across restarts instead of per-start server-<pid>.token - add `kimi server rotate-token` to regenerate the token; the token store reloads on mtime/inode change so rotation applies without restart - print the token and Vite-style Local/Network URLs in the startup banner - allow non-loopback binds with bearer-token-only auth (password now optional) and update SECURITY.md - surface daemon boot failures immediately with the exit reason and log tail instead of waiting for the spawn timeout
- Drop the ready-panel border so token URLs print in full for copying; keep the Kimi sprite beside the title. - Re-print Local/Network access links after `server rotate-token` (host/port from the lock). - Extract shared access-URL helpers into access-urls.ts. - Unify link and token colors between the banner and rotate-token.
- Render the `#token=…` fragment in a dim gray so the host/port stands out in the banner and rotate-token links. - De-highlight the standalone token; set it off with surrounding whitespace instead of color. - Add splitTokenFragment helper.
- move version onto the ready banner title line; drop the separate Ready:/Version: rows and the startup-time metric - reorder rotate-token output so the new token sits between the invalidation note and the access links - update server CLI tests for the new layout
- Warn when `server run` reuses an already-running daemon (its options are not applied) and show the running server's actual URLs. - Show a `Network: off use --host 0.0.0.0 to enable` hint on loopback binds. - Move the version onto the title line and drop the startup-time metric.
- Relabel the server auth dialog from "password" to "token"; the server accepts the bearer token, with the password only as a fallback. - Make the auth dialog overlay fully opaque so it covers the whole page instead of revealing the login page underneath.
- Replace chalk.yellow named color with chalk.hex(darkColors.warning) in the server reuse notice to satisfy the chalk named color guard. - Update pnpmDeps hash in flake.nix to match the regenerated pnpm-lock.yaml so the Nix build succeeds. - Retry rmSync in ws-broadcast e2e teardown to ride out EBUSY / ENOTEMPTY races while the server flushes files after close().
The feat/web-auth branch adds GET /api/v1/sessions/{session_id}/warnings
(packages/server/src/routes/sessions.ts), so the API surface guardrail
snapshot needs to record the new documented v1 route.
Merged
5 tasks
7723qqq
pushed a commit
to 7723qqq/kimi-code
that referenced
this pull request
Jul 11, 2026
…I#1006) * test(server): add API surface snapshot guardrail Boot startServer on port 0 and snapshot the documented v1 route table derived from /openapi.json paths, plus the reachability of doc/meta endpoints (/healthz, /openapi.json, /asyncapi.json, /). Gives later auth/--host phases an intentional diff when routes change. M0 makes no production behavior change. * test(server): add e2e server harness with token support Add test/helpers/serverHarness.ts: boot() wraps startServer with an isolated lock + home dir and returns a handle (server, address, baseUrl, wsUrl, token, close) plus authedFetch/authedWs that carry Authorization: Bearer <token> (and the kimi-code.bearer.<token> WS subprotocol). serviceOverrides is the generic DI seam later phases use to inject a fixed-token auth service; IAuthTokenService is not referenced yet. closeAll() tears down every booted server and socket. M0 makes no production behavior change; typecheck-only gate. * feat(server): add privateFiles 0600 atomic write/read utility * feat(server): add per-start tokenStore * feat(server): add env-based bcrypt password hash utility * feat(server): add IAuthTokenService DI seam * feat(server): add global onRequest auth hook with bypass + redaction * fix(server): stop reflecting Host header in /asyncapi.json * feat(server): add WS bearer subprotocol constant and parser * feat(server): enforce bearer token auth on WS upgrade * feat(server): add Host header allowlist middleware * feat(server): add Origin/CORS middleware * feat(server): wire Host/Origin checks into HTTP and WS * feat(server): wire token auth, Host/Origin, and WS auth into start.ts * fix(server): create lock file with 0600 permissions * fix(server): suppress debug routes on non-loopback binds * feat(kimi-code): read server token and send Authorization on CLI calls * feat(kimi-code): inject server token into /web URL fragment * feat(server): add bindClassify for loopback/lan/public classification * feat(kimi-code): register --host flag and pass it through the daemon * feat(server): require password and TLS opt-out on non-loopback binds * feat(server): rate-limit repeated auth failures on non-loopback binds * feat(server): disable shutdown and terminals on public binds by default * feat(server): add security response headers on non-loopback binds * test(server): cover LAN/public host-exposure hardening end to end * docs(server): add deployment security and threat-model guide * changeset: minor kimi-code for server auth and host exposure * feat(kimi-web): add server bearer-token auth support * fix: repair CI for server auth and host exposure - Replace native @node-rs/bcrypt with pure-JS bcryptjs so the ESM CLI bundle and the SEA native bundle both build without native-addon require issues (node-rs/bcrypt broke the ESM smoke and the SEA check-bundle allowlist). - Remove dead cleanup references (stopSpinner, authLogoBlinkTimer) in apps/kimi-web App.vue that failed vue-tsc. - Fix lint: drop empty spread fallbacks in the e2e auth-header merge, void the intentionally-async WS upgrade listener, add missing assertions to satisfy jest/expect-expect, and convert a ternary statement to if/else. - Send the bearer token in the snapshot perf/smoke tests so they pass under the new global auth hook. - Refresh the pnpmDeps hash in flake.nix for the updated lockfile. * feat(server): persist bearer token and add rotate-token command - persist the server bearer token in <home>/server.token (0600) and reuse it across restarts instead of per-start server-<pid>.token - add `kimi server rotate-token` to regenerate the token; the token store reloads on mtime/inode change so rotation applies without restart - print the token and Vite-style Local/Network URLs in the startup banner - allow non-loopback binds with bearer-token-only auth (password now optional) and update SECURITY.md - surface daemon boot failures immediately with the exit reason and log tail instead of waiting for the spawn timeout * feat(server): print full token URLs and re-print links after rotate - Drop the ready-panel border so token URLs print in full for copying; keep the Kimi sprite beside the title. - Re-print Local/Network access links after `server rotate-token` (host/port from the lock). - Extract shared access-URL helpers into access-urls.ts. - Unify link and token colors between the banner and rotate-token. * feat(server): dim URL #token= fragment and de-highlight token - Render the `#token=…` fragment in a dim gray so the host/port stands out in the banner and rotate-token links. - De-highlight the standalone token; set it off with surrounding whitespace instead of color. - Add splitTokenFragment helper. * refactor(cli): polish server ready banner and rotate-token output - move version onto the ready banner title line; drop the separate Ready:/Version: rows and the startup-time metric - reorder rotate-token output so the new token sits between the invalidation note and the access links - update server CLI tests for the new layout * feat(server): warn on reuse and refine ready banner - Warn when `server run` reuses an already-running daemon (its options are not applied) and show the running server's actual URLs. - Show a `Network: off use --host 0.0.0.0 to enable` hint on loopback binds. - Move the version onto the title line and drop the startup-time metric. * fix(web): relabel auth dialog to token and cover full page - Relabel the server auth dialog from "password" to "token"; the server accepts the bearer token, with the password only as a fallback. - Make the auth dialog overlay fully opaque so it covers the whole page instead of revealing the login page underneath. * fix: resolve CI failures on web auth PR - Replace chalk.yellow named color with chalk.hex(darkColors.warning) in the server reuse notice to satisfy the chalk named color guard. - Update pnpmDeps hash in flake.nix to match the regenerated pnpm-lock.yaml so the Nix build succeeds. - Retry rmSync in ws-broadcast e2e teardown to ride out EBUSY / ENOTEMPTY races while the server flushes files after close(). * test(server): update API surface snapshot for warnings route The feat/web-auth branch adds GET /api/v1/sessions/{session_id}/warnings (packages/server/src/routes/sessions.ts), so the API surface guardrail snapshot needs to record the new documented v1 route. (cherry picked from commit 60dfb68)
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.
Summary
This PR makes the local Kimi Code server safe to authenticate and expose. The server now requires a per-start bearer token on every REST and WebSocket call (the CLI and web UI pick it up automatically), validates
Host/Originheaders, and gains a--hostflag with a dedicated hardening tier for non-loopback (LAN / public) binds — mandatory password + TLS, auth-failure rate limiting, disabled remote shutdown/terminals, and security response headers. Seepackages/server/SECURITY.mdfor the threat model.1. Bearer-token authentication for the server
Problem: The local server exposed its full REST + WebSocket API with no authentication — anything that could reach the port could drive the agent, read the filesystem, and run terminals.
What was done:
tokenStore) and a globalonRequestauth hook enforcing it on all API routes, with bypass and redaction support.IAuthTokenServiceDI seam and an env-based bcrypt password-hash utility for password-derived auth.WS bearersubprotocol and parser.Authorizationon its own calls; the/webURL carries the token in its fragment so the browser UI can authenticate.2. kimi-web authentication flow
Problem: The browser UI had no way to present credentials, so it could not talk to an authenticated server.
What was done:
ServerAuthDialogand aserverAuthAPI client module to prompt for and store the token / password.3. Host / Origin request validation
Problem: Without Host/Origin checks, a DNS-rebinding or cross-origin browser request could target the local server even when it is authenticated.
What was done:
Hostheader in/asyncapi.json.4. Safe non-loopback (
--host) exposureProblem: Binding the server beyond loopback dramatically increases the attack surface and was previously unguarded.
What was done:
bindClassifyto classify binds as loopback / LAN / public, and registered a--hostflag passed through the daemon.KIMI_CODE_PASSWORDand TLS (or explicit--insecure-no-tls), rate-limit repeated auth failures, disable remote shutdown and terminals by default, add security response headers, and suppress debug routes.0600permissions.5. Tests, docs, and changeset
What was done:
packages/server/SECURITY.mddocumenting the deployment security model and threat model.minorchangeset for@moonshot-ai/kimi-code.Checklist
gen-changesetsskill, or this PR needs no changeset. (Changeset:.changeset/server-auth-and-host-exposure.md)gen-docsskill, or this PR needs no doc update. (Addspackages/server/SECURITY.md; user-facing CLI docs update to be confirmed.)