Loopback MITM for Claude Code on Amazon Bedrock: sniff invokes, hold/edit the JSON, Forward (SigV4 re-sign) or Drop. One listener, one UI, no run wrapper.
This is not ANTHROPIC_BASE_URL=localhost. Claude Code on Bedrock does not speak that.
Proxy and Intercept start off and are not persisted.


Edited hold, then Forward or Drop:

CLAUDE_CODE_USE_BEDROCK=1, same region/profile as a working claude session)main.py (AWS_PROFILE, SSO, or the default chain). Forwarding an edited body also needs InvokeModel on those same models (--aws-profile / AWS_PROFILE / SSO).uv sync
uv run python main.py --aws-profile your-profile # optional; same chain as AWS_PROFILE
Listener: http://127.0.0.1:9090. UI: http://127.0.0.1:8765. First run writes the mitmproxy CA to ~/.mitmproxy/mitmproxy-ca-cert.pem.
UI tabs: Sessions, Usage, Proxy, Info. Proxy/Intercept are in the header (Intercept stays off until Proxy is on). Default view is Sessions.
1. Pin every new Claude Code process (it does not inherit exports from another shell):
export HTTP_PROXY=http://127.0.0.1:9090
export HTTPS_PROXY=http://127.0.0.1:9090
claude
With Proxy off, HTTPS is tunneled (Bedrock’s real cert, no CA). Usage still reads ~/.claude/projects.
2. Sniff: in the UI, turn Proxy on. Then, in the same shell as claude (or copy from the Info page):
export NODE_EXTRA_CA_CERTS="$HOME/.mitmproxy/mitmproxy-ca-cert.pem"
export SSL_CERT_FILE="$HOME/.mitmproxy/mitmproxy-ca-cert.pem"
export REQUESTS_CA_BUNDLE="$HOME/.mitmproxy/mitmproxy-ca-cert.pem"
export AWS_CA_BUNDLE="$HOME/.mitmproxy/mitmproxy-ca-cert.pem"
Restart claude after the CA exports. Send a message. A session with x-claude-code-session-id appears under Sessions (and on the Proxy page while live). Rows land in logs/<client>/<session-id>.jsonl. Empty Sessions with Proxy off is expected; costs still come from ~/.claude/projects.
3. Intercept: turn Intercept on in the header (disabled until Proxy is on). Each Bedrock Runtime invoke / invoke-with-response-stream with x-claude-code-session-id is held. Edit the head JSON, then Forward or Drop.
--aws-profile / AWS_PROFILE / SSO).Trust the CA only while Proxy is on.
EU regional on-demand (eu-central-1) in the sidebar and per request. Family from model-name keywords, or from account-scoped application-inference-profile ids (this repo ships none):
export CLAUDE_PROXY_INFERENCE_PROFILES='{"yourId":"sonnet-4.6"}'
or a gitignored inference-profiles.json. Journals: ~/.claude/projects (--claude-projects-dir). Same UUID in logs/ and the journal is shown once (MITM dollars win). Direct rows are cost-only, no chat. Unknown model/profile → $0. Pricing is read-time; logs are not rewritten with costUSD.
| Flag | Default |
|---|---|
--port | 9090 |
--ui-port | 8765 |
--no-ui | UI on |
--logs-dir | logs/ |
--claude-projects-dir | ~/.claude/projects |
--aws-profile | process default chain |
uv run python main.py ui # UI only, no MITM
logs/ is gitignored. JSONL strips Authorization and x-amz-security-token; bodies (prompts, system) are stored as-is.
UI is served from ui/dist. Hot reload: keep main.py running, cd ui && bun run dev. Rebuild with cd ui && bun install && bun run build when shipping UI changes.
claude after HTTP(S)_PROXY (and CA, if Proxy is on).$0 → unknown model/profile; map inference-profile ids.invoke, or no x-claude-code-session-id.ANTHROPIC_BASE_URL1 commits
Python
60.8%
TypeScript
30.7%
CSS
8.4%
Loopback MITM for Claude Code on Amazon Bedrock: sniff invokes, hold/edit the JSON, Forward (SigV4 re-sign) or Drop. One listener, one UI, no run wrapper.
This is not ANTHROPIC_BASE_URL=localhost. Claude Code on Bedrock does not speak that.
Proxy and Intercept start off and are not persisted.


Edited hold, then Forward or Drop:

CLAUDE_CODE_USE_BEDROCK=1, same region/profile as a working claude session)main.py (AWS_PROFILE, SSO, or the default chain). Forwarding an edited body also needs InvokeModel on those same models (--aws-profile / AWS_PROFILE / SSO).uv sync
uv run python main.py --aws-profile your-profile # optional; same chain as AWS_PROFILE
Listener: http://127.0.0.1:9090. UI: http://127.0.0.1:8765. First run writes the mitmproxy CA to ~/.mitmproxy/mitmproxy-ca-cert.pem.
UI tabs: Sessions, Usage, Proxy, Info. Proxy/Intercept are in the header (Intercept stays off until Proxy is on). Default view is Sessions.
1. Pin every new Claude Code process (it does not inherit exports from another shell):
export HTTP_PROXY=http://127.0.0.1:9090
export HTTPS_PROXY=http://127.0.0.1:9090
claude
With Proxy off, HTTPS is tunneled (Bedrock’s real cert, no CA). Usage still reads ~/.claude/projects.
2. Sniff: in the UI, turn Proxy on. Then, in the same shell as claude (or copy from the Info page):
export NODE_EXTRA_CA_CERTS="$HOME/.mitmproxy/mitmproxy-ca-cert.pem"
export SSL_CERT_FILE="$HOME/.mitmproxy/mitmproxy-ca-cert.pem"
export REQUESTS_CA_BUNDLE="$HOME/.mitmproxy/mitmproxy-ca-cert.pem"
export AWS_CA_BUNDLE="$HOME/.mitmproxy/mitmproxy-ca-cert.pem"
Restart claude after the CA exports. Send a message. A session with x-claude-code-session-id appears under Sessions (and on the Proxy page while live). Rows land in logs/<client>/<session-id>.jsonl. Empty Sessions with Proxy off is expected; costs still come from ~/.claude/projects.
3. Intercept: turn Intercept on in the header (disabled until Proxy is on). Each Bedrock Runtime invoke / invoke-with-response-stream with x-claude-code-session-id is held. Edit the head JSON, then Forward or Drop.
--aws-profile / AWS_PROFILE / SSO).Trust the CA only while Proxy is on.
EU regional on-demand (eu-central-1) in the sidebar and per request. Family from model-name keywords, or from account-scoped application-inference-profile ids (this repo ships none):
export CLAUDE_PROXY_INFERENCE_PROFILES='{"yourId":"sonnet-4.6"}'
or a gitignored inference-profiles.json. Journals: ~/.claude/projects (--claude-projects-dir). Same UUID in logs/ and the journal is shown once (MITM dollars win). Direct rows are cost-only, no chat. Unknown model/profile → $0. Pricing is read-time; logs are not rewritten with costUSD.
| Flag | Default |
|---|---|
--port | 9090 |
--ui-port | 8765 |
--no-ui | UI on |
--logs-dir | logs/ |
--claude-projects-dir | ~/.claude/projects |
--aws-profile | process default chain |
uv run python main.py ui # UI only, no MITM
logs/ is gitignored. JSONL strips Authorization and x-amz-security-token; bodies (prompts, system) are stored as-is.
UI is served from ui/dist. Hot reload: keep main.py running, cd ui && bun run dev. Rebuild with cd ui && bun install && bun run build when shipping UI changes.
claude after HTTP(S)_PROXY (and CA, if Proxy is on).$0 → unknown model/profile; map inference-profile ids.invoke, or no x-claude-code-session-id.ANTHROPIC_BASE_URL1 commits
Python
60.8%
TypeScript
30.7%
CSS
8.4%