From d6af9e6a0b364a35326d1ea6558ea8c49f3e433f Mon Sep 17 00:00:00 2001 From: sec-check Date: Wed, 2 Sep 2026 01:51:12 -0400 Subject: [PATCH] [guide] docs: document the Claude Remote Control pin-off (#5607) in CHANGELOG and security model PR #5619 (e26a861, shipped in v4.1.0) pins the Claude Code Remote Control bridge off at every claude-CLI launch, closing a fleet-wide session-exposure vector introduced by a server-side rollout flag in Claude Code ~2.1.226. The change was user-visible and security-relevant but appeared nowhere in the docs: - CHANGELOG.md: add a Security entry to the 2026-09-02 (v4.1.0) section describing the exposure, the add-if-missing pin, the operator opt-out (set remoteControlAtStartup: true), and the hasUsedRemoteControl launch-time warning. - src/docs/security-model.md: add a Layer 4 (agent sandboxing) bullet documenting the pin alongside the other per-agent launch controls. Fixes #5627 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: sec-check --- CHANGELOG.md | 1 + src/docs/security-model.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d5debcac..3a1708a55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ Hive did not historically maintain a complete changelog. This file starts a prag ### Security +- The Claude Code **Remote Control** bridge (session publishing to claude.ai/code) is now pinned OFF at every claude-CLI launch ([#5607](https://github.com/kubestellar/hive/issues/5607)). Since Claude Code ~2.1.226 the bridge auto-starts whenever no explicit `remoteControlAtStartup` value exists — the CLI falls through to a server-side rollout flag — so an image upgrade flipped it on fleet-wide with no local change, and the in-pane toggle is session-scoped so every relaunch brought it back. On a hive this exposed every agent (launched with `--dangerously-skip-permissions`) as a remote-controllable session under the one shared claude.ai account. Hive now merges `"remoteControlAtStartup": false` into the shared `/data/home/.claude/settings.json` for the plain claude backend, and into the inference settings seed for gateway agents, re-asserted on every launch. The merge is **add-if-missing only**: an operator who wants Remote Control sets the key to `true` in the same file and hive never clobbers it, with an Info receipt logged so an enabled bridge is always explainable. A launch-time check also warns when an agent's session file records `hasUsedRemoteControl` despite the pin. - `hive-snapshot.service` no longer executes code out of a path an unprivileged local user could have planted ([#5483](https://github.com/kubestellar/hive/issues/5483)). This is the same class as [#5435](https://github.com/kubestellar/hive/issues/5435) and the last remaining unit in `systemd/` that ran an `ExecStart` out of the checkout: `ExecStart=/tmp/hive/dashboard/publish-snapshot.sh` resolved the executed script out of a world-writable parent that is cleared on every reboot, so a local user who created `/tmp/hive/dashboard/publish-snapshot.sh` before `hive-deploy` repopulated the checkout got code execution as the service user. Severity is lower than #5435 — the unit is `Type=oneshot` driven by `hive-snapshot.timer` rather than `Restart=always`, so the planted code runs on the timer's 15-minute schedule rather than immediately at boot, and the unit carries no `EnvironmentFile`, so systemd hands it no credential — but it is not zero: the script itself reads a GitHub App token from `/var/run/hive-metrics/gh-app-token.cache` and pushes to `kubestellar/docs`. The unit now runs `bin/hive-checkout-guard.sh` as an `ExecStartPre`, unchanged from #5481, and refuses to start unless every directory from `/` down to the checkout is owned by the service user or root and not writable by anyone else (a sticky ancestor such as `/tmp` is accepted, the leaf is not), and unless `publish-snapshot.sh` exists, is a regular file rather than a symlink, and is not group- or world-writable — the file ships mode 755, so it is the write bits rather than the exec bit that matter here. `NoNewPrivileges`, `ProtectSystem=full`, `PrivateDevices` and `RestrictSUIDSGID` are set alongside it. **The checkout stays at `/tmp/hive` and no path changes**, so existing hosts keep working. `PrivateTmp` is deliberately *not* set and `ProtectSystem` is deliberately not `strict` — either would hide or freeze both the checkout and the `/tmp/kubestellar-docs-snapshot` clone the script commits from; the contract test pins both as absent. `ProtectHome` is deliberately *not* `read-only` here, unlike `hive-discord.service`, because this script drives `git` and `gh`, which write under `$HOME`; that difference is pinned by the test as well so a later consistency pass cannot silently break the snapshot push. ## 2026-09-01 (v4.0.2) diff --git a/src/docs/security-model.md b/src/docs/security-model.md index 5b66f536f..ccc4e932c 100644 --- a/src/docs/security-model.md +++ b/src/docs/security-model.md @@ -59,6 +59,7 @@ Agents are unprivileged, separated, and policed at the network layer: - **Per-agent Unix UIDs.** Each agent runs as its own user (UIDs allocated from base 2001) via `su-exec`, with its own tmux server on a per-agent socket. One agent cannot attach to another's session or signal its processes. `su-exec` is mode `4750 root:hive-launch`, so no agent UID can re-exec it to become root. - **Per-agent GitHub tokens, not shared.** Each agent's scoped token is delivered to a per-agent cache file that is not world-readable: the entrypoint pre-creates it owned `dev:hive-` with mode `0640`, so only that agent's group can read it, and the locally-minted token path additionally writes `0600` and `chown`s to the agent's UID (deleting the file rather than leaving it shared if the chown fails). Agents cannot read each other's GitHub tokens. - **Per-agent tool denylists.** Declarative per-agent tool rules in `hive.yaml` become hard CLI flags at launch (`--disallowed-tools` for Claude, `--deny-tool` for Copilot). Separately, GitHub MCP *write* tools are denied for **every** agent in **every** mode — the MCP layer never grants agents a GitHub write path regardless of autonomy level; GitHub writes are instead governed by the token tier and the policy proxy below. +- **Claude Remote Control is pinned off.** Since Claude Code ~2.1.226 the Remote Control bridge (session publishing to claude.ai/code) auto-starts whenever no explicit `remoteControlAtStartup` value exists — a server-side rollout flag decides, so a routine image upgrade could expose every agent as a remote-controllable session under the shared claude.ai account. Hive writes a durable `"remoteControlAtStartup": false`, re-asserted at every claude-CLI launch: merged into the shared `/data/home/.claude/settings.json` for the plain claude backend, and into the per-agent settings seed for inference-gateway agents. The merge is add-if-missing only — an operator who wants the bridge sets the key to `true` in the same file and hive never clobbers it, logging an Info receipt so an enabled bridge is always explainable — and a launch-time check warns if an agent's session file records `hasUsedRemoteControl` despite the pin. - **A default-on GitHub policy proxy.** Every agent's `HTTPS_PROXY` points at an in-pod proxy that intercepts **GitHub API traffic** and enforces, deterministically, what the agent's current ACMM mode allows: REST method/path rules, GraphQL query-vs-mutation gating, and a **repository allowlist** — writes to any repo outside the hive's configured list are blocked with `403` (`X-Hive-Proxy-Blocked`) at the network layer, regardless of what the agent was prompted (or prompt-injected) to do. The proxy MITM-inspects only `api.github.com`; other destinations are tunneled without inspection. The same proxy attributes traffic to agents by UID (read from `/proc/net/tcp`, unforgeable) for token accounting. ## Layer 5 — GitHub blast-radius controls