| title | Authentication matrix |
|---|---|
| description | Provider-by-provider reference for static keys, OIDC federation, headers, targets, and credential isolation in the AWF API proxy. |
This document describes every authentication combination supported by AWF's api-proxy sidecar, including how each provider's auth works, what configuration is required, and how the proxy transforms credentials before forwarding to upstream APIs.
The Auth Doctor Updater workflow periodically compares this matrix with current default-branch implementation, recent issues and pull requests, and official provider guidance. It opens a deduplicated, file-bounded pull request only when an evidence-backed documentation correction is needed.
- Dimensions Overview
- Provider: OpenAI
- Provider: Anthropic
- Provider: GitHub Copilot
- Provider: Google Gemini
- Provider: Google Vertex AI
- OIDC Providers
- GitHub Instance Types
- Custom Headers & Injection
- Coverage Matrix
Auth evaluation in the api-proxy is determined by the combination of these independent axes:
| # | Dimension | Controlled By | Values |
|---|---|---|---|
| 1 | Engine | Port binding (10000–10004) | openai, anthropic, copilot, gemini, vertex |
| 2 | Auth Type | AWF_AUTH_TYPE |
api-key (default), github-oidc |
| 3 | OIDC Provider | AWF_AUTH_PROVIDER |
azure, aws, gcp, anthropic |
| 4 | Instance Type | GITHUB_SERVER_URL |
github.com, GHEC (*.ghe.com), GHES |
| 5 | BYOK Mode | COPILOT_PROVIDER_TYPE |
unset (standard), azure |
| 6 | Target Override | {PROVIDER}_API_TARGET |
Any hostname |
| 7 | Custom Auth Header | AWF_{PROVIDER}_AUTH_HEADER |
Any valid HTTP header name |
| 8 | Extra Injection | AWF_BYOK_EXTRA_HEADERS, AWF_BYOK_EXTRA_BODY_FIELDS |
JSON objects |
:::note
The OIDC Provider dimension (AWF_AUTH_PROVIDER) applies to OpenAI, Anthropic, Copilot, Gemini, and Vertex AI when the listed provider supports that cloud's token format. The Gemini and Vertex AI adapters support AWF_AUTH_PROVIDER=gcp through Google Workload Identity Federation; other OIDC providers do not apply to those Google adapters.
:::
Port: 10000
Implementation: containers/api-proxy/providers/openai.js
| Setting | Value |
|---|---|
| Env var | OPENAI_API_KEY |
| Header sent upstream | Authorization: Bearer <key> |
| Default target | api.openai.com |
| Default base path | /v1 |
Official docs: https://platform.openai.com/docs/api-reference/authentication
When COPILOT_PROVIDER_TYPE=azure and COPILOT_PROVIDER_BASE_URL is set:
| Setting | Value |
|---|---|
| Env var | COPILOT_PROVIDER_API_KEY |
| Header sent upstream | api-key: <key> (NOT Authorization:) |
| Target | Derived from COPILOT_PROVIDER_BASE_URL |
| Base path | Derived from URL path component |
Official docs: https://learn.microsoft.com/en-us/azure/foundry/openai/reference
When AWF_AUTH_TYPE=github-oidc and AWF_AUTH_PROVIDER=azure:
| Setting | Value |
|---|---|
| Header sent upstream | Authorization: Bearer <token> |
| Token exchange | GitHub JWT → Azure AD token endpoint |
| Scope | https://cognitiveservices.azure.com/.default (default; configurable via AWF_AUTH_AZURE_SCOPE) |
| OIDC audience | api://AzureADTokenExchange (configurable via AWF_AUTH_OIDC_AUDIENCE) |
Note: When Azure OIDC is active, the header switches from api-key: back to Authorization: Bearer.
:::note[Implementation vs. provider documentation]
https://cognitiveservices.azure.com/.default is AWF's hardcoded default scope and remains valid for Azure OpenAI/Foundry resources. Some newer Microsoft Foundry how-to guides show https://ai.azure.com/.default for certain data-plane operations — this is not a universal replacement, and either value may be required depending on your resource and API surface. Set AWF_AUTH_AZURE_SCOPE (or apiProxy.auth.azureScope) explicitly if your deployment needs a different scope; AWF does not infer the correct scope for you.
:::
Official docs: https://learn.microsoft.com/en-us/azure/foundry-classic/openai/how-to/managed-identity
:::note[Implementation vs. provider documentation]
OpenAI's own API now offers native workload identity federation, which exchanges an external OIDC/JWT identity at https://auth.openai.com/oauth/token for a short-lived OpenAI access token. AWF does not implement this. The openai.js adapter's AWF_AUTH_PROVIDER OIDC support (azure, aws, gcp) is for reaching Azure OpenAI/Foundry or GCP-fronted OpenAI-compatible endpoints using that cloud's own identity tokens — it is unrelated to OpenAI's native federation feature.
:::
AWF_OPENAI_AUTH_HEADER overrides the header name used for the API key. The raw key or token value is sent directly as the header value (no Bearer prefix is added), both for static keys and OIDC tokens.
Port: 10001
Implementation: containers/api-proxy/providers/anthropic.js
| Setting | Value |
|---|---|
| Env var | ANTHROPIC_API_KEY |
| Header sent upstream | x-api-key: <key> |
| Default target | api.anthropic.com |
| Default base path | (none) |
Additional required headers: anthropic-version: 2023-06-01
Official docs: https://platform.claude.com/docs/en/api/overview
:::note[Implementation vs. provider documentation]
Anthropic SDKs officially support ANTHROPIC_AUTH_TOKEN for bearer-token authentication. AWF does not currently accept that variable as a host-side source credential: static Anthropic auth is read from ANTHROPIC_API_KEY and sent as x-api-key. In the agent container, AWF reserves ANTHROPIC_AUTH_TOKEN for the non-secret placeholder sk-ant-placeholder-key-for-credential-isolation; the real credential remains in the sidecar.
:::
When AWF_AUTH_TYPE=github-oidc and AWF_AUTH_PROVIDER=anthropic:
| Setting | Value |
|---|---|
| Header sent upstream | Authorization: Bearer <wif_token> (NOT x-api-key) |
| Token exchange endpoint | POST https://api.anthropic.com/v1/oauth/token |
| Grant type | urn:ietf:params:oauth:grant-type:jwt-bearer |
| Required config | AWF_AUTH_ANTHROPIC_FEDERATION_RULE_ID, AWF_AUTH_ANTHROPIC_ORGANIZATION_ID, AWF_AUTH_ANTHROPIC_SERVICE_ACCOUNT_ID |
| Optional config | AWF_AUTH_ANTHROPIC_WORKSPACE_ID, AWF_AUTH_ANTHROPIC_TOKEN_URL |
| OIDC audience | https://api.anthropic.com (default, configurable) |
| Token format | sk-ant-oat01-... (short-lived) |
Key behavior change: When OIDC is active, the auth header switches from x-api-key to Authorization: Bearer.
:::note[Anthropic beta headers]
AWF follows Anthropic's SDK behavior: JWT-bearer POST /v1/oauth/token exchanges send oauth-2025-04-20,oidc-federation-2026-04-01, while API requests authenticated with the resulting bearer token send oauth-2025-04-20. The federation beta is never added to static x-api-key requests or forwarded refresh-token exchanges. Client-supplied anthropic-beta values are preserved and deduplicated with AWF-required values and the optional auto-cache beta.
:::
Official references: Anthropic WIF documentation · Anthropic TypeScript SDK federation exchange
AWF_ANTHROPIC_AUTH_HEADER overrides the header name (default: x-api-key). Useful for enterprise gateways that expect a different header.
Port: 10002
Implementation: containers/api-proxy/providers/copilot.js, containers/api-proxy/providers/copilot-auth.js
| Instance | Env var | Header | Target |
|---|---|---|---|
| github.com | COPILOT_GITHUB_TOKEN |
Authorization: Bearer <token> |
api.githubcopilot.com |
GHEC (*.ghe.com) |
COPILOT_GITHUB_TOKEN |
Authorization: Bearer <value> |
copilot-api.<subdomain>.ghe.com |
| GHES (on-prem) | COPILOT_GITHUB_TOKEN |
Authorization: token <value> |
api.enterprise.githubcopilot.com |
| Business tier | COPILOT_GITHUB_TOKEN |
Authorization: token <value> |
api.business.githubcopilot.com (must be set explicitly via COPILOT_API_TARGET; never auto-derived) |
:::note[Implementation vs. provider documentation]
GitHub's REST API authentication docs state that both Bearer-prefixed and token-prefixed Authorization headers are generally accepted for PATs/OAuth tokens (only JWTs strictly require Bearer). The token prefix requirement documented here is AWF-implementation-specific defensive behavior for Copilot API requests, not a general GitHub REST API rule: the enterprise target (api.enterprise.githubcopilot.com) and the business target (api.business.githubcopilot.com) return 400 Bad Request: Authorization header is badly formatted when sent the wrong prefix. AWF detects this via copilotTargetRequiresGitHubTokenPrefix() in copilot-auth.js, which matches on the specific target hostname or GHES-detection heuristics (AWF_PLATFORM_TYPE=ghes, or a GITHUB_SERVER_URL that isn't github.com/*.ghe.com). GHEC data-residency targets (copilot-api.<subdomain>.ghe.com) use the standard Bearer prefix. BYOK keys always use the Bearer prefix regardless of target.
Fine-grained PAT override: As of PR #8038, COPILOT_GITHUB_TOKEN values that start with github_pat_ (fine-grained PATs) always use the Bearer prefix, on every Copilot target including GHEC/GHES/Business — they never fall back to token <value>, even where classic PATs and OAuth tokens on those targets require it. This override lives in getGitHubTokenAuthPrefix() in copilot-auth.js and is covered by copilot-adapter-enterprise.test.js and copilot-auth.test.js.
:::
Copilot API host requests also include Copilot-Integration-Id. The default is agentic-workflows; set COPILOT_INTEGRATION_ID to override it, or set GITHUB_COPILOT_INTEGRATION_ID as a fallback (checked only when COPILOT_INTEGRATION_ID is unset) — added in PR #8038.
When the resolved upstream host is a Copilot host (githubcopilot.com or a *.githubcopilot.com subdomain), the sidecar guarantees two non-secret headers are present on every forwarded request:
| Header | Value | Purpose |
|---|---|---|
X-Interaction-Id |
${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}, else a UUID minted once per sidecar process |
CAPI prompt-cache key — must be stable for a whole run and differ across runs |
Copilot-Integration-Id |
COPILOT_INTEGRATION_ID, else GITHUB_COPILOT_INTEGRATION_ID, else agentic-workflows |
Attribution, quota bucket, and model allowlist |
Non-empty inbound values are preserved; empty values and case-variant duplicates are replaced so exactly one instance of each header reaches CAPI. Harnesses that do not send a stable X-Interaction-Id themselves (aider, Pi, …) therefore still get prompt-cache hits, and a harness that owns its own session identity can override the value simply by sending the header. There is no AWF-specific override env var for X-Interaction-Id: outside GitHub Actions the sidecar mints one UUID per process. Neither header is injected on non-Copilot hosts, so BYOK targets (Azure OpenAI, OpenRouter, …) and the OpenAI/Anthropic/Gemini providers are unaffected.
For GitHub Copilot catalog targets, the sidecar auto-selects appropriate x-github-api-version headers for certain endpoints to ensure API compatibility:
| Endpoint | HTTP Method | Auto-Selected Version | Notes |
|---|---|---|---|
/auto |
POST | 2026-08-01 |
Copilot Auto inference endpoint |
/models/session |
POST | 2025-07-16 |
Model session initialization |
/models/session/intent |
POST | 2025-07-16 |
Model session intent parsing |
/models |
GET | 2026-07-01 |
Model catalog listing (see below); applied for GitHub Copilot catalog targets when COPILOT_GITHUB_TOKEN is available, via a path separate from the POST method check |
Key behaviors:
- Auto-injection applies only to GitHub Copilot targets (
*.githubcopilot.com); BYOK and non-Copilot targets are unaffected. - If the request already includes an
x-github-api-versionheader (case-insensitive), it is preserved and auto-injection is skipped — this applies to the POST endpoints above (getDefaultAutoApiVersion()incopilot.js). - POST requests to other endpoints do not receive auto-injected versions; callers must specify versions for other endpoints if needed.
/modelsGET is handled by a separate, unconditional code path (buildCopilotModelsRequest()and the request-header hook incopilot.js), not by the POST-gatedgetDefaultAutoApiVersion()logic above — it always attachesX-GitHub-Api-Version: 2026-07-01for GitHub Copilot catalog targets.
The /models endpoint prefers COPILOT_GITHUB_TOKEN (GitHub OAuth) over BYOK keys when both are configured, because model listing is a GitHub platform feature. However, when no GitHub token is available (typical for direct-BYOK/custom targets), /models will use the BYOK credential. For GitHub Copilot catalog targets, /models GET requests always receive X-GitHub-Api-Version: 2026-07-01 (COPILOT_MODELS_API_VERSION in copilot.js), verified by copilot-adapter-enterprise.test.js and copilot-byok.test.js.
| Setting | Value |
|---|---|
| Env var | COPILOT_PROVIDER_API_KEY |
| Header | Authorization: Bearer <key> (always Bearer, even on GHES) |
| Target | From COPILOT_PROVIDER_BASE_URL or COPILOT_API_TARGET |
Select --copilot-token-broker or apiProxy.copilotTokenBroker: true explicitly.
An inherited GH_AW_TOKEN_BROKER_URL alone does not select broker authentication.
This mode routes the agent to the Copilot sidecar on port 10002 with non-secret
placeholder authentication, even without a static Copilot credential. Static-token
and BYOK behavior is unchanged when the mode is not selected.
The runtime host must supply:
| Setting | Meaning |
|---|---|
GH_AW_TOKEN_BROKER_URL |
Complete absolute HTTPS exchange endpoint and exact OIDC audience |
ACTIONS_ID_TOKEN_REQUEST_URL / ACTIONS_ID_TOKEN_REQUEST_TOKEN |
Actions-owned OIDC bootstrap, used only by the trusted sidecar |
GITHUB_REPOSITORY_ID, GITHUB_REPOSITORY_OWNER_ID |
Stable IDs partitioning trusted local credential state |
GITHUB_RUN_ID, GITHUB_RUN_ATTEMPT, GITHUB_JOB |
Consuming execution context; not server authorization evidence |
Keep the broker URL in runtime Actions variables/secrets, not checked-in AWF
configuration or compiled deployment URLs. A selected mode with missing configuration
fails closed. Copilot static credentials, BYOK/cloud authentication, custom
destinations/base paths, cross-provider fallback, and experimental model routing
are conflicting configurations in broker mode, not recovery mechanisms.
Agent enclaves are also rejected: their dedicated proxy deliberately excludes
the Actions OIDC bootstrap required by broker mode. Script-only enclaves remain supported.
Broker responses cannot select an inference destination or tenant.
Broker mode defaults to api.githubcopilot.com; explicit targets are limited to
that host, api.individual.githubcopilot.com, api.business.githubcopilot.com,
and api.enterprise.githubcopilot.com, using HTTPS port 443 without a base path.
Listing an approved destination is not a promise that the preview token works
there. GHEC tenant destinations require separate reviewed broker support;
non-broker enterprise routing is unchanged.
github/gh-aw must map boolean token-broker.copilot-request: true to
--copilot-token-broker (or the equivalent AWF configuration member). It must
resolve the URL at Actions runtime:
env:
GH_AW_TOKEN_BROKER_URL: ${{ vars.GH_AW_TOKEN_BROKER_URL || secrets.GH_AW_TOKEN_BROKER_URL }}Only consuming jobs need id-token: write, within the caller's permission
envelope. Do not combine the inference selection with
permissions.copilot-requests: write. Preserve separate copilot-task handling;
an inference installation token does not establish authorization for coding-agent
task creation or assignment.
This AWF change does not remove the upstream compiler's main-agent inference
guard, including its applicable fallback-model guard. That change belongs in
github/gh-aw after a compatible AWF image/runtime and a real integrated
deployment are verified. Bounded detection/evals exchanges are not evidence of
renewable main-agent inference.
The host selects the mode, validates configuration, and passes the bootstrap, broker endpoint, and execution context only to the isolated API-proxy container. The sidecar owns OIDC acquisition, exchange, in-memory caching, and renewal. The agent receives neither broker configuration nor OIDC bootstrap/JWTs nor real inference credentials, including with environment passthrough enabled.
Broker, OIDC and the selected Copilot target receive source-scoped CONNECT
policy for the trusted sidecar, not agent domain allowlist entries.
Existing blocked-network and unsafe-port guards still apply.
These authentication tunnels are spliced before TLS inspection; their endpoint
hosts are suppressed in Squid access logs and redacted from audit snapshots.
Failure diagnostics omit the encoded Squid configuration as well.
The client uses the sidecar's
HTTPS proxy transport, so it does not depend on runner filesystem paths or a
runner-local socket; the same handoff works with Docker-in-Docker. Deployment
proxies and TLS trust must be configured through existing AWF transport/trust
facilities. Do not disable TLS verification to make a broker reachable.
This runtime handoff accepts GitHub.com Actions OIDC endpoints under
*.actions.githubusercontent.com; other GitHub deployments need separately
reviewed issuer/transport support, not wildcard enrollment.
Endpoint configuration is trusted cooperative deployment policy: HTTPS alone
does not establish that a repository administrator's chosen broker is approved.
The client follows the experimental
gh-aw Token Broker Specification 1.0.0.
The configured URL, including case, path, query and explicit port, is the exact
OIDC audience. No suffix/discovery route is appended; no redirects are followed
on either hop. An exchange is one HTTPS POST with JSON content type, JSON accept
header, and Cache-Control: no-store:
{"token_type":"copilot-request","provider":"github","oidc_token":"<fresh-actions-jwt>"}Success is HTTP 200 with Content-Type: application/json and
Cache-Control: no-store, containing exactly two unique string members:
{"token":"<opaque-installation-token>","expires_at":"2026-10-30T18:00:00Z"}Tokens are nonempty printable ASCII U+0021–U+007E; no prefix or fixed length is
assumed. Responses cannot contain endpoints, scopes or tenant metadata. Duplicate
members, additional/missing members, unsafe tokens, invalid UTF-8/JSON, and invalid
calendar dates are rejected. Expiration uses the specification's restricted RFC
3339 profile: lowercase t/z, fractions, and numeric offsets are accepted;
leap seconds, hour 24, and invalid time/offset components are not.
Both response reads and the request body are bounded to 65,536 bytes. Compressed responses are rejected; configure broker middleware to serve this small JSON response without compression. One 10-second total deadline includes OIDC acquisition, proxy/TLS connection, broker exchange, and both response reads. There is no automatic exchange retry.
Each upstream request, including model discovery, resolves authorization when it is prepared. Tokens with 30 seconds or less remaining are discarded and renewed using a fresh OIDC identity. A newly issued token must exceed that margin. Each provider instance owns one immutable endpoint/provider/capability/ repository/run/attempt/job context; concurrent requests share its in-flight exchange, but separate contexts/processes cannot inherit credentials. Shutdown clears memory and cancels pending exchanges.
Denial, malformed/near-expiry credentials, unsupported capability, redirects, timeout and outage fail closed. No static credential, alternate provider, stale token, or replay of a potentially billable inference request is an authentication workaround. Already-established streams are not stopped solely because their token expires. AWF does not guarantee the upstream will keep such a stream open: operators must measure actual stream behavior, while subsequent requests renew. HTTP streaming is supported; WebSocket upgrades fail closed in broker mode because renewable WebSocket authentication is not implemented.
Organization-installed GitHub Apps using Copilot are in public preview, subject to change. This path is not supported for personal-account or enterprise installations. The organization must approve the App's organization Copilot Requests: Read-only permission and installation. Read-only permits inference spending; it does not mean cost-free access.
The broker, not AWF, holds App signing credentials in protected server-side storage and chooses the organization installation from enrolled policy. After identity and policy verification, use GitHub's supported installation access token issuance with minimum approved permissions. Return the issuer's actual expiration. Installation tokens expire after one hour; there is no assumed caller-selected TTL. Advertising an earlier expiration only changes client refresh timing, not the credential's underlying lifetime. Stronger lifetime promises require supported issuer behavior or revocation.
The following is an illustrative server adapter boundary, not a broker, JWT verifier, or production SDK implementation. The injected functions must be implemented using the deployment's protected GitHub App client and policy:
async function issueCopilotRequest(verifiedContext, capability, services) {
// Each operation below is server-owned; no installation ID comes from the caller.
const policy = await services.authorizeEnrolledContext(verifiedContext);
if (capability !== 'copilot-request' || !policy.githubCopilotEnabled) {
throw services.sanitizedUnsupported();
}
await services.enforceReplayAndRatePolicy(verifiedContext, capability);
const issued = await services.issueOrganizationInstallationToken({
installationId: policy.organizationInstallationId,
permissions: policy.minimumApprovedCopilotPermissions,
});
await services.validateIssuerResult(issued, policy);
// Track this independently revocable token only in job-scoped server memory.
await services.recordJobCredential(verifiedContext, issued);
return { token: issued.token, expires_at: issued.expiresAt };
}Before invoking any issuer adapter, the backend must:
- Verify the trusted GitHub issuer/discovery/JWKS, allowed algorithm, signature,
exact single-string audience, and finite bounded
iat/nbf/expage/lifetime. JWT-supplied key URLs must not select trust roots or destinations. Support bounded trusted-key refresh/rotation; missing trust/time policy denies issuance. - Bind stable owner/repository IDs to an enrolled tenant and approved
workflow/reusable-workflow revision, ref, environment and event/fork policy.
Verify run/attempt and consuming-job evidence through an approved signed claim
profile or correlated trusted GitHub metadata.
GITHUB_JOBand other caller environment variables are only local cache context, never authorization evidence. - Authorize capability, installation, entitlement and approved inference destination from server policy; reject cross-tenant selection and unsupported capabilities without minting. For known types verify identity before disclosing capability support. Apply bounded abuse/replay controls and per-context rate limits without preventing independently authorized cross-capability exchanges.
- Enforce finite upstream timeouts, least privilege, actual expiration, issuer compatibility, and independent revocation. Fail issuance if any required evidence or result cannot be verified; never switch tenant/issuer/capability.
Organization model policies, billing and spending controls apply. There is no per-App or per-installation budget. Enable the organization's stop-spending budget behavior when required; alert-only budgets do not stop usage. The CLI organization-billing policy is a different authentication path. Suspending or uninstalling an installation blocks App access, including other organization resources, and is not a job-scoped cleanup mechanism.
Lifecycle ownership is split deliberately: AWF cancels local exchanges and
clears its cache; the compiler owns an unconditional, separate always()
post-job step, with a fresh OIDC identity and the same endpoint/audience:
{"operation":"invalidate","oidc_token":"<fresh-actions-jwt>"}Only HTTP 204 with empty body and Cache-Control: no-store is successful.
Verify identity and exact execution context again; revoke all credentials issued
to that authorized job, make repeated cleanup idempotent, and deny cross-job,
run, attempt, repository and tenant invalidation without revealing existence.
Do not share one cached installation token across jobs and claim it can be
revoked independently. The broker needs a working per-token revocation strategy
or must reject deployment conformance. Cleanup cannot replace finite credential
lifetimes when a job is force-terminated.
GitHub documents
DELETE /installation/token:
authenticate with the particular installation token to revoke it; success is
HTTP 204. A backend can retain each job's independently issued tokens in protected
memory and revoke each through this endpoint, then discard them. Never accept a
caller-supplied token to choose what to revoke. Handle already-revoked/expired
tokens under the server's idempotent cleanup policy without hiding genuine
revocation outages. Demonstrate that revoking one job's tokens leaves another
job's separately issued tokens usable; SDK installation-token caches must not
collapse these into a shared credential.
Backend authors should use the upstream T-TBR-002–009 and T-TBR-013–016 fixtures with a controlled issuer/JWKS, deterministic clock, isolated tenants and an instrumented issuer. Assert zero mint calls on pre-issuance rejection and no returned credential on rejected issuer results. Exercise key rotation, unknown keys, invalid audience/signature/time, unsupported capability, fork and job policy denial, replay/rate boundaries, duplicate/oversize bodies, actual expiration, repeated invalidation and denied cross-context revocation.
AWF's client fixtures are containers/api-proxy/providers/token-broker.test.js;
proxy fixtures cover request-time inference/model discovery and streaming.
These are client/runtime tests, not server or integrated-deployment certification.
Live acceptance remains deployment-owned and unverified here: demonstrate
actual model listing, Copilot inference, renewal with real Actions OIDC,
long-lived stream behavior, and job-scoped revocation while another job continues
using its own credential. Successful minting/exchange alone does not count.
Client classifications are ERR_TOKEN_BROKER_CONFIGURATION, OIDC,
UNAUTHORIZED (401/403), UNSUPPORTED (400/404/422/501), UNAVAILABLE
(transport/deadline/other status), and MALFORMED_RESPONSE, each prefixed
ERR_TOKEN_BROKER_. Inspect only sanitized lifecycle/policy diagnostics.
Never print JWTs, bootstrap tokens, issued tokens, full broker URLs/bodies,
claim dumps or underlying exception details. Disable credential-bearing HTTP
capture, proxy/APM traces and durable credential storage on the backend.
Use access-controlled, retention-bounded policy diagnostics and non-sensitive
test evidence; do not put credentials in workflow outputs or artifacts.
When COPILOT_PROVIDER_TYPE=azure, the OpenAI adapter on port 10000:
- Header switches to
api-key: <value>(Azure convention) - Unless OIDC is active, in which case it's
Authorization: Bearer
The Copilot adapter on port 10002 does not emit api-key; its BYOK requests use Authorization: Bearer.
When AWF_AUTH_TYPE=github-oidc with Copilot:
| Provider | Header | Notes |
|---|---|---|
| Azure | Authorization: Bearer <entra_token> |
Via oidc-token-provider.js |
| GCP | Authorization: Bearer <gcp_token> |
Via gcp-oidc-token-provider.js |
| AWS | SigV4 Authorization plus x-amz-* signing headers |
Via aws-oidc-token-provider.js and aws-sigv4.js |
:::caution[Agent routing is credential-triggered] Cloud OIDC configuration can initialize credentials in the sidecar, but the agent's OpenAI and Copilot base URLs/placeholders are currently configured only when the corresponding static OpenAI/Copilot credential is present. Treat Azure, GCP, and AWS OIDC support for OpenAI/Copilot as sidecar authentication capability rather than a complete keyless agent-routing path until OIDC-aware agent routing is implemented for those adapters.
Three providers are exceptions with explicit OIDC-aware agent routing: Anthropic WIF (shouldProxyAnthropic() recognizes AWF_AUTH_PROVIDER=anthropic), and GCP WIF for the Gemini and Vertex AI adapters (isGcpOidcConfigured() in gemini-credential-env.ts/vertex-credential-env.ts, added in PR #8888) — all three set agent base-URL/placeholder env vars from OIDC config alone, without a static key. GCP OIDC routing for Copilot specifically remains credential-triggered only; buildCopilotCredentialEnv() does not check isGcpOidcConfigured().
:::
:::note[AWS OIDC + Copilot]
Selecting AWF_AUTH_PROVIDER=aws signs Copilot-adapter HTTP requests at final dispatch with the cached temporary STS credentials. The target must be the exact regional Bedrock Runtime hostname; credentials are never returned by getAuthHeaders() or exposed to the agent.
:::
Official docs:
- Copilot API: https://docs.github.com/en/rest/copilot (documents management endpoints; the inference/chat-completions endpoint AWF proxies to is not covered by public GitHub REST docs)
- REST API authentication: https://docs.github.com/en/rest/authentication/authenticating-to-the-rest-api
Port: 10003
Implementation: containers/api-proxy/providers/google-adapter.js (Gemini factory from the declarative google-provider-specs.js registry)
| Setting | Value |
|---|---|
| Env var | GEMINI_API_KEY |
| Header sent upstream | x-goog-api-key: <key> |
| Default target | generativelanguage.googleapis.com |
| Default base path | (none) |
The proxy also strips ?key=, ?apiKey=, and ?api_key= query parameters from requests to prevent duplicate-key errors.
| Setting | Value |
|---|---|
| Env vars | AWF_AUTH_TYPE=github-oidc, AWF_AUTH_PROVIDER=gcp, AWF_AUTH_GCP_WORKLOAD_IDENTITY_PROVIDER |
| Header sent upstream | Bearer auth header |
| Optional service account | AWF_AUTH_GCP_SERVICE_ACCOUNT |
| Optional scope | AWF_AUTH_GCP_SCOPE (default: https://www.googleapis.com/auth/cloud-platform) — applies only to service-account impersonation; the STS exchange always requests cloud-platform |
This path keeps the GitHub OIDC exchange and short-lived GCP access token inside the api-proxy sidecar. It is intended for Gemini API endpoints that accept Google OAuth bearer tokens; billing follows the Google Cloud project/service account authorized by the workload identity pool.
:::caution[Gemini API key migration]
Google says the Gemini API will reject standard API keys beginning in September 2026. Migrate GEMINI_API_KEY to Google's service-account-bound authorization key format before that deadline; AWF forwards either key type through the same x-goog-api-key header.
:::
Official docs: https://ai.google.dev/gemini-api/docs/api-key
Port: 10004
Implementation: containers/api-proxy/providers/google-adapter.js (Vertex factory from the declarative google-provider-specs.js registry)
| Setting | Value |
|---|---|
| Env var | GOOGLE_API_KEY |
| Header sent upstream | x-goog-api-key: <key> |
| Default target | aiplatform.googleapis.com |
| Default base path | (none) |
| Setting | Value |
|---|---|
| Env vars | AWF_AUTH_TYPE=github-oidc, AWF_AUTH_PROVIDER=gcp, AWF_AUTH_GCP_WORKLOAD_IDENTITY_PROVIDER |
| Header sent upstream | Bearer auth header |
| Optional service account | AWF_AUTH_GCP_SERVICE_ACCOUNT |
| Optional scope | AWF_AUTH_GCP_SCOPE (default: https://www.googleapis.com/auth/cloud-platform) — applies only to service-account impersonation; the STS exchange always requests cloud-platform |
This is the native Vertex/Gemini CLI path for GOOGLE_GENAI_USE_VERTEXAI=true: AWF sets GOOGLE_VERTEX_BASE_URL to port 10004, exchanges the GitHub Actions OIDC token for a short-lived GCP token in the sidecar, and forwards Vertex requests to aiplatform.googleapis.com (or VERTEX_API_TARGET). Billing and quota are charged to the Google Cloud project associated with the impersonated service account, or to the directly granted workload identity principal when no service account is configured.
:::note[Implementation vs. provider documentation]
This adapter exists to support the Gemini CLI's GOOGLE_GENAI_USE_VERTEXAI=true mode: setting GOOGLE_VERTEX_BASE_URL to point at this sidecar routes Vertex AI traffic through AWF. Google's general Vertex AI guidance recommends Application Default Credentials, a service account key, or workload identity federation for Vertex AI endpoints, and treats a bare API key as unsupported for most Vertex AI surfaces (API keys are the norm for the separate Gemini Developer API at generativelanguage.googleapis.com). Prefer GCP WIF for Vertex AI. AWF still forwards x-goog-api-key when GOOGLE_API_KEY is configured for deployments that explicitly rely on API-key-compatible Vertex surfaces such as Vertex AI Express Mode.
:::
Official docs: https://geminicli.com/docs/get-started/authentication/
All OIDC flows require GitHub Actions runtime tokens:
ACTIONS_ID_TOKEN_REQUEST_URL— endpoint to mint OIDC JWTsACTIONS_ID_TOKEN_REQUEST_TOKEN— auth token for the OIDC endpoint
Documentation audits treat both as non-observable credentials: they verify id-token: write and sidecar configuration but never request or print either value, a minted JWT, or exchanged cloud credentials.
AWF forwards these variables to the api-proxy sidecar in github-oidc mode, and also when GH_AW_OTLP_WORKLOAD_IDENTITY is configured to enable OIDC workload identity for the OTLP trace exporter; both cases exclude the variables from the agent container. GitHub Agentic Workflows independently passes them from its runner-owned Start MCP Gateway step directly to the MCP gateway when a remote HTTP MCP server uses auth.type: github-oidc; AWF does not launch or configure that gateway. github/gh-aw#50053 is resolved by github/gh-aw#50054, which enforces this boundary.
| Config | Env Var | Required |
|---|---|---|
| Tenant ID | AWF_AUTH_AZURE_TENANT_ID |
✅ |
| Client ID | AWF_AUTH_AZURE_CLIENT_ID |
✅ |
| Scope | AWF_AUTH_AZURE_SCOPE |
❌ (default: https://cognitiveservices.azure.com/.default) |
| Cloud | AWF_AUTH_AZURE_CLOUD |
❌ (default: public; options: public, usgovernment, china) |
| Audience | AWF_AUTH_OIDC_AUDIENCE |
❌ (default: api://AzureADTokenExchange) |
Token exchange endpoint: https://login.microsoftonline.com/<tenant>/oauth2/v2.0/token
Implementation: containers/api-proxy/oidc-token-provider.js
Official docs: https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation-create-trust
| Config | Env Var | Required |
|---|---|---|
| Role ARN | AWF_AUTH_AWS_ROLE_ARN |
✅ |
| Region | AWF_AUTH_AWS_REGION |
✅ |
| Session Name | AWF_AUTH_AWS_ROLE_SESSION_NAME |
❌ (default: awf-oidc-session) |
| Audience | AWF_AUTH_OIDC_AUDIENCE |
❌ (default: sts.amazonaws.com) |
Token exchange: GET https://sts.<region>.amazonaws.com/?Action=AssumeRoleWithWebIdentity
Result: Temporary credentials (AccessKeyId, SecretAccessKey, SessionToken), cached and refreshed by AwsOidcTokenProvider
Request signing: SigV4 with service bedrock-runtime, applied after body transforms and repeated for retries
Implementation: containers/api-proxy/aws-oidc-token-provider.js, containers/api-proxy/aws-sigv4.js
Official docs: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_oidc.html
:::note[Signing boundary and fail-closed behavior]
The request layer signs the HTTP method, canonical path and sorted query, final body SHA-256, exact regional Bedrock Runtime host, region, and bedrock-runtime service. It includes the STS session token and re-signs retries. Missing/expired credentials return 503 without opening an upstream connection. To prevent credential disclosure, other target hosts and WebSocket upgrades are rejected.
:::
| Config | Env Var | Required |
|---|---|---|
| WIF Provider | AWF_AUTH_GCP_WORKLOAD_IDENTITY_PROVIDER |
✅ |
| Service Account | AWF_AUTH_GCP_SERVICE_ACCOUNT |
❌ (direct federation if omitted) |
| Scope | AWF_AUTH_GCP_SCOPE |
❌ (default: https://www.googleapis.com/auth/cloud-platform; used in step 2 only) |
| Audience | AWF_AUTH_OIDC_AUDIENCE |
❌ (default: https://iam.googleapis.com/ + WIF provider) |
| Project | AWF_AUTH_GCP_PROJECT |
❌ (needed with location for native Vertex path scoping) |
| Location | AWF_AUTH_GCP_LOCATION |
❌ (needed with project for native Vertex path scoping) |
When both project and location are set, native v1/v1beta1 Vertex model paths are rewritten to project-scoped paths; if either is absent, paths are left unchanged. See api-proxy-sidecar.md.
Token exchange (2-step):
POST https://sts.googleapis.com/v1/token— exchange GitHub JWT for federated tokenPOST https://iamcredentials.googleapis.com/.../:generateAccessToken— (optional) exchange for SA-scoped token
Implementation: containers/api-proxy/gcp-oidc-token-provider.js
Official docs: https://cloud.google.com/iam/docs/workload-identity-federation-with-deployment-pipelines
| Config | Env Var | Required |
|---|---|---|
| Federation Rule ID | AWF_AUTH_ANTHROPIC_FEDERATION_RULE_ID |
✅ |
| Organization ID | AWF_AUTH_ANTHROPIC_ORGANIZATION_ID |
✅ |
| Service Account ID | AWF_AUTH_ANTHROPIC_SERVICE_ACCOUNT_ID |
✅ |
| Workspace ID | AWF_AUTH_ANTHROPIC_WORKSPACE_ID |
❌ |
| Token URL | AWF_AUTH_ANTHROPIC_TOKEN_URL |
❌ (default: https://api.anthropic.com/v1/oauth/token) |
| Audience | AWF_AUTH_OIDC_AUDIENCE |
❌ (default: https://api.anthropic.com) |
Token exchange: POST https://api.anthropic.com/v1/oauth/token (RFC 7523 jwt-bearer), with anthropic-beta: oauth-2025-04-20,oidc-federation-2026-04-01
Bearer API requests: anthropic-beta: oauth-2025-04-20 (merged with client and auto-cache beta values)
Implementation: containers/api-proxy/anthropic-oidc-token-provider.js
Official references: Anthropic WIF documentation · Anthropic TypeScript SDK credential constants
Detection logic in containers/api-proxy/providers/copilot-auth.js:
GITHUB_SERVER_URL → deriveCopilotApiTarget():
*.ghe.com → copilot-api.<subdomain>.ghe.com
github.com → api.githubcopilot.com
(other) → api.enterprise.githubcopilot.com
api.business.githubcopilot.com (Business tier) is never auto-derived from GITHUB_SERVER_URL — it must be set explicitly via COPILOT_API_TARGET.
| Target | Credential Type | Auth Header Format |
|---|---|---|
api.githubcopilot.com |
GitHub token | Bearer <token> |
copilot-api.*.ghe.com |
GitHub token (classic PAT/OAuth) | Bearer <value> |
api.enterprise.githubcopilot.com |
GitHub token (classic PAT/OAuth) | token <value> |
api.business.githubcopilot.com |
GitHub token (classic PAT/OAuth) | token <value> |
| Any target | GitHub token (fine-grained PAT, github_pat_*) |
Bearer <value> (always, since PR #8038) |
| Any target | BYOK key | Bearer <key> (always) |
| Any target | OIDC token | Bearer <token> (always) |
The token prefix is used for classic PAT/OAuth GitHub tokens on the enterprise and business Copilot targets, or when GHES is otherwise detected (see copilotTargetRequiresGitHubTokenPrefix() in copilot-auth.js). GHEC data-residency targets use the standard Bearer prefix. BYOK and OIDC always use the standard prefix. As noted above, this is AWF-implementation-specific behavior driven by observed 400 errors from those targets — not a general GitHub REST API requirement. Fine-grained PATs (github_pat_*) are detected via getGitHubTokenAuthPrefix() and always use Bearer, overriding the target-based rule above.
authorizationx-api-keyx-goog-api-keyproxy-authorization
JSON object of headers injected on BYOK inference requests:
- Only active when
COPILOT_PROVIDER_API_KEYis set - NOT injected on
/modelsGET when GitHub OAuth token is available - Protected headers are skipped with a
console.warnmessage
JSON object of string fields merged into request body:
- Only active in BYOK mode
- Existing body fields are NOT overridden
Adds x-session-id header automatically in BYOK mode unless already present.
| Engine | Auth Mode | Instance | Tested | Implementation |
|---|---|---|---|---|
| OpenAI | Static key | — | ✅ | openai.js |
| OpenAI | Azure BYOK | — | ✅ | openai.js |
| OpenAI | Azure OIDC | — | ✅ | openai.js, oidc-token-provider.js |
| OpenAI | AWS Bedrock OIDC + SigV4 | — | ✅ | openai.js, aws-oidc-token-provider.js, aws-sigv4.test.js |
| OpenAI | GCP OIDC | — | ✅ | openai.js, gcp-oidc-token-provider.js |
| Anthropic | Static key | — | ✅ | anthropic.js |
| Anthropic | WIF | — | ✅ | anthropic.js, anthropic-oidc-token-provider.js |
| Anthropic | Custom header | — | ✅ | anthropic.js |
| Copilot | GitHub token | github.com | ✅ | copilot.js, copilot-auth.js |
| Copilot | GitHub token | GHEC | ✅ | copilot.js, copilot-auth.js |
| Copilot | GitHub token | GHES | ✅ | copilot.js, copilot-auth.js |
| Copilot | GitHub token | Business tier | ✅ | copilot-adapter-enterprise.test.js |
| Copilot | BYOK key | — | ✅ | copilot.js, copilot-byok.js |
| Copilot | Azure BYOK | — | ✅ | via OpenAI adapter |
| Copilot | Azure OIDC | — | ✅ | copilot-adapter-enterprise.test.js |
| Copilot | AWS Bedrock OIDC + SigV4 | — | ✅ | aws-oidc-token-provider.js, server.auth-matrix.test.js |
| Copilot | GCP OIDC | — | ✅ | gcp-oidc-token-provider.js, server.auth-matrix.test.js |
| Copilot | GHES + BYOK | GHES | ✅ | server.auth-matrix.test.js |
| Gemini | Static key | — | ✅ | google-adapter.js, google-provider-specs.js |
| Gemini | GCP WIF | — | ✅ | google-adapter.js, gcp-oidc-token-provider.js, server.auth-matrix.test.js |
| Vertex AI | Static key | — | ✅ | google-adapter.js, google-provider-specs.js |
| Vertex AI | GCP WIF | — | ✅ | google-adapter.js, gcp-oidc-token-provider.js, server.auth-matrix.test.js |
:::note "Implementation" column lists source files, not line numbers — line references go stale quickly as the code evolves. Use your editor's search to locate the relevant logic within each file. :::