Skip to main content

GitHub Actions Workload Identity

GitHub Actions can exchange its workflow OIDC token for a KnoxCall access token via RFC 8693 — no secrets stored in the repo, organization, or environment.

1. Add the workflow permission

In your .github/workflows/*.yml:

2. Configure the trust binding in KnoxCall

In the dashboard, open Settings → API → Workload Identity → Connect workload — the wizard checks your issuer, builds the rule, and lets you test a real token before your first run — or call the admin API directly. The /admin/* routes are served on the admin host (admin.knoxcall.com / any knoxcall.com host) and are authenticated by your logged-in admin/owner session — a session JWT plus the X-Tenant-ID header — not an API key against api.knoxcall.com.
Creating a binding requires a recent step-up verification (passkey, TOTP or emailed code) within the last 5 minutes — a binding is a trust that lets an external workload mint tenant tokens, so it carries the same bar as creating an OAuth client. A curl carrying only a session JWT answers 403 {"requires_step_up": true}: verify in the Dashboard (any action that prompts for your passkey or TOTP), then replay the request inside the 5-minute window. Each verification is single-use, so a retried request needs a fresh one. Listing and revoking bindings need no verification — containment must never be gated.
Attribute conditions are required. They prevent any caller on token.actions.githubusercontent.com (i.e., any GitHub repository in the world) from minting tokens for your tenant. Always lock down at minimum repository; lock to workflow + ref for production paths. allowed_scopes is required and may not be empty. A binding with no scopes is not a binding that grants nothing — a token with an empty scope is treated as unnarrowed, so it would reach every endpoint your tenant has. Creating one is refused with 400 invalid_allowed_scopes, and a binding that somehow holds an empty list refuses to exchange. List exactly the scopes the workflow needs.

3. Call KnoxCall from the workflow

In deploy.js:
The SDK auto-detects ACTIONS_ID_TOKEN_REQUEST_URL + ACTIONS_ID_TOKEN_REQUEST_TOKEN, fetches the OIDC token, exchanges it for a 1-hour KnoxCall access token, and uses it for all API calls. Zero stored secrets anywhere in your repo or organization.

What’s in the OIDC token

GitHub puts these claims in every workflow OIDC token; you can match on any of them in attribute_conditions:

Recommendations

  • For production paths: require environment + ref + repository. GitHub Environments add a manual-approval gate you should leverage.
  • For PR / CI paths: require event_name != pull_request to avoid third-party fork PRs minting tokens.
  • Use one binding per logical workload, not one per repo. Granular bindings make last_used_at data actually useful.

Troubleshooting

  • invalid_grant: no binding matched — JWT attributes don’t match. Fetch and decode the OIDC token to inspect its claims and compare them to your binding:
  • invalid_request: subject_token signature invalid — Audience mismatch. The SDK appends &audience=knoxcall:api to the OIDC request — make sure your binding’s audience field is exactly knoxcall:api.
  • invalid_request: subject_token expired — Clock skew. Ensure your runner’s clock is sane (NTP).