Skip to content

Use federated model credentials instead of static LLM API keys - #100

Open
rgarcia wants to merge 1 commit into
mainfrom
hypeship/federated-llm-credentials
Open

rgarcia wants to merge 1 commit into
mainfrom
hypeship/federated-llm-credentials

Conversation

@rgarcia

@rgarcia rgarcia commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Blocked: don't merge yet. The live integration run fails because the Anthropic token exchange returns 401 authentication_error (run). The cause is in repo settings, not this diff:

  • This repo uses GitHub's immutable OIDC subject claims. GET repos/kernel/browser-loop/actions/oidc/customization/sub returns use_immutable_subject: true, so sub starts with repo:kernel@194616459/browser-loop@1214004226.
  • The provider-side federation rules match sub against the prefix repo:kernel/*. Every other kernel repo checked (kernel, hypeship, hypeman, infra, kernel-images-private, cli, security-workflows) uses the default repo:kernel/<repo> form.

There are two ways to unblock this:

  • Turn off immutable subjects for this repo (repo admin; AWS roles here only trust environment subjects).
  • Add Anthropic/OpenAI federation rules that also accept repo:kernel@194616459/*.

Once either is done, re-run the integration job.

Summary

CI should not need the ANTHROPIC_API_KEY / OPENAI_API_KEY org secrets, so they can be deleted. Instead, jobs exchange their GitHub OIDC token for short-lived provider credentials through workload identity federation. This PR moves this repo's CI off both secrets.

What actually used the keys

  • integration: only test/anthropic-native.integration.test.ts calls a model (Anthropic, two short requests). No integration test uses OpenAI. The live e2e suite is skipped in this job because LOOP_E2E_LIVE is unset.
  • live-e2e: the OpenAI and Anthropic cases (and the openai-to-gemini switch case) need LOOP_E2E_OPENAI / LOOP_E2E_ANTHROPIC under CI. Neither is set, so these cases were already skipped. The last main run passed only the two moonshotai tests, with 10 skipped. Both keys were dead config here, so this PR drops them.

Approach

SDK-native federation, plugged into pi's transport. The test goes through pi-ai's Anthropic transport, which builds its own SDK client (pi-ai bundles @anthropic-ai/sdk 0.91, which has no federation support). Loop's Anthropic client-toolset adapter needs that fetch transport, so a custom federating client can't be passed in.

The test helper therefore uses the federation pieces from the repo's existing @anthropic-ai/sdk 0.117 devDependency, with no version bump:

  • oidcFederationProvider does the jwt-bearer exchange.
  • identityTokenFromFile reads the token file.
  • TokenCache caches the token and refreshes it.

The resulting access token goes to pi through its per-request env override as ANTHROPIC_AUTH_TOKEN, which pi sends as Authorization: Bearer. The OAuth anthropic-beta header the API needs for bearer tokens is added alongside it.

When ANTHROPIC_API_KEY is set (local development), the helper returns it unchanged and federation is never touched.

Token refresh

kernel/security-workflows/.github/actions/llm-credentials (pinned to a SHA) writes a fresh GitHub OIDC token to ANTHROPIC_IDENTITY_TOKEN_FILE every 30s and exports the federation settings. The helper calls TokenCache.getToken() before every request:

  • It reuses the access token while more than 120s remain.
  • Inside that window it re-reads the token file and re-exchanges.
  • Each GitHub token is exchanged at most once.

Long runs therefore keep working, although this job takes about 5s of test time today. A final if: always() step stops the refresh loop and deletes the token file.

Files

  • .github/workflows/ci.yml:
    • integration now grants contents: read + id-token: write (no environment:).
    • It runs llm-credentials with the Anthropic IDs from the org vars, then stops it after the tests.
    • The OpenAI and Anthropic secrets are removed from integration and live-e2e. Other secrets are unchanged.
  • packages/browser-loop/test/support/anthropic-auth.ts: new anthropicRequestAuth(). It returns the API key when set, otherwise federated per-request auth, or undefined when neither is configured (the test then skips, as before).
  • packages/browser-loop/test/anthropic-native.integration.test.ts: uses anthropicRequestAuth() instead of reading ANTHROPIC_API_KEY directly.
  • packages/browser-loop/test/anthropic-auth.test.ts: unit tests for the helper:
    • API-key precedence and missing configuration.
    • The exact exchange request, and reuse of a fresh token.
    • Re-reading the token file and re-exchanging near expiry.
    • pi's Anthropic transport with client toolsets sends Authorization: Bearer <token>, no x-api-key, and includes the OAuth beta.

Testing

  • npm run build --workspace @onkernel/browser-loop, then npm test --workspace @onkernel/browser-loop: 35 files, 447 tests passed.
  • npx tsc -b: passes.
  • npm run test:integration --workspace @onkernel/browser-loop with no credentials: the Anthropic integration test skips cleanly.
  • actionlint 1.7.7 (sha256 verified) on .github/workflows/ci.yml: no findings.
  • Live: this PR's integration job.
    • What worked: the action wrote the identity token and exported the settings, and the helper read the file and called the exchange endpoint.
    • What failed: the exchange returned 401 because of the subject-claim mismatch described above. A live model call has not been verified yet.
    • live-e2e, unit and typecheck-and-ptywright passed.

@rgarcia
rgarcia requested a review from dprevoznik November 9, 2026 22:06
@rgarcia

rgarcia commented Nov 9, 2026

Copy link
Copy Markdown
Contributor Author

Picked dprevoznik to review: no one but the author has history on these files, so all candidates tied at the lightest queue; broke the tie toward the only other committer in this repo; nothing pending

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant