Unmasking the Provenance Gap: Securing Your Software Supply Chain and Boosting Git Quality with Reliable GitHub Attestations
In the intricate world of modern software development, trust is the bedrock of every successful release. Ensuring the integrity and provenance of your build artifacts is not just a best practice; it's a critical component of supply chain security. GitHub's Artifacts Attestation feature aims to provide this crucial transparency, offering a cryptographic link between your source code and your deployed binaries. However, a recent discussion on the GitHub Community forum has unveiled a significant blind spot, one that could fundamentally undermine the very trust these attestations are designed to instill and directly impact your git quality.
This vulnerability, brought to light by 'amiller', exposes a gap in how GitHub-hosted runners are specified and attested, creating a scenario where a seemingly legitimate attestation could certify an artifact that was not built from its claimed source. For dev teams, product managers, delivery managers, and CTOs, this isn't merely a technical nuance; it's a potential threat to your software supply chain, your delivery confidence, and the overall reliability of your software development productivity tools.
The Provenance Gap: Malicious Runners and Misleading Attestations
The heart of the problem lies in the flexibility afforded to organizations on paid plans using GitHub-hosted larger runners. These organizations can select the specific image their runners boot. Critically, GitHub does not validate runner names against its own standard labels. This opens a door for malicious actors – or even unintentional misconfigurations – to create a custom runner, name it identically to a standard GitHub-provided runner (e.g., ubuntu-24.04), and then serve jobs pinned to that seemingly trusted label.
The alarming consequence? The resulting build attestation, meticulously designed to provide provenance, contains no record that would expose this substitution. The runnerEnvironment field, intended to differentiate between GitHub-controlled and self-hosted environments, misleadingly reports github-hosted even when an organization-controlled runner with a custom, potentially compromised, image is in play. This creates a dangerous blind spot for anyone attempting to verify the true origin of a build artifact.
A Concrete Example: When '4' Becomes '5'
To underscore the severity of this vulnerability, 'amiller' provided a compelling, real-world example:
- A simple C program (
hello.c) is written to print the number '4'. - A standard GitHub Actions workflow (
build.yml) compiles this program and then usesactions/attest-build-provenance@v2to attest the resulting binary, explicitly pinning the job to run onruns-on: ubuntu-24.04.
When this workflow executes on a genuine GitHub-provided shared runner, the resulting binary behaves exactly as expected, printing '4'. The attestation correctly links the binary to the source. However, the exact same workflow, from the same commit, was then run on an organization-created runner. This custom runner was configured with an image that subtly rewrote the source code before compilation, changing the '4' to a '5', then compiled it, and finally restored the original source file to leave no visible trace in the workspace.
The critical finding: When a consumer downloaded this 'compromised' artifact and verified it using gh attestation verify, the command reported success. Even with the --deny-self-hosted-runners flag, the verification passed, despite the binary printing '5' instead of '4'. This demonstrates a profound failure in the attestation chain to provide reliable provenance, directly impacting git quality and the trust in your CI/CD pipeline.
The Missing Link: What the Attestation Doesn't Tell You
The core problem is the lack of specific, immutable runner identity within the signed attestation material. Here’s what the investigation revealed:
- The
runner_environmentfield is too broad, reportinggithub-hostedfor both genuine GitHub-shared runners and organization-created runners that merely run on GitHub's infrastructure. This is a crucial distinction that is lost. - Fields like
builder.idpoint to the workflow file, not the specific machine or runner configuration. No field explicitly names the runner, its group, its size, or, most importantly, the image it booted from. - Even attempting to pin a workflow to GitHub's own shared runner group (
GitHub Actions) fails, as this group is reserved and not directly addressable by name inruns-on.
This means that from the perspective of the attestation alone, the 'good' build and the 'malicious' build are indistinguishable, presenting a significant challenge for maintaining robust git quality and secure delivery.
Uncovering the Truth: The API Holds the Clues (For Now)
While the attestation itself falls short, the GitHub API does contain the necessary information to differentiate these runs. The attestation's Fulcio extension and runDetails.metadata.invocationId provide a direct link to the specific GitHub Actions run. From there, the Jobs API exposes critical fields:
runner_group_name: For genuine GitHub-shared runners, this isGitHub Actions. For organization-created runners, it's a group owned by the organization (e.g.,default).runner_group_id: GitHub's reserved group has an ID of0. Organization groups have different IDs.runner_name: Distinct names for GitHub's runners versus organization-created ones.
The job logs further confirm this, explicitly stating 'Runner Image: ubuntu-24.04' for GitHub's shared pool versus 'Source: Custom Name: shimcompile' for the substituted run.
This means a verifier can manually follow the invocationId to the API and check runner_group_id == 0 to confirm a truly GitHub-controlled environment. However, as 'ralftpaw' notes in the discussion, this is 'post-hoc API evidence' and not part of the cryptographically signed attestation. It introduces a dependency on mutable API state and adds complexity to the verification process, undermining the promise of self-contained, verifiable provenance.
The Path Forward: Securing Provenance and Boosting Trust
To close this critical provenance gap and restore full confidence in GitHub Artifacts Attestation as a leading software development productivity tool, the solution is clear: the runner's true identity must be embedded directly into the signed material.
Immediate Action (Verifier Side)
Until a platform-level fix is implemented, verifiers should treat runnerEnvironment: github-hosted as an insufficient claim. They must augment their verification process by using the invocationId to query the GitHub Jobs API and explicitly require runner_group_id == 0 (or runner_group_name == GitHub Actions). This should be a 'fail closed' requirement if the lookup is unavailable or inconsistent.
Longer-Term Solution (Platform Side)
The most robust fix involves integrating the runner_group_id and, ideally, an immutable runner-image digest/version directly into the Actions OIDC token, and subsequently into the certificate and the SLSA predicate. GitHub already computes and exposes this information via its API; including it in the attestation would provide an unforgeable, cryptographically verifiable link to the actual build environment.
Furthermore, ensuring that organization runner names cannot mimic GitHub's reserved runner names (e.g., by disallowing spaces or specific prefixes) would add another layer of defense.
Conclusion
This discussion highlights a crucial area for improvement in GitHub's build provenance capabilities. For CTOs, delivery managers, and dev teams, the integrity of your build artifacts is non-negotiable. Addressing this provenance gap is essential not only for enhancing supply chain security and compliance but also for upholding the fundamental git quality and trustworthiness of the software you deliver. By integrating robust runner identity into attestations, GitHub can further solidify its position as a trusted platform for secure and efficient software development.
