-
Notifications
You must be signed in to change notification settings - Fork 340
AWF API proxy doubles https:// scheme in --anthropic-api-target URL #25137
Description
Description
When using engine.env.ANTHROPIC_BASE_URL with a GitHub Actions expression (e.g., ${{ vars.ANTHROPIC_BASE_URL }}), the AWF API proxy constructs a malformed URL by prepending https:// to a target that already includes the scheme. This only affects expression-based values — hardcoded URLs are stripped correctly at compile time.
Reproduction
Workflow .md configuration:
engine:
id: claude
version: "2.1.92"
model: claude-sonnet-4-6
env:
ANTHROPIC_BASE_URL: ${{ vars.ANTHROPIC_BASE_URL }}
network:
allowed:
- defaults
- my-custom-gateway.example.comWhere vars.ANTHROPIC_BASE_URL = https://my-custom-gateway.example.com/some-path
Compiled output
The compiler generates:
--enable-api-proxy --anthropic-api-target '${{ vars.ANTHROPIC_BASE_URL }}'
At runtime, GitHub resolves this to --anthropic-api-target 'https://my-custom-gateway.example.com/some-path'.
Expected behavior
The API proxy forwards requests to https://my-custom-gateway.example.com/some-path/v1/messages correctly.
Actual behavior
The Squid proxy logs show a request to https://https/* and returns ERR_ACCESS_DENIED (403).
Root cause
Traced through the source — this is a compile-time/runtime mismatch across three layers:
1. Compiler: pkg/workflow/awf_helpers.go — extractAPITargetHost()
host := baseURL
if idx := strings.Index(host, "://"); idx != -1 {
host = host[idx+3:]
}At compile time, the value is the literal string ${{ vars.ANTHROPIC_BASE_URL }} — which does not contain ://. The function returns it unchanged, and the lock file emits:
--anthropic-api-target '${{ vars.ANTHROPIC_BASE_URL }}'
If the value were hardcoded (e.g., https://my-gateway.example.com), the scheme would be stripped correctly. The bug only manifests with ${{ ... }} expressions.
2. AWF CLI: src/cli.ts
At runtime, GitHub resolves the expression to the actual URL (e.g., https://my-gateway.example.com). The CLI does no scheme stripping — it passes the full URL into the container as ANTHROPIC_API_TARGET.
3. API Proxy: containers/api-proxy/server.js
The proxy reads the env var directly:
const ANTHROPIC_API_TARGET = process.env.ANTHROPIC_API_TARGET || 'api.anthropic.com';Then uses it in two places that assume a bare hostname:
buildUpstreamPath():new URL(reqUrl, "https://${targetHost}")— constructshttps://https://my-gateway.example.com/...(double scheme)proxyRequest():hostname: targetHostpassed tohttps.request()— Node treatshttps://my-gateway.example.comas a literal hostname, andHttpsProxyAgentsendsCONNECT https://my-gateway.example.com:443through Squid, which parses the host ashttps
Proposed fix
The simplest defensive fix is a one-liner in containers/api-proxy/server.js — normalize ANTHROPIC_API_TARGET to strip any scheme on startup:
- const ANTHROPIC_API_TARGET = process.env.ANTHROPIC_API_TARGET || 'api.anthropic.com';
+ const ANTHROPIC_API_TARGET = (process.env.ANTHROPIC_API_TARGET || 'api.anthropic.com').replace(/^https?:\/\//, '');This works regardless of whether the value arrives with or without a scheme, and covers both the expression-based path and any future callers. The compiler's extractAPITargetHost() already handles hardcoded URLs correctly, so no change is needed there. An optional belt-and-suspenders strip in src/cli.ts before passing to the container would also be reasonable.
Environment
- gh-aw v0.67.1
- AWF image tag: 0.25.13
- Runner: ubuntu-latest