github attestor
Fetches the GitHub Actions OIDC ID token, verifies its signature against the GitHub JWKS, and records the decoded claims alongside a small slice of GITHUB_* / RUNNER_* workflow context.
| Name | github |
|---|---|
| Predicate type | https://aflock.ai/attestations/github/v0.1 |
| Lifecycle | prematerial |
| Default binary? | No |
| Category | ci-context (primary) |
| Recommended trace | off — no syscall tracing needed |
| Auto-attaches when |
|
The facts in this box are generated from the cilock binary's own catalog (cilock tools list). Do not hand-edit — run npm run gen:catalog.
What it captures
The attestor first fails fast with ErrNotGitHub unless GITHUB_ACTIONS=true. It then fetches an ID token from GitHub's token endpoint (audience witness) and delegates JWT parsing + JWKS verification to the embedded jwt attestor. The remaining fields are sampled directly from GITHUB_* / RUNNER_* env vars.
Top-level fields (the json tags on Attestor):
jwt— full nestedjwtattestor record, includingclaims(the entire decoded OIDC payload:sub,aud,iss,repository,ref,sha,workflow,event_name,actor,job_workflow_ref,run_id,runner_environment, etc. — whatever GitHub put in the token) andverifiedBy(jwksUrlplus the JWK that validated the signature).ciconfigpath—GITHUB_ACTION_PATH.pipelineid—GITHUB_RUN_ID.pipelinename—GITHUB_WORKFLOW.pipelineurl—<GITHUB_SERVER_URL>/<GITHUB_REPOSITORY>/actions/runs/<GITHUB_RUN_ID>(also emitted as a subject and the sole back-ref).projecturl—<GITHUB_SERVER_URL>/<GITHUB_REPOSITORY>(also emitted as a subject).runnerid—RUNNER_NAME.cihost— declared but not populated byAttest().ciserverurl—GITHUB_SERVER_URL.runnerarch—RUNNER_ARCH.runneros—RUNNER_OS.
The OIDC claims themselves live under jwt.claims — the attestor does not duplicate them at the top level.
When to use
In any GitHub Actions workflow that grants permissions: id-token: write. The OIDC token is GitHub's signed assertion of who is running, in what repo, at what ref, in what workflow; capturing and verifying it gives downstream policy a CI-platform-rooted identity that is independent of the cilock binary and any signer it uses.
Flags
None. Two undocumented env-var overrides exist for testing:
| Env var | Purpose |
|---|---|
WITNESS_GITHUB_JWKS_URL | Override JWKS endpoint (default https://token.actions.githubusercontent.com/.well-known/jwks). |
ACTIONS_ID_TOKEN_REQUEST_URL / ACTIONS_ID_TOKEN_REQUEST_TOKEN | Set by the GitHub runner; consumed to fetch the ID token. |
Audience is hard-coded to witness.
Output shape
{
"jwt": {
"claims": {
"sub": "repo:aflock-ai/rookery:ref:refs/heads/main",
"aud": "witness",
"iss": "https://token.actions.githubusercontent.com",
"repository": "aflock-ai/rookery",
"ref": "refs/heads/main",
"sha": "deadbeef...",
"workflow": "release",
"event_name": "push",
"actor": "colek42",
"job_workflow_ref": "aflock-ai/rookery/.github/workflows/release.yml@refs/heads/main",
"run_id": "1234567890",
"runner_environment": "github-hosted"
},
"verifiedBy": {
"jwksUrl": "https://token.actions.githubusercontent.com/.well-known/jwks",
"jwk": { "kty": "RSA", "kid": "...", "n": "...", "e": "AQAB" }
}
},
"pipelineid": "1234567890",
"pipelinename": "release",
"pipelineurl": "https://github.com/aflock-ai/rookery/actions/runs/1234567890",
"projecturl": "https://github.com/aflock-ai/rookery",
"ciserverurl": "https://github.com",
"runneros": "Linux"
}
Gotchas
- Hard-fails outside Actions. If
GITHUB_ACTIONS != "true"the attestor returnsErrNotGitHuband produces no record. - OIDC permission required. Without
permissions: id-token: writeon the job,ACTIONS_ID_TOKEN_REQUEST_URL/ACTIONS_ID_TOKEN_REQUEST_TOKENwill be unset andfetchTokenwill fail. - Signature is verified, not just decoded. The embedded
jwtattestor fetches the JWKS (response capped at 1 MB), parses the signed JWT, validates the signature against the matchingkid, and only then populatesclaims. A bad signature causesAttest()to fail. - Audience is fixed. Token requests always pass
audience=witness; this is also the value asserted in theaudclaim. There is no flag to change it — only theWITNESS_GITHUB_JWKS_URLenv var, intended for test stubs. - Token response is size-limited. Both the token-endpoint response and the JWKS response are read through a 1 MB
io.LimitReaderto prevent OOM from a hostile endpoint. cihostis never populated in this version — it's declared in the struct butAttest()does not assign to it.- Subjects + back-refs.
pipelineurlandprojecturlare both subjects; onlypipelineurlis a back-ref.
CLI example
Real GitHub Actions OIDC token. The attestor calls ACTIONS_ID_TOKEN_REQUEST_URL with the ACTIONS_ID_TOKEN_REQUEST_TOKEN bearer and verifies the JWT against GitHub's public JWKS.
# In a workflow with `permissions: id-token: write`:
cilock run --step github-validation \
--signer-file-key-path key.pem --outfile attestation.json --workingdir . \
--attestations environment,git,github,github-action \
-- echo "real GH Actions run $GITHUB_RUN_ID"
Validated via .github/workflows/cilock-ci-attestors.yml in the linked repo. Captures the live OIDC token signed by token.actions.githubusercontent.com. See the full real-data example at https://github.com/aflock-ai/attestor-compliance-examples/tree/main/19-github.