Unmasking Hidden Risks: How GitHub Attestations Can Mislead on Git Quality
In the realm of modern software development, ensuring the integrity and provenance of build artifacts is paramount. GitHub's Artifacts Attestation feature is designed to provide this crucial build provenance, bolstering supply chain security. However, a recent discussion on the GitHub Community forum, initiated by user 'amiller', has brought to light a significant vulnerability that could undermine the trust placed in these attestations, directly impacting git quality and the reliability of our software development productivity tools.
The Provenance Gap: Malicious Runners and Misleading Attestations
The core of the issue lies in how GitHub-hosted larger runners, particularly those on paid plans, can be configured. Organizations can choose the image their runners boot, and critically, runner names are not validated against GitHub's own labels. This means a malicious actor could create a custom runner, name it after a standard one (e.g., ubuntu-24.04), and serve jobs pinned to it. The resulting attestation, alarmingly, provides no evidence against this.
The discussion highlights that the runnerEnvironment field in the attestation reports github-hosted even when an organization-created runner with a custom, potentially compromised, image is used. This creates a dangerous blind spot for verifiers.
A Concrete Example of Misleading Provenance
To illustrate the severity, 'amiller' provided a compelling example:
- A simple
hello.cprogram is designed to print '4'. - A
build.ymlworkflow compiles and attests the binary, pinned toruns-on: ubuntu-24.04.
When this workflow runs on a standard GitHub-hosted runner, the resulting binary prints '4', as expected. However, when the same workflow and commit are run on an organization-created runner with a custom image (which subtly rewrites the source before compilation), the verified binary prints '5'. Crucially, the gh attestation verify command, even with the --deny-self-hosted-runners flag, returns success and shows runnerEnvironment: github-hosted, providing a false sense of security.
$ gh attestation verify hello --repo gh-zktls-tester/capture-test --format json
{
"sourceRepositoryURI": "https://github.com/gh-zktls-tester/capture-test",
"sourceRepositoryDigest": "16b7c64c566bab12ec7c6312fcdac8ad126aafee",
"buildSignerURI": ".../.github/workflows/build.yml@refs/heads/main",
"runnerEnvironment": "github-hosted",
"buildTrigger": "workflow_dispatch"
}
$ gh attestation verify hello --repo gh-zktls-tester/capture-test --deny-self-hosted-runners
exit 0
$ ./hello
5
This demonstrates a critical flaw: the attestation certifies an artifact that the attested source could not have produced, directly undermining git quality and trust in the build chain.
The Missing Information in Attestations
The discussion meticulously details what's missing from the signed material:
runner_environmentis too broad, classifying both legitimate GitHub-managed runners and organization-managed custom-image runners asgithub-hosted.- No field names the specific runner, its group, its size, or its image.
builder.idnames the workflow file, not the machine executing it.
Leveraging the API for Post-Hoc Verification
While the attestation itself is insufficient, the GitHub Jobs API does expose crucial distinguishing information. The attestation's invocationId can be used to look up the run via the API, which then reveals the runner_group_name and runner_group_id. GitHub's own shared runners reside in a reserved group (GitHub Actions, id: 0), whereas organization-created runners are in groups owned by that organization (e.g., default, id: 1).
| jobs-API field | control 32667660392 | substituted 32670271551 |
|---------------------|----------------------|-------------------------|
| operated by | GitHub's shared pool | the organization |
| `runner_group_name` | `GitHub Actions` | `default` |
| `runner_group_id` | `0` (GitHub's reserved group) | `1` (an org group) |
| `runner_name` | `GitHub Actions 1000000042` | `ubuntu-24.04-1000000046` |
The job logs further confirm the distinction, showing 'GitHub Actions' image details versus 'Custom Name: shimcompile' for the substituted run.
Recommendations for Stronger Provenance
User 'ralftpaw' aptly describes this as a "sharp provenance gap." Until runner identity is signed, they suggest treating runnerEnvironment: github-hosted as an insufficient claim. Verifiers can, as a temporary workaround, follow the invocationId to the Actions jobs API and require runner_group_id == 0, recording runner name and image release in a supplemental receipt. This, however, is a post-hoc API check, not part of the attestation's cryptographic proof.
The long-term solution, as proposed by 'amiller', is to include the runner image or at least the runner_group_id into the Actions OIDC token, and subsequently into the certificate and SLSA predicate. This would embed immutable runner identity directly into the proof, making attestations truly reliable for ensuring git quality and the security of our software development productivity tools.
