From e71495d52f66cb74b24e0775021c3c247e5c8247 Mon Sep 17 00:00:00 2001 From: LP Date: Mon, 17 Aug 2026 21:56:18 +0000 Subject: [PATCH] Ship platform.claude.com in bank/anthropic's allowlist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude Code 2.x makes a startup connectivity check against platform.claude.com and treats failure as fatal, so an entry listing only api.anthropic.com installs cleanly, injects its credential correctly, and still leaves an agent that refuses to start. Allowlist and not `hosts`: the endpoint answers 200 unauthenticated, so it is a destination the agent must reach rather than one the credential should be attached to. The addon compares `flow.request.host` to "api.anthropic.com" exactly, so this cannot widen where a token is sent. PLAYBOOK's "things that belong in the allowlist and not in `hosts`" gains this as a fourth category — the client's own startup checks, which are rarely on the API host and are the one category that does not look like an egress failure when missed. What the user sees names the proxy, so it reads as a credential or TLS problem; the `blocked` line in the trail is what says otherwise. raw.githubusercontent.com, the issue's other candidate, needs nothing: it is already in bank/github/allowlist under OPTIONAL, which is its correct home. Closes #112 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01K7ZLAqx3456HP7BAY9gmqA --- PLAYBOOK.md | 25 ++++++++++++++++++++++++- bank/anthropic/allowlist | 21 +++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/PLAYBOOK.md b/PLAYBOOK.md index 3674e1c..ebe3878 100644 --- a/PLAYBOOK.md +++ b/PLAYBOOK.md @@ -686,7 +686,7 @@ Claude Code makes, all of which are POSTs. The symptom — every call failing on freshly installed provider — reads as "the credential is wrong", and the credential is fine. -**Three things belong in the allowlist that are not in `hosts`:** +**Four things belong in the allowlist that are not in `hosts`:** - Hosts the agent must reach but must never be credentialed — `github.com` for git push/pull, which `010_github.py` deliberately does not match. @@ -696,6 +696,29 @@ credential is fine. - Multi-tenant suffixes that are fine to reach and unsafe to inject for — `*.workers.dev`. Reachable and credentialed are different lists, and this is the direction where conflating them is dangerous. +- **The client's own startup checks, which are rarely on the API host.** Claude + Code calls `GET platform.claude.com/v1/oauth/hello` before it will run and + treats failure as fatal, so an `anthropic` entry listing only + `api.anthropic.com` installs cleanly, injects correctly, and produces an + agent that refuses to start. The endpoint needs no credential — it answers + 200 unauthenticated — so it is allowlist-only by the first bullet's rule. + Find these the same way you find the methods: run the client once against a + real allowlist and read the `blocked` lines out of the trail. Do not reason + about them from the vendor's API documentation, which describes the API and + not the client. + + This is the category most likely to be missed, because the failure does not + look like an egress failure. The message the user sees names the *proxy*, and + reads as a credential or TLS problem: + + ``` + Unable to connect to Anthropic services + Failed to connect to platform.claude.com: Status 403 + ``` + + That 403 is `001_allowlist.py` refusing, and the matching + `{"event":"blocked","reason":"allowlist",...}` line in the trail is what says + so. Check there first when a freshly installed provider will not start. **Ship optional hosts commented out**, with the reason on the line. Telemetry, error reporting, CDN mirrors, anything the provider works without. The default diff --git a/bank/anthropic/allowlist b/bank/anthropic/allowlist index 6852c0a..401f3a7 100644 --- a/bank/anthropic/allowlist +++ b/bank/anthropic/allowlist @@ -9,6 +9,7 @@ # `api.anthropic.com` line blocks the provider completely while looking right. api.anthropic.com GET,POST +platform.claude.com GET # GET is here for model listing (GET /v1/models), which some clients call at # startup; POST carries /v1/messages and /v1/messages/count_tokens. Narrowing @@ -16,6 +17,26 @@ api.anthropic.com GET,POST # know your client never lists models. Widening past these two is not needed by # anything: the Admin API under /v1/organizations is refused by the addon # regardless of what this file says. +# +# platform.claude.com is the client's startup connectivity check (Claude Code +# 2.x calls GET /v1/oauth/hello) and failing it is FATAL — the agent installs +# cleanly, gets its credential injected correctly, and still refuses to start. +# It is the second line rather than an optional one for that reason. Leaving it +# out is easy to misdiagnose: what the user sees names a proxy, not an +# allowlist, so it reads as a credential or TLS problem rather than an egress +# one. +# +# Unable to connect to Anthropic services +# Failed to connect to platform.claude.com: Status 403 +# +# That 403 is the proxy's own refusal. The endpoint needs no credential — it +# answers 200 unauthenticated — which is why it belongs here and NOT in the +# entry's `hosts`, on the same reasoning as the OPTIONAL block below. +# +# Editing this file is not enough on its own: the proxy reads the allowlist +# once, at startup, and the file is a bind mount, so `docker compose up -d` +# sees no config change and the container keeps the ruleset it already has. +# Restart the proxy. # --------------------------------------------------------------------------- # OPTIONAL — nothing below is needed for the provider to work.