Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion PLAYBOOK.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down
21 changes: 21 additions & 0 deletions bank/anthropic/allowlist
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,34 @@
# `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
# to POST alone is safe for Claude Code itself and is the tighter choice if you
# 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.
Expand Down