Summary
When an engine env base URL uses an explicit http:// scheme, gh-aw strips the scheme when generating the AWF apiProxy.targets.<provider>.host value in the compiled lock. The emitted target is a bare hostname, so AWF's api-proxy (per github/gh-aw-firewall#7868 / github/gh-aw-firewall#7862, shipped in AWF v0.28.13) treats it as HTTPS and dials :443. This reintroduces the exact 502 / TCP_DENIED failure that the firewall fix was meant to resolve.
Reproduction
Compile with gh aw v0.88.2 (also reproduces on current main):
network:
allowed: [defaults]
engine:
id: claude
env:
ANTHROPIC_BASE_URL: "http://gateway.example.com"
ANTHROPIC_API_KEY: ${{ secrets.MY_ANTHROPIC_KEY }}
Generated lock contains:
"targets":{"anthropic":{"host":"gateway.example.com"}}
Expected (to honor the firewall fix):
"targets":{"anthropic":{"host":"http://gateway.example.com"}}
The agent-container env ANTHROPIC_BASE_URL keeps http:// correctly; only the proxy target loses it. The same applies to OPENAI_BASE_URL (codex) and GEMINI_API_BASE_URL (gemini).
Root cause
extractAPITargetHost() in pkg/workflow/engine_api_targets.go unconditionally strips ://…. Its result is written into AWFAPITargetConfig{Host} in pkg/workflow/awf_config_build.go for openai/anthropic (and via GetGeminiAPITarget() for gemini). AWF v0.28.13 expects the scheme embedded in the target value (--anthropic-api-target http://host); its normalizeApiTargetScheme() keeps http://<hostname> only for explicit-HTTP targets and a bare host still defaults to HTTPS/443.
Fix considerations
- The
host written to apiProxy.targets.* should preserve an explicit http:// scheme (an https:// or bare value can stay bare, mirroring AWF's own normalization, so existing lock files are unchanged).
extractAPITargetHost is also consumed by allowlist/audit logic (mergeAPITargetDomains, getEngineAPIHosts, GetCopilotAllowlistTargets) which needs the bare host — so the scheme should be preserved only on the proxy target host, not the allowlist entries (AWF strips it internally via stripTargetScheme()).
- Gate on AWF ≥ v0.28.13 (already the default) so workflows pinning an older AWF keep the bare host.
- Note for the
copilot target: at AWF v0.28.13, containers/api-proxy/providers/copilot.js buildCopilotModelsRequest() still hardcodes https://${rawTarget}/models while deriveCopilotApiTarget() now returns http://<host>, so an http:// copilot target would yield a malformed https://http://host/models. The copilot target should stay bare until that is fixed on the firewall side.
A PR implementing the above is attached.
Summary
When an engine
envbase URL uses an explicithttp://scheme, gh-aw strips the scheme when generating the AWFapiProxy.targets.<provider>.hostvalue in the compiled lock. The emitted target is a bare hostname, so AWF's api-proxy (per github/gh-aw-firewall#7868 / github/gh-aw-firewall#7862, shipped in AWF v0.28.13) treats it as HTTPS and dials:443. This reintroduces the exact 502 /TCP_DENIEDfailure that the firewall fix was meant to resolve.Reproduction
Compile with
gh aw v0.88.2(also reproduces on currentmain):Generated lock contains:
Expected (to honor the firewall fix):
The agent-container env
ANTHROPIC_BASE_URLkeepshttp://correctly; only the proxy target loses it. The same applies toOPENAI_BASE_URL(codex) andGEMINI_API_BASE_URL(gemini).Root cause
extractAPITargetHost()inpkg/workflow/engine_api_targets.gounconditionally strips://…. Its result is written intoAWFAPITargetConfig{Host}inpkg/workflow/awf_config_build.gofor openai/anthropic (and viaGetGeminiAPITarget()for gemini). AWF v0.28.13 expects the scheme embedded in the target value (--anthropic-api-target http://host); itsnormalizeApiTargetScheme()keepshttp://<hostname>only for explicit-HTTP targets and a bare host still defaults to HTTPS/443.Fix considerations
hostwritten toapiProxy.targets.*should preserve an explicithttp://scheme (anhttps://or bare value can stay bare, mirroring AWF's own normalization, so existing lock files are unchanged).extractAPITargetHostis also consumed by allowlist/audit logic (mergeAPITargetDomains,getEngineAPIHosts,GetCopilotAllowlistTargets) which needs the bare host — so the scheme should be preserved only on the proxy targethost, not the allowlist entries (AWF strips it internally viastripTargetScheme()).copilottarget: at AWF v0.28.13,containers/api-proxy/providers/copilot.jsbuildCopilotModelsRequest()still hardcodeshttps://${rawTarget}/modelswhilederiveCopilotApiTarget()now returnshttp://<host>, so anhttp://copilot target would yield a malformedhttps://http://host/models. The copilot target should stay bare until that is fixed on the firewall side.A PR implementing the above is attached.