Skip to content

AWF API proxy doubles https:// scheme in --anthropic-api-target URL #25137

Description

@salekseev

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.com

Where 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.goextractAPITargetHost()

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}") — constructs https://https://my-gateway.example.com/... (double scheme)
  • proxyRequest(): hostname: targetHost passed to https.request() — Node treats https://my-gateway.example.com as a literal hostname, and HttpsProxyAgent sends CONNECT https://my-gateway.example.com:443 through Squid, which parses the host as https

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions