Skip to content

fix(api-proxy): add required Anthropic WIF exchange parameters#3979

Merged
lpcox merged 5 commits into
mainfrom
copilot/fix-anthropic-oidc-exchange-params
May 29, 2026
Merged

fix(api-proxy): add required Anthropic WIF exchange parameters#3979
lpcox merged 5 commits into
mainfrom
copilot/fix-anthropic-oidc-exchange-params

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 28, 2026

The Anthropic OIDC provider was sending only grant_type and assertion to /v1/oauth/token, omitting the federation_rule_id, organization_id, and service_account_id fields that Anthropic requires — causing all exchanges to fail with 400 invalid_grant.

Changes

  • anthropic-oidc-token-provider.js: Extends AnthropicOidcTokenProviderConfig with required federationRuleId, organizationId, serviceAccountId and optional workspaceId; includes them in the exchange request body; removes the unused scope field; surfaces the new fields in init success/failure log context. Constructor now validates required fields and throws a clear error if any are missing. Empty/whitespace workspaceId is normalized to undefined so workspace_id: "" is never sent. httpPost is stored as this._httpPost to enable test spying without module-level mocking.

  • providers/anthropic.js: Reads AWF_AUTH_ANTHROPIC_FEDERATION_RULE_ID, AWF_AUTH_ANTHROPIC_ORGANIZATION_ID, AWF_AUTH_ANTHROPIC_SERVICE_ACCOUNT_ID, AWF_AUTH_ANTHROPIC_WORKSPACE_ID from env and passes them to the provider constructor.

  • api-proxy-service.ts: Adds AWF_AUTH_ANTHROPIC_FEDERATION_RULE_ID, AWF_AUTH_ANTHROPIC_ORGANIZATION_ID, AWF_AUTH_ANTHROPIC_SERVICE_ACCOUNT_ID, AWF_AUTH_ANTHROPIC_WORKSPACE_ID to the pickEnvVars OIDC allowlist so they are forwarded into the api-proxy sidecar container.

  • anthropic-oidc-token-provider.test.js: Updates existing tests with required fields; rewrites WIF-fields and workspace_id-omission tests to spy on this._httpPost and assert on the body produced by the real _exchangeForAnthropicToken implementation; adds tests for required-field validation and empty-string workspaceId normalization.

  • api-proxy-service-env-forwarding.test.ts: Adds a test block covering forwarding of all four AWF_AUTH_ANTHROPIC_* WIF env vars.

  • Docs (awf-config-spec.md §9.5.4, api-proxy-sidecar.md): Replaces the incorrect "no provider-specific variables required" with a config table and updated GitHub Actions example; adds AWF_AUTH_ANTHROPIC_WORKSPACE_ID to the sudo --preserve-env=... allowlist.

The exchange body now looks like:

{
  "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
  "assertion": "<github-oidc-jwt>",
  "federation_rule_id": "fdrl_...",
  "organization_id": "<uuid>",
  "service_account_id": "svac_...",
  "workspace_id": "wrkspc_..."
}

workspace_id is included only when AWF_AUTH_ANTHROPIC_WORKSPACE_ID is set and non-empty — required when the federation rule covers multiple workspaces.

Include federation_rule_id, organization_id, and service_account_id
(and optional workspace_id) in the /v1/oauth/token exchange request body.
Read these from AWF_AUTH_ANTHROPIC_FEDERATION_RULE_ID,
AWF_AUTH_ANTHROPIC_ORGANIZATION_ID, AWF_AUTH_ANTHROPIC_SERVICE_ACCOUNT_ID,
and AWF_AUTH_ANTHROPIC_WORKSPACE_ID environment variables.

Remove the unused scope field. Update docs and tests accordingly.
Copilot AI changed the title [WIP] Fix missing exchange parameters in Anthropic OIDC provider fix(api-proxy): add required Anthropic WIF exchange parameters May 28, 2026
Copilot finished work on behalf of lpcox May 28, 2026 22:39
Copilot AI requested a review from lpcox May 28, 2026 22:39
@lpcox lpcox marked this pull request as ready for review May 28, 2026 23:23
Copilot AI review requested due to automatic review settings May 28, 2026 23:23
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 28, 2026

Documentation Preview

Documentation build failed for this PR. View logs.

Built from commit 36d407b

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 28, 2026

✅ Coverage Check Passed

Overall Coverage

Metric Base PR Delta
Lines 96.57% 96.62% 📈 +0.05%
Statements 96.45% 96.49% 📈 +0.04%
Functions 98.24% 98.24% ➡️ +0.00%
Branches 90.74% 90.78% 📈 +0.04%
📁 Per-file Coverage Changes (1 files)
File Lines (Before → After) Statements (Before → After)
src/config-writer.ts 89.3% → 90.9% (+1.65%) 89.3% → 90.9% (+1.65%)

Coverage comparison generated by scripts/ci/compare-coverage.ts

Comment thread containers/api-proxy/anthropic-oidc-token-provider.test.js Fixed
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes Anthropic GitHub OIDC → Anthropic WIF token exchange by sending the required Anthropic parameters (federation_rule_id, organization_id, service_account_id, optional workspace_id) in the /v1/oauth/token request body, and updates documentation/tests accordingly.

Changes:

  • Extend Anthropic OIDC token exchange payload to include required WIF fields (and optionally workspace_id).
  • Wire new Anthropic-specific WIF environment variables into the Anthropic provider adapter.
  • Update docs and tests to reflect the new required configuration.
Show a summary per file
File Description
docs/awf-config-spec.md Documents Anthropic WIF required/conditional configuration variables.
docs/api-proxy-sidecar.md Updates Anthropic WIF env var docs and GitHub Actions example.
containers/api-proxy/providers/anthropic.js Reads Anthropic WIF env vars and passes them into the OIDC token provider.
containers/api-proxy/anthropic-oidc-token-provider.test.js Updates tests for new required config and adds WIF field-related assertions.
containers/api-proxy/anthropic-oidc-token-provider.js Adds required WIF parameters to the Anthropic OAuth token exchange request body and logs.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comments suppressed due to low confidence (1)

containers/api-proxy/providers/anthropic.js:77

  • OIDC configuration gating/error messaging only checks for ACTIONS_ID_TOKEN_; with the new required Anthropic WIF fields, the adapter should also require non-empty federationRuleId/organizationId/serviceAccountId (and treat empty/whitespace values as missing). Otherwise we instantiate the provider with undefined fields and surface a confusing 503/400. Consider updating the if (requestUrl && requestToken) guard and oidcUnavailableError to mention the required AWF_AUTH_ANTHROPIC_ variables.
  if (oidcRequested) {
    const requestUrl = env.ACTIONS_ID_TOKEN_REQUEST_URL;
    const requestToken = env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
    if (requestUrl && requestToken) {
      const federationRuleId = env.AWF_AUTH_ANTHROPIC_FEDERATION_RULE_ID;
      const organizationId = env.AWF_AUTH_ANTHROPIC_ORGANIZATION_ID;
      const serviceAccountId = env.AWF_AUTH_ANTHROPIC_SERVICE_ACCOUNT_ID;
      const workspaceId = env.AWF_AUTH_ANTHROPIC_WORKSPACE_ID;
      oidcProvider = new AnthropicOidcTokenProvider({
        requestUrl,
        requestToken,
        federationRuleId,
        organizationId,
        serviceAccountId,
        ...(workspaceId !== undefined ? { workspaceId } : {}),
        oidcAudience: env.AWF_AUTH_OIDC_AUDIENCE || 'https://api.anthropic.com',
      });
    }
  }
  const oidcConfigured = !!oidcProvider;
  const oidcUnavailableError = oidcConfigured
    ? 'Anthropic OIDC token unavailable; retry shortly'
    : 'Anthropic OIDC requires ACTIONS_ID_TOKEN_REQUEST_URL and ACTIONS_ID_TOKEN_REQUEST_TOKEN (permissions: id-token: write).';

  • Files reviewed: 5/5 changed files
  • Comments generated: 7

Comment on lines +58 to 70
const federationRuleId = env.AWF_AUTH_ANTHROPIC_FEDERATION_RULE_ID;
const organizationId = env.AWF_AUTH_ANTHROPIC_ORGANIZATION_ID;
const serviceAccountId = env.AWF_AUTH_ANTHROPIC_SERVICE_ACCOUNT_ID;
const workspaceId = env.AWF_AUTH_ANTHROPIC_WORKSPACE_ID;
oidcProvider = new AnthropicOidcTokenProvider({
requestUrl,
requestToken,
federationRuleId,
organizationId,
serviceAccountId,
...(workspaceId !== undefined ? { workspaceId } : {}),
oidcAudience: env.AWF_AUTH_OIDC_AUDIENCE || 'https://api.anthropic.com',
});
Comment on lines 25 to 34
constructor(config) {
super('anthropic_oidc', config);
this._requestUrl = config.requestUrl;
this._requestToken = config.requestToken;
this._federationRuleId = config.federationRuleId;
this._organizationId = config.organizationId;
this._serviceAccountId = config.serviceAccountId;
this._workspaceId = config.workspaceId;
this._oidcAudience = config.oidcAudience || 'https://api.anthropic.com';
this._scope = config.scope;

Comment on lines +45 to +54
const body = {
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
assertion: oidcJwt,
federation_rule_id: this._federationRuleId,
organization_id: this._organizationId,
service_account_id: this._serviceAccountId,
};
if (this._workspaceId !== undefined) {
body.workspace_id = this._workspaceId;
}
Comment thread docs/api-proxy-sidecar.md Outdated
# AWF_AUTH_ANTHROPIC_WORKSPACE_ID: wrkspc_... # required for multi-workspace rules
run: |
sudo --preserve-env=AWF_AUTH_TYPE,AWF_AUTH_PROVIDER \
sudo --preserve-env=AWF_AUTH_TYPE,AWF_AUTH_PROVIDER,AWF_AUTH_ANTHROPIC_FEDERATION_RULE_ID,AWF_AUTH_ANTHROPIC_ORGANIZATION_ID,AWF_AUTH_ANTHROPIC_SERVICE_ACCOUNT_ID \
Comment on lines +90 to +132
it('should include required WIF fields in the exchange request body', async () => {
const capturedBodies = [];
const wifServer = createMockServer({
oauthToken: (body) => {
capturedBodies.push(JSON.parse(body));
return {
statusCode: 200,
body: JSON.stringify({ access_token: 'sk-ant-oat01-wif-token', expires_in: 3600 }),
};
},
});

await new Promise(resolve => wifServer.listen(0, '127.0.0.1', resolve));
const wifPort = wifServer.address().port;

const provider = new AnthropicOidcTokenProvider({
requestUrl: `http://127.0.0.1:${wifPort}/token`,
requestToken: 'mock-token',
federationRuleId: 'fdrl_myrule',
organizationId: 'org-00000000-0000-0000-0000-000000000001',
serviceAccountId: 'svac_myaccount',
workspaceId: 'wrkspc_myworkspace',
});

// Override _exchangeForAnthropicToken to point at the local mock
const origExchange = provider._exchangeForAnthropicToken.bind(provider);
provider._exchangeForAnthropicToken = async (jwt) => {
const response = await httpPost(
`http://127.0.0.1:${wifPort}/v1/oauth/token`,
JSON.stringify({
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
assertion: jwt,
federation_rule_id: provider._federationRuleId,
organization_id: provider._organizationId,
service_account_id: provider._serviceAccountId,
workspace_id: provider._workspaceId,
}),
{ 'Content-Type': 'application/json', 'Accept': 'application/json' }
);
const data = JSON.parse(response.body);
return { access_token: data.access_token, expires_in: data.expires_in || 3600 };
};

Comment on lines +114 to +116
// Override _exchangeForAnthropicToken to point at the local mock
const origExchange = provider._exchangeForAnthropicToken.bind(provider);
provider._exchangeForAnthropicToken = async (jwt) => {
Comment on lines +150 to +161
const provider = new AnthropicOidcTokenProvider({
requestUrl: 'http://localhost:0/token',
requestToken: 'test',
federationRuleId: 'fdrl_norule',
organizationId: 'org-uuid',
serviceAccountId: 'svac_nows',
});

// Access the private fields to verify workspace_id is undefined
expect(provider._workspaceId).toBeUndefined();

provider.shutdown();
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 29, 2026

@copilot address the review feedback

All review feedback addressed in the latest commit. Here's a summary of what changed:

anthropic-oidc-token-provider.js

  • Added constructor validation throwing clear errors for missing federationRuleId, organizationId, serviceAccountId
  • Empty/whitespace workspaceId is now normalized to undefined so it is never sent as ""
  • httpPost is stored as this._httpPost (instance method) so tests can spy on it without module-level mocking

anthropic-oidc-token-provider.test.js

  • Removed the unused origExchange variable (CodeQL alert)
  • Rewrote the "required WIF fields" test to exercise the real _exchangeForAnthropicToken body-building via jest.spyOn(provider, '_httpPost')
  • Rewrote the "omit workspace_id" test to assert on the actual serialized request body (not just the private field)
  • Added tests for required-field validation and empty-string normalization of workspaceId

src/services/api-proxy-service.ts

  • Added AWF_AUTH_ANTHROPIC_FEDERATION_RULE_ID, AWF_AUTH_ANTHROPIC_ORGANIZATION_ID, AWF_AUTH_ANTHROPIC_SERVICE_ACCOUNT_ID, AWF_AUTH_ANTHROPIC_WORKSPACE_ID to the OIDC pickEnvVars allowlist so they reach the sidecar container

src/services/api-proxy-service-env-forwarding.test.ts

  • Added a new AWF_AUTH_ANTHROPIC_* WIF env var forwarding test block covering all four new vars

docs/api-proxy-sidecar.md

  • Added AWF_AUTH_ANTHROPIC_WORKSPACE_ID to the sudo --preserve-env=... list in the GitHub Actions example

Copilot finished work on behalf of lpcox May 29, 2026 11:59
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@lpcox
Copy link
Copy Markdown
Collaborator

lpcox commented May 29, 2026

Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 29, 2026

@copilot fix the failing ci checks https://github.com/github/gh-aw-firewall/actions/runs/26635949361/job/78498762643?pr=3979 https://github.com/github/gh-aw-firewall/actions/runs/26635949361/job/7849...

Fixed in the latest commit. server.auth.test.js (2 tests) and server.lifecycle.test.js (1 test) were creating Anthropic OIDC adapters without the required AWF_AUTH_ANTHROPIC_FEDERATION_RULE_ID, AWF_AUTH_ANTHROPIC_ORGANIZATION_ID, and AWF_AUTH_ANTHROPIC_SERVICE_ACCOUNT_ID env vars — causing the new constructor validation to throw. Added those fields to all three test fixtures.

Copilot finished work on behalf of lpcox May 29, 2026 12:27
@github-actions
Copy link
Copy Markdown
Contributor

Smoke Test: Claude Engine

  • ✅ GitHub API: 2 PR entries found
  • ✅ GitHub check: playwright PASS
  • ✅ File verify: smoke-test file present

Result: PASS

💥 [THE END] — Illustrated by Smoke Claude

@github-actions
Copy link
Copy Markdown
Contributor

🔥 Smoke Test: Copilot BYOK (Offline Mode)

Test Result
GitHub MCP connectivity ✅ (listed PRs successfully)
GitHub.com HTTP ⚠️ pre-step data unavailable (template not expanded)
File write/read ⚠️ pre-step data unavailable (template not expanded)
BYOK inference (agent → api-proxy → api.githubcopilot.com) ✅ (responding now)

Running in BYOK offline mode (COPILOT_OFFLINE=true) via api-proxy → api.githubcopilot.com.

PR author: @Copilot · Assignees: @lpcox, @Copilot

Overall: PARTIAL (BYOK path ✅, pre-step smoke data unexpanded)

🔑 BYOK report filed by Smoke Copilot BYOK

@github-actions
Copy link
Copy Markdown
Contributor

🔥 Smoke Test Results

Test Result
GitHub MCP (list PRs)
File write/read
GitHub connectivity

PR: fix(api-proxy): add required Anthropic WIF exchange parameters
Author: @Copilot | Assignees: @lpcox, @Copilot

Overall: PASS

📰 BREAKING: Report filed by Smoke Copilot

@github-actions
Copy link
Copy Markdown
Contributor

Smoke Test: API Proxy OpenTelemetry Tracing

Scenario Result Notes
1. Module Loading otel.js loads successfully; exports: startRequestSpan, setTokenAttributes, endSpan, endSpanError, shutdown, isEnabled
2. Test Suite 33/33 tests passed (otel.test.js)
3. Env Var Forwarding src/services/api-proxy-service.ts forwards OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS, and sets OTEL_SERVICE_NAME
4. Token Tracker Integration onUsage callback exists in upstream-response.js (line 237) as the OTEL hook point
5. OTEL Diagnostics No OTLP endpoint configured in this run — graceful degradation confirmed, no errors

All scenarios pass. ✅

📡 OTel tracing validated by Smoke OTel Tracing

@github-actions
Copy link
Copy Markdown
Contributor

Gemini Engine Smoke Test Results\n\n- PR: refactor: move test helper imports to .test-utils.ts files (#3972)\n- PR: (Not found - mcpscripts missing)\n- GitHub MCP Testing: ❌\n- GitHub.com Connectivity: ❌ (SSL 35 / HTTP 400)\n- File Writing Testing: ✅\n- Bash Tool Testing: ✅\n\nOverall status: FAIL

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • localhost

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "localhost"

See Network Configuration for more information.

💎 Faceted by Smoke Gemini

@github-actions
Copy link
Copy Markdown
Contributor

Chroot Runtime Version Comparison

Runtime Host Version Chroot Version Match?
Python Python 3.12.13 Python 3.12.3
Node.js v24.16.0 v22.22.3
Go go1.22.12 go1.22.12

Result: Not all versions match — smoke-chroot label not applied.

Tested by Smoke Chroot

@github-actions
Copy link
Copy Markdown
Contributor

🏗️ Build Test Suite Results

Ecosystem Project Build/Install Tests Status
Bun elysia 1/1 passed ✅ PASS
Bun hono 1/1 passed ✅ PASS
C++ fmt N/A ✅ PASS
C++ json N/A ✅ PASS
Deno oak N/A 1/1 passed ✅ PASS
Deno std N/A 1/1 passed ✅ PASS
.NET hello-world N/A ✅ PASS
.NET json-parse N/A ✅ PASS
Go color 1/1 passed ✅ PASS
Go env 1/1 passed ✅ PASS
Go uuid 1/1 passed ✅ PASS
Java gson 1/1 passed ✅ PASS
Java caffeine 1/1 passed ✅ PASS
Node.js clsx all passed ✅ PASS
Node.js execa all passed ✅ PASS
Node.js p-limit all passed ✅ PASS
Rust fd 1/1 passed ✅ PASS
Rust zoxide 1/1 passed ✅ PASS

Overall: 8/8 ecosystems passed — ✅ PASS

Generated by Build Test Suite for issue #3979 · sonnet46 924.1K ·

@github-actions
Copy link
Copy Markdown
Contributor

Smoke Test Results — FAIL

Check Result
Redis PING ❌ Timeout (no response)
PostgreSQL pg_isready ❌ No response
PostgreSQL SELECT 1 ❌ Failed

host.docker.internal is not reachable from this environment. All 3 checks failed.

Overall: ❌ FAIL

🔌 Service connectivity validated by Smoke Services

@github-actions
Copy link
Copy Markdown
Contributor

feat: support custom API auth headers for internal AI gateways ✅
Optimize duplicate-code-detector workflow with precomputed analysis and tighter run scope ✅
fix(api-proxy): add required Anthropic WIF exchange parameters ❌
Overall: FAIL

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • registry.npmjs.org

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "registry.npmjs.org"

See Network Configuration for more information.

🔮 The oracle has spoken through Smoke Codex

@lpcox lpcox merged commit e63f733 into main May 29, 2026
69 of 70 checks passed
@lpcox lpcox deleted the copilot/fix-anthropic-oidc-exchange-params branch May 29, 2026 12:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

api-proxy: Anthropic OIDC provider missing required exchange parameters (federation_rule_id, organization_id, service_account_id)

4 participants