From 4d51b1aa0238d157781c2df56af586f3256bbac4 Mon Sep 17 00:00:00 2001 From: claude Date: Sat, 25 Jul 2026 22:27:18 +0000 Subject: [PATCH] fix(claude): don't log the raw identity token in supervise MCP recovery hint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous fix interpolated the real per-bottle identity token into the `warn()` message shown when `claude mcp add supervise` fails, so a registration failure would leak the credential into host terminal output and any collected launch logs (#476 review, PR #471 comment). Render a `` placeholder instead. The recovery hint still shows the required `--header x-bot-bottle-identity: …` shape, and the operator substitutes the value from inside the bottle (it rides in the agent's HTTPS_PROXY credentials) — so the token never reaches the host log. The token truthiness still gates whether the header is shown at all. Test updated to assert the header name and placeholder are present and the raw token is absent. Co-Authored-By: Claude Opus 4.8 --- bot_bottle/contrib/claude/agent_provider.py | 14 ++++++++++++-- tests/unit/test_contrib_claude_provider.py | 7 +++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/bot_bottle/contrib/claude/agent_provider.py b/bot_bottle/contrib/claude/agent_provider.py index fd91ffbd..3d83a56f 100644 --- a/bot_bottle/contrib/claude/agent_provider.py +++ b/bot_bottle/contrib/claude/agent_provider.py @@ -35,6 +35,11 @@ if TYPE_CHECKING: _SUPERVISE_MCP_NAME = "supervise" # App-layer identity token header (mirrors egress_addon / git_http_backend). _IDENTITY_HEADER = "x-bot-bottle-identity" +# Placeholder stood in for the real identity token in the manual-recovery hint. +# The token is a per-bottle credential, so it must never be rendered into the +# host-side launch log (#476 review); the operator substitutes the value from +# inside the bottle (it rides in the agent's HTTPS_PROXY credentials). +_IDENTITY_TOKEN_PLACEHOLDER = "" def _skills_dir(guest_home: str) -> str: @@ -327,13 +332,18 @@ class ClaudeAgentProvider(AgentProvider): user="node", ) if r.returncode != 0: + # A placeholder — never the real token — keeps this per-bottle + # credential out of the host launch log (#476 review). The operator + # substitutes it from inside the bottle (it rides in the agent's + # HTTPS_PROXY credentials). manual_header = ( - f" --header {shlex.quote(f'{_IDENTITY_HEADER}: {token}')}" if token else "" + f" --header {shlex.quote(f'{_IDENTITY_HEADER}: {_IDENTITY_TOKEN_PLACEHOLDER}')}" + if token else "" ) warn( f"`claude mcp add supervise` failed (exit {r.returncode}): " f"{(r.stderr or r.stdout or '').strip()}. Inside the bottle, " - f"register manually with: " + f"register manually (substitute the bottle's identity token) with: " f"claude mcp add --scope user --transport http " f"supervise {supervise_url}{manual_header}" ) diff --git a/tests/unit/test_contrib_claude_provider.py b/tests/unit/test_contrib_claude_provider.py index 59cc8646..dbd617ed 100644 --- a/tests/unit/test_contrib_claude_provider.py +++ b/tests/unit/test_contrib_claude_provider.py @@ -353,7 +353,9 @@ class TestClaudeSuperviseMcp(unittest.TestCase): _plan(supervise=True), bottle, _URL, ) - def test_fallback_warning_includes_identity_header(self): + def test_fallback_warning_includes_header_but_not_raw_token(self): + # The recovery hint must show the header is required, but must NOT + # render the per-bottle identity token into the host log (#476 review). bottle = _make_bottle( exec_result=ExecResult(returncode=1, stdout="", stderr="boom"), ) @@ -363,7 +365,8 @@ class TestClaudeSuperviseMcp(unittest.TestCase): ) msg = warn_mock.call_args.args[0] self.assertIn("x-bot-bottle-identity", msg) - self.assertIn("tok-xyz", msg) + self.assertNotIn("tok-xyz", msg) + self.assertIn("", msg) def test_fallback_warning_omits_header_when_no_token(self): bottle = _make_bottle(