Skip to content

feat: disable Remote Control - #12

Merged
devGregA merged 2 commits into
mainfrom
harden/disable-remote-control
Sep 17, 2026
Merged

devGregA merged 2 commits into
mainfrom
harden/disable-remote-control

Conversation

@devGregA

Copy link
Copy Markdown
Contributor

Problem

Upstream 2.0.0 added Remote Control: a reverse tunnel that registers the machine with a relay operated by a third party and forwards public-internet HTTP and WebSocket traffic into the local server. That server has the terminal, file and approval APIs enabled, because it is only reachable on a loopback bind and the tunnel requires one, so a caller the relay lets through can run shell commands, read and write files, and answer the agent's own approval prompts.

Three properties made it worth removing rather than documenting:

  • The tunnel client does no local authorization. It strips the caller's Authorization, Cookie, Host and Origin, then injects the local server token and Host, so the server's bearer, Host and Origin checks pass by construction rather than by decision. The only real check is the relay operator's.
  • It authenticates with the long-lived Kimi refresh token, not an access token, carried as a WebSocket subprotocol value where proxies and CDNs log it far more readily than an Authorization header, and re-sent on every reconnect.
  • POST /api/v1/remote-control started it with no terminal interaction and no second confirmation, so anything holding the local server token could put the machine on the public internet.

What changed

The refusal sits in startRemoteControl, which is the single point every caller reaches: the CLI, the TUI and the server route all start a tunnel through it. A caller added by a later upstream merge is therefore covered too.

Upstream's implementation is kept as startRemoteControlTunnel, unreachable. That keeps upstream's own tunnel tests (header blocklist, absolute-URI rejection, reconnect behaviour) running, and lets upstream changes to the tunnel merge cleanly rather than conflicting against a deleted function.

Removed so the feature is absent rather than merely unreachable:

  • the kimi rc / kimi remote subcommand
  • the --rc / --remote-control flag on kimi web
  • the /rc and /remote-control slash commands
  • staticEnableError is now unconditional, so the server route refuses instead of starting a tunnel

kimi web is unaffected.

No environment escape hatch, deliberately. An env variable would re-enable a public tunnel from exactly the contexts where the environment is least trustworthy, which is the same weakness this fork's audit flagged in upstream's KIMI_CODE_DANGEROUS_COMMAND_GUARD. Re-enabling is a source change, reviewed as one.

Verification

Typecheck, lint and build clean. Test suite is at the same 70 pre-existing kap-server search failures as main, plus one known parallelism flake (reconcile.test.ts, passes 3/3 in isolation and has no remote-control references).

Against the built binary:

$ kimi rc                    → unknown command 'rc'
$ kimi remote                → unknown command 'remote'
$ kimi web --rc              → error: unknown option '--rc'
$ kimi web --remote-control  → error: unknown option '--remote-control'

Against a live kimi web, the route that needed no terminal interaction:

POST /api/v1/remote-control {"enabled":true}
→ 40001 "Remote Control is disabled in this build…"
GET  /api/v1/remote-control → state "off"

The new unit tests were negative-controlled by temporarily removing the guard: all three fail without it, and the kap-server test asserts the relay saw no WebSocket connection and no registration at all.

🤖 Generated with Claude Code

Greg Anderson and others added 2 commits September 17, 2026 13:15
Remote Control opened a reverse tunnel to a relay operated by a third party
and forwarded public-internet traffic into the local server. That server has
the terminal, file and approval APIs enabled, because it is only reachable on
a loopback bind and the tunnel requires one, so a caller the relay let through
could run shell commands, read and write files, and answer the agent's own
approval prompts.

Three properties made it worth removing rather than documenting:

- The tunnel client does no local authorization. It strips the caller's
  Authorization, Cookie, Host and Origin, then injects the local server token
  and Host, so the server's bearer, Host and Origin checks pass by
  construction. The only real check is the relay operator's.
- It authenticates with the long-lived Kimi refresh token, not an access
  token, carried as a WebSocket subprotocol value where proxies and CDNs log
  it far more readily than an Authorization header, and re-sent on every
  reconnect.
- POST /api/v1/remote-control started it with no terminal interaction and no
  second confirmation, so anything holding the local server token could put
  the machine on the public internet.

The refusal sits in startRemoteControl, which is the single point every
caller reaches: the CLI, the TUI and the server route all start a tunnel
through it, so a caller added by a later upstream merge is covered too.
Upstream's implementation is kept as startRemoteControlTunnel, unreachable,
so upstream's own tunnel tests keep running against it and upstream changes
still merge cleanly instead of conflicting with a deleted function.

Also removed so the feature is not merely unreachable but absent: the
`kimi rc` / `kimi remote` subcommand, the `--rc` / `--remote-control` flag,
and the `/rc` slash command. The server route now returns a refusal rather
than starting a tunnel. `kimi web` is unaffected.

There is deliberately no environment escape hatch. An env variable would
re-enable a public tunnel from exactly the contexts where the environment is
least trustworthy, which is the same weakness noted for upstream's
KIMI_CODE_DANGEROUS_COMMAND_GUARD. Re-enabling is a source change.

Verified: every CLI path refuses in the built binary (`rc` and `remote` are
unknown commands, `--rc` and `--remote-control` are unknown options), and a
live `kimi web` returns the refusal for POST /api/v1/remote-control with the
tunnel state still off. The new unit tests were negative-controlled by
removing the guard: all three fail without it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
createRemoteControlHome takes the refresh token to plant; calling it with no
argument typechecked locally only because vitest does not typecheck, so CI
caught it and the suite did not.

Planting a valid token also strengthens the tests: the credential the tunnel
would use is present on disk and it still refuses.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@devGregA
devGregA merged commit 71f5c61 into main Sep 17, 2026
14 checks passed
@devGregA
devGregA deleted the harden/disable-remote-control branch September 17, 2026 19:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant