Skip to content
Draft
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ Key settings:
| `ENABLE_PASSTHROUGH_ROUTES` | `true` | Enable provider-native passthrough routes under `/p/{provider}/...` |
| `ALLOW_PASSTHROUGH_V1_ALIAS` | `true` | Allow `/p/{provider}/v1/...` aliases while keeping `/p/{provider}/...` canonical |
| `ENABLED_PASSTHROUGH_PROVIDERS` | `openai,anthropic` | Comma-separated list of enabled passthrough providers |
| `EXPERIMENTAL_FORWARD_PROXY_ENABLED` | `false` | Enable the experimental HTTP forward proxy wrapper for client traffic inspection |
| `EXPERIMENTAL_FORWARD_PROXY_MITM_HOSTS` | `api.anthropic.com` | Comma-separated HTTPS hosts to inspect; other CONNECT targets are tunneled blindly |
| `CACHE_TYPE` | `local` | Cache backend (`local` or `redis`) |
Comment on lines +175 to 177
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Add CA file env vars to the key-settings table for parity.

The paragraph on Line 182 mentions EXPERIMENTAL_FORWARD_PROXY_CA_CERT_FILE and EXPERIMENTAL_FORWARD_PROXY_CA_KEY_FILE, but they are not listed in the table. Adding them there improves discoverability.

Also applies to: 182-182

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 172 - 174, Add two rows to the environment variables
key-settings table to list EXPERIMENTAL_FORWARD_PROXY_CA_CERT_FILE and
EXPERIMENTAL_FORWARD_PROXY_CA_KEY_FILE (matching the paragraph that mentions
them); set an appropriate default (e.g., empty) and a short description like
"Path to CA cert file for forward-proxy MITM" and "Path to CA key file for
forward-proxy MITM" so the README's table includes these two env vars alongside
EXPERIMENTAL_FORWARD_PROXY_ENABLED, EXPERIMENTAL_FORWARD_PROXY_MITM_HOSTS, and
CACHE_TYPE.

| `STORAGE_TYPE` | `sqlite` | Storage backend (`sqlite`, `postgresql`, `mongodb`) |
| `METRICS_ENABLED` | `false` | Enable Prometheus metrics |
Expand All @@ -180,6 +182,8 @@ Key settings:

**Quick Start - Authentication:** By default `GOMODEL_MASTER_KEY` is unset. Without this key, API endpoints are unprotected and anyone can call them. This is insecure for production. **Strongly recommend** setting a strong secret before exposing the service. Add `GOMODEL_MASTER_KEY` to your `.env` or environment for production deployments.

**Experimental forward proxy:** When `EXPERIMENTAL_FORWARD_PROXY_ENABLED=true`, GoModel can act as a local HTTP proxy for client traffic. To inspect HTTPS bodies, provide `EXPERIMENTAL_FORWARD_PROXY_CA_CERT_FILE` and `EXPERIMENTAL_FORWARD_PROXY_CA_KEY_FILE`, trust that CA in the client environment, and point the client at GoModel with `HTTP_PROXY` or `HTTPS_PROXY`. This mode is intended for local experiments and is not hardened as a general-purpose enterprise proxy. For Claude Code setup examples, see [`docs/guides/claude-code.mdx`](docs/guides/claude-code.mdx).

---

## Response Caching
Expand Down
4 changes: 4 additions & 0 deletions config/config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ server:
enable_passthrough_routes: true # expose /p/{provider}/{endpoint} passthrough routes
allow_passthrough_v1_alias: true # allow /p/{provider}/v1/... while keeping /p/{provider}/... canonical
enabled_passthrough_providers: ["openai", "anthropic"] # providers enabled on /p/{provider}/...
experimental_forward_proxy_enabled: false # HTTP forward proxy for client traffic inspection (experimental)
experimental_forward_proxy_mitm_hosts: ["api.anthropic.com"] # HTTPS hosts to inspect; all others tunnel blindly
experimental_forward_proxy_ca_cert_file: "" # PEM CA cert used for MITM leaf cert generation
experimental_forward_proxy_ca_key_file: "" # PEM CA private key used for MITM leaf cert generation

cache:
model:
Expand Down
13 changes: 13 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,18 @@ type ServerConfig struct {
// EnabledPassthroughProviders lists the provider types enabled on
// /p/{provider}/... passthrough routes. Default: ["openai", "anthropic"].
EnabledPassthroughProviders []string `yaml:"enabled_passthrough_providers" env:"ENABLED_PASSTHROUGH_PROVIDERS"`
// ExperimentalForwardProxyEnabled enables an HTTP forward proxy entrypoint that can
// optionally MITM selected HTTPS hosts for traffic inspection. Default: false.
ExperimentalForwardProxyEnabled bool `yaml:"experimental_forward_proxy_enabled" env:"EXPERIMENTAL_FORWARD_PROXY_ENABLED"`
// ExperimentalForwardProxyMITMHosts lists the hosts whose HTTPS CONNECT traffic
// should be terminated and inspected. Other hosts are tunneled blindly.
ExperimentalForwardProxyMITMHosts []string `yaml:"experimental_forward_proxy_mitm_hosts" env:"EXPERIMENTAL_FORWARD_PROXY_MITM_HOSTS"`
// ExperimentalForwardProxyCACertFile points at the PEM-encoded CA certificate used
// to mint leaf certificates for inspected HTTPS hosts.
ExperimentalForwardProxyCACertFile string `yaml:"experimental_forward_proxy_ca_cert_file" env:"EXPERIMENTAL_FORWARD_PROXY_CA_CERT_FILE"`
// ExperimentalForwardProxyCAKeyFile points at the PEM-encoded CA private key used
// to mint leaf certificates for inspected HTTPS hosts.
ExperimentalForwardProxyCAKeyFile string `yaml:"experimental_forward_proxy_ca_key_file" env:"EXPERIMENTAL_FORWARD_PROXY_CA_KEY_FILE"`
Comment on lines +437 to +448
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Default MITM host + empty CA paths creates a brittle enabled state.

With Line 431 defaulting ExperimentalForwardProxyMITMHosts and Lines 357-360 defaulting CA paths to empty, turning on only EXPERIMENTAL_FORWARD_PROXY_ENABLED leads to runtime proxy init failure. Add config-time validation (or default MITM hosts to empty) so this fails fast with a clear config error.

🛠️ Proposed fix
 func Load() (*LoadResult, error) {
   ...
+  if cfg.Server.ExperimentalForwardProxyEnabled &&
+    len(cfg.Server.ExperimentalForwardProxyMITMHosts) > 0 &&
+    (strings.TrimSpace(cfg.Server.ExperimentalForwardProxyCACertFile) == "" ||
+      strings.TrimSpace(cfg.Server.ExperimentalForwardProxyCAKeyFile) == "") {
+    return nil, fmt.Errorf("experimental forward proxy MITM requires both EXPERIMENTAL_FORWARD_PROXY_CA_CERT_FILE and EXPERIMENTAL_FORWARD_PROXY_CA_KEY_FILE")
+  }
   ...
 }

Alternative: set default ExperimentalForwardProxyMITMHosts to empty and require explicit opt-in hosts.

Also applies to: 431-431

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@config/config.go` around lines 349 - 360, The config exposes
ExperimentalForwardProxyEnabled, ExperimentalForwardProxyMITMHosts,
ExperimentalForwardProxyCACertFile and ExperimentalForwardProxyCAKeyFile such
that enabling the feature without CA paths or explicit MITM hosts causes runtime
failure; add config-time validation in the Config load/validate path (e.g.,
implement/extend Config.Validate or the function that parses the YAML) to reject
configurations where ExperimentalForwardProxyEnabled is true but either
ExperimentalForwardProxyMITMHosts is empty or the CA files
(ExperimentalForwardProxyCACertFile/ExperimentalForwardProxyCAKeyFile) are
empty/missing, returning a clear error; alternatively, make
ExperimentalForwardProxyMITMHosts default to an empty slice and ensure the
validator requires explicit non-empty hosts when enabled so the app fails fast
with a descriptive message.

}

// MetricsConfig holds observability configuration for Prometheus metrics
Expand Down Expand Up @@ -504,6 +516,7 @@ func buildDefaultConfig() *Config {
"openai",
"anthropic",
},
ExperimentalForwardProxyMITMHosts: []string{"api.anthropic.com"},
},
Cache: CacheConfig{
Model: ModelCacheConfig{
Expand Down
196 changes: 132 additions & 64 deletions docs/guides/claude-code.mdx
Original file line number Diff line number Diff line change
@@ -1,45 +1,34 @@
---
title: "Using GoModel with Claude Code"
description: "Step-by-step guide for routing Claude Code through GoModel with Anthropic passthrough."
description: "Step-by-step guide for using Claude Code with GoModel in gateway mode or subscription proxy mode."
---

GoModel can sit in front of Claude Code so every request goes through your own
gateway first.
GoModel supports two different Claude Code setups:

Flow:

`Claude Code -> GoModel -> Anthropic`
| Goal | Mode | Upstream auth |
| --- | --- | --- |
| Use Claude Code through an Anthropic-compatible gateway | Gateway mode | GoModel's `ANTHROPIC_API_KEY` |
| Keep Claude.ai subscription auth and still capture traffic in GoModel | Subscription proxy mode | Claude Code's own `claude.ai` or enterprise session |

## Before you start
If you want company-wide Claude Code observability without moving users onto API
keys, use **subscription proxy mode**.

- Install Claude Code on your machine.
- Choose a GoModel master key, for example `change-me`.
- Make sure GoModel has an Anthropic upstream credential.
## Mode 1: Gateway mode

<Note>
Claude Code can be routed through GoModel whether or not you personally use a
Claude Code subscription. For gateway mode, Claude Code talks to GoModel with
`ANTHROPIC_BASE_URL` and `ANTHROPIC_AUTH_TOKEN`. GoModel still needs its own
`ANTHROPIC_API_KEY` to reach Anthropic upstream.
</Note>

## How to get `ANTHROPIC_API_KEY`
Flow:

1. Open the Claude Console and sign in to your API account.
2. Go to account settings in Console, then create an API key.
3. Copy the key once and set it for GoModel as `ANTHROPIC_API_KEY`.
`Claude Code -> GoModel /p/anthropic -> Anthropic`

Anthropic's API docs state that API keys are created in Console account
settings.
This is the standard Anthropic-compatible gateway setup. Claude Code talks to
GoModel with `ANTHROPIC_BASE_URL` and `ANTHROPIC_AUTH_TOKEN`, and GoModel calls
Anthropic with its own `ANTHROPIC_API_KEY`.

<Warning>
Claude paid plans (Pro, Max, Team, Enterprise) and Claude API Console billing
are separate. API key usage is billed as API usage.
Gateway mode does not use a user's Claude subscription allowance. It uses
GoModel's upstream Anthropic API credentials.
</Warning>

## 1. Run GoModel

Start GoModel with a master key and an Anthropic provider key:
### 1. Run GoModel

```bash
docker run --rm -p 8080:8080 \
Expand All @@ -48,17 +37,13 @@ docker run --rm -p 8080:8080 \
enterpilot/gomodel
```

## 2. Confirm Anthropic passthrough with curl

Check that the Anthropic passthrough models endpoint responds:
### 2. Confirm Anthropic passthrough

```bash
curl -s http://localhost:8080/p/anthropic/v1/models \
-H "Authorization: Bearer change-me"
```

Then send one small test message:

```bash
curl -s http://localhost:8080/p/anthropic/v1/messages \
-H "Authorization: Bearer change-me" \
Expand All @@ -76,67 +61,150 @@ curl -s http://localhost:8080/p/anthropic/v1/messages \
}'
```

If the gateway is wired correctly, the response will contain `ok`.

## 3. Configure Claude Code to use GoModel

Point Claude Code at GoModel's Anthropic passthrough:
### 3. Point Claude Code at GoModel

```bash
export ANTHROPIC_BASE_URL=http://localhost:8080/p/anthropic
export ANTHROPIC_AUTH_TOKEN=change-me
export CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1
```

Short Claude Code doc summary: for gateway mode, set `ANTHROPIC_BASE_URL` to
your gateway URL and `ANTHROPIC_AUTH_TOKEN` to your gateway token, then run
Claude Code normally. See the official guide:
[Claude Code LLM gateway docs](https://code.claude.com/docs/en/llm-gateway).
```bash
claude -p --output-format text --model claude-3-haiku-20240307 \
'Reply with exactly ok and no punctuation.'
```

## Mode 2: Subscription proxy mode

Flow:

If GoModel is not running on your local machine, replace `localhost:8080` with
the correct host and port.
`Claude Code -> GoModel forward proxy -> Anthropic`

## 4. Run a Claude Code test prompt
This mode keeps Claude Code signed in with `claude.ai` or enterprise auth and
places GoModel in the middle as an HTTP(S) proxy. It is the mode to use when
you want GoModel `audit_logs` and `usage` for real Claude Code subscription
traffic.

<Note>
Validated on March 23, 2026 with Claude Code `2.1.81`, `authMethod:
claude.ai`, and GoModel's experimental forward proxy.
</Note>

<Warning>
The forward proxy is experimental and intended for local or tightly controlled
internal use. Do not expose it directly to the internet in its current form.
</Warning>

### 1. Generate a local MITM CA

```bash
claude -p --output-format text --model claude-3-haiku-20240307 \
'Reply with exactly ok and no punctuation.'
mkdir -p proxy-certs

openssl req -x509 -newkey rsa:2048 -nodes \
-keyout proxy-certs/ca-key.pem \
-out proxy-certs/ca-cert.pem \
-days 30 \
-subj "/CN=GoModel Claude Proxy CA"
```

### 2. Run GoModel with the forward proxy enabled

```bash
docker run --rm -p 8080:8080 \
-v "$PWD/proxy-certs:/certs" \
-e OPENAI_API_KEY="dummy" \
-e LOGGING_ENABLED=true \
-e LOGGING_LOG_BODIES=true \
-e LOGGING_LOG_HEADERS=true \
-e USAGE_ENABLED=true \
-e EXPERIMENTAL_FORWARD_PROXY_ENABLED=true \
-e EXPERIMENTAL_FORWARD_PROXY_MITM_HOSTS="api.anthropic.com" \
-e EXPERIMENTAL_FORWARD_PROXY_CA_CERT_FILE="/certs/ca-cert.pem" \
-e EXPERIMENTAL_FORWARD_PROXY_CA_KEY_FILE="/certs/ca-key.pem" \
enterpilot/gomodel
```

<Note>
The forward proxy path itself does not use `OPENAI_API_KEY`. The example sets
`OPENAI_API_KEY=dummy` only because GoModel currently expects at least one
configured provider at startup. If you already run GoModel with a real
provider credential, keep using that instead.
</Note>

### 3. Point Claude Code at GoModel as an HTTP proxy

Keep Claude Code logged in normally, then export:

```bash
export HTTPS_PROXY=http://localhost:8080
export HTTP_PROXY=http://localhost:8080
export NODE_EXTRA_CA_CERTS="$PWD/proxy-certs/ca-cert.pem"
export NO_PROXY=127.0.0.1,localhost
```

The validated result was:
You can confirm the client is still using subscription auth with:

```text
ok
```bash
claude auth status
```

## 5. Check the traffic in GoModel
### 4. Run a real Claude Code prompt

Open the GoModel dashboard audit logs:
```bash
claude -p --output-format json \
--setting-sources user \
--strict-mcp-config --mcp-config '{"mcpServers":{}}' \
--disable-slash-commands \
--no-chrome \
--no-session-persistence \
'Respond with exactly the word THROUGHPROXY.'
```

A successful run returns JSON with:

```json
{
"result": "THROUGHPROXY"
}
```

### 5. Verify audit and usage in GoModel

Open the dashboard audit view:

[http://localhost:8080/admin/dashboard/audit](http://localhost:8080/admin/dashboard/audit)

This is the easiest place to confirm that Claude Code is reaching GoModel and
to inspect the full request and response trail. From the same dashboard, you
can keep following your GoModel traffic and usage.
For a successful proxied subscription request, you should see:

- startup requests such as `/api/oauth/claude_cli/client_data`
- a model request row for `/v1/messages`
- `usage` data for that `/v1/messages` call

The admin APIs are also available directly:

- `GET /admin/api/v1/audit/log`
- `GET /admin/api/v1/usage/log`

## References

- Anthropic Claude Code gateway docs: [LLM gateway](https://code.claude.com/docs/en/llm-gateway)
- Anthropic Claude Code settings: [Settings](https://code.claude.com/docs/en/settings)
- Anthropic Claude Code enterprise network config: [Network configuration](https://code.claude.com/docs/en/network-config)
- Claude Help: [Managing API key environment variables in Claude Code](https://support.claude.com/en/articles/12304248-managing-api-key-environment-variables-in-claude-code)
- Claude Help: [Paid Claude plans vs API Console billing](https://support.claude.com/en/articles/9876003-i-have-a-paid-claude-subscription-pro-max-team-or-enterprise-plans-why-do-i-have-to-pay-separately-to-use-the-claude-api-and-console)
- Claude API docs: [API overview (keys from Console account settings)](https://docs.anthropic.com/en/api/getting-started)

## Validated on March 10, 2026
## Validation summary

This guide was validated against:
Validated on March 23, 2026 against:

- a local GoModel instance on `http://localhost:8080`
- a GoModel branch exposing `/p/{provider}/v1/...`
- Claude Code `2.1.72`
- GoModel running locally on `http://localhost:8080`
- Claude Code `2.1.81`
- Anthropic gateway mode through `/p/anthropic`
- subscription proxy mode through `HTTP_PROXY` / `HTTPS_PROXY`

Local validation confirmed:

- `GET /p/anthropic/v1/models` returned `200 OK`
- `POST /p/anthropic/v1/messages` returned `200 OK`
- `claude` returned `ok` when pointed at GoModel
- gateway mode returned successful Anthropic passthrough responses
- subscription proxy mode returned `THROUGHPROXY`
- GoModel stored `/v1/messages` in `audit_logs`
- GoModel stored token and cost data for `/v1/messages` in `usage`
9 changes: 9 additions & 0 deletions internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,12 @@ func New(ctx context.Context, cfg Config) (*App, error) {
EnabledPassthroughProviders: appCfg.Server.EnabledPassthroughProviders,
AllowPassthroughV1Alias: &allowPassthroughV1Alias,
SwaggerEnabled: appCfg.Server.SwaggerEnabled,
ExperimentalForwardProxy: &server.ForwardProxyConfig{
Enabled: appCfg.Server.ExperimentalForwardProxyEnabled,
MITMHosts: appCfg.Server.ExperimentalForwardProxyMITMHosts,
CACertFile: appCfg.Server.ExperimentalForwardProxyCACertFile,
CAKeyFile: appCfg.Server.ExperimentalForwardProxyCAKeyFile,
},
}

// Initialize admin API and dashboard (behind separate feature flags)
Expand Down Expand Up @@ -256,6 +262,9 @@ func New(ctx context.Context, cfg Config) (*App, error) {
} else {
slog.Info("provider passthrough disabled")
}
if appCfg.Server.ExperimentalForwardProxyEnabled {
slog.Info("experimental forward proxy enabled", "mitm_hosts", appCfg.Server.ExperimentalForwardProxyMITMHosts)
}

rcm, err := responsecache.NewResponseCacheMiddleware(appCfg.Cache.Response, cfg.AppConfig.RawProviders)
if err != nil {
Expand Down
Loading
Loading