CI/CD evidence

GitHub Actions artifacts: useful evidence without false trust

A GitHub Actions artifact preserves files from a workflow run for later inspection or another job. It can carry a test report or release candidate, but its existence does not mean the test passed or the candidate is safe to execute. Record the producing revision, run and attempt, job, artifact identity and digest, then verify the expected source and result before a trusted job consumes it. Provenance attestations can strengthen origin checks; deployment still needs target-side proof.

Decide whether the file is evidence or a reusable input

A workflow artifact is a file or collection of files produced during a run and retained after the job finishes. Test reports, screenshots, build outputs, and logs are natural examples. GitHub distinguishes artifacts from dependency caches: a cache makes repeatable setup faster, while an artifact preserves a result for viewing or transfer between jobs. A release candidate may be handed from a build job to a later release job, but the transfer alone does not approve the candidate. Keep the artifact's producer, job conclusion, and intended consumer in the same record.

This distinction matters for an agent reading CI results. A failed test job may upload a report precisely because it failed. Seeing report.xml in the run's artifact list proves that a file was uploaded; it does not prove that the report contains passing tests, that all tests ran, or that the required check is green. Conversely, a successful check does not tell you which particular binary was uploaded unless the workflow ties the output to the same revision and job. An agent should inspect the run outcome and the file contents, then report the failure or success with the exact source revision.

Use a cache for reconstructible downloads or intermediate inputs that can be discarded. Use an artifact when someone must inspect a result after the run or a downstream job must consume the produced bytes. Neither storage mechanism creates trust by itself. A cache hit is not a release artifact; an artifact download is not a test verdict. The producer's event and trust level travel with the file, even when the download happens in a new job with stronger permissions.

Sources: Workflow artifacts — GitHub Docs · Dependency caching — GitHub Docs

Keep an artifact ledger across handoff

Imagine a synthetic run that builds candidate A from revision H2. The first attempt fails during tests but uploads a diagnostic report; the second attempt passes and uploads a candidate bundle. A release job later downloads an item named candidate. If the ledger records only the name, the team cannot tell which attempt produced the bytes or whether the release consumed the passing candidate. Give each candidate a stable identity beyond its friendly name: source SHA, run ID, attempt, producer job, environment, artifact ID, digest, and retained-until date. Then record the target revision observed after release.

Illustrative handoff ledger; H2, R17, and values below are placeholders, not real GitHub identifiers.
Evidence fieldDiagnostic reportRelease candidate
Source and runH2, run R17, attempt 1H2, run R17, attempt 2
Producer and origintest job, trusted push eventbuild job, trusted push event
Job conclusionfailed; report uploaded for diagnosispassed required build and tests
Artifact identity and digestreport artifact ID plus recorded digestcandidate artifact ID plus recorded digest
Retention and environmentreview until retention expiry, no deploy authorityrelease handoff to reviewed environment
Target proofnone; diagnostic onlycompare consumed digest and target live revision after delivery

The ledger is an original operating example, not a claim about any customer or GitHub run. It helps answer two different questions. Which bytes did the release job consume? Which revision did the destination actually serve? The first requires an artifact identity and an integrity check. The second requires deployment evidence from the target. If either value is absent, mark that part unresolved. A green build on H2 cannot prove production serves H2, and a live H2 label cannot by itself prove which archived candidate was used.

Choose a retention period that covers your debugging and release review window, then read the repository and organization settings that govern it. GitHub documents default retention and configurable limits that differ by repository type and managing policy. An artifact can expire or be deleted, taking away the ability to inspect it later. Preserve longer-term release records in an approved durable system when policy requires them. Do not assume a copied artifact URL will remain available indefinitely or that everyone with a link can read a private repository's artifact.

Sources: actions/upload-artifact README — GitHub · Downloading workflow artifacts — GitHub Docs · Managing GitHub Actions settings for a repository — GitHub Docs

Use names for routing and IDs for identity

A descriptive artifact name helps humans find the output, but name alone is a weak handoff key. The current upload-artifact action returns an artifact ID and a digest on successful upload. Its documentation says artifact names must be unique within the relevant run; matrix jobs trying to upload to the same name can conflict. Give matrix outputs a distinct name that includes the dimension that changes the bytes, then collect or select them explicitly. If a workflow uses overwrite, GitHub's upload action deletes the prior artifact and creates a new ID. Any ledger still pointing at the old ID is stale.

The upload action also has behavior that affects evidence quality. Its default when no files match is to warn rather than fail, so a green job may not contain the report an operator expected unless the workflow checks for it. Hidden files are excluded by default in the current action; turning inclusion on requires reviewing the path for credentials and other sensitive files. Review the action inputs used in the repository rather than assuming a wildcard captured every intended result. An artifact that is missing a file can be perfectly intact as an archive while still being incomplete evidence.

The download action can compare the downloaded artifact digest with the upload digest. GitHub's artifact tutorial says a mismatch produces a warning in the run UI and logs. A warning is a signal to stop and investigate, not an automatic release block you can assume exists. A digest match means these bytes match the recorded upload digest; it does not establish that the producing workflow was trusted, that the build used approved inputs, or that the software is safe. Bind digest verification to a separately checked producer identity and policy.

Sources: actions/upload-artifact README — GitHub · Store and share data with workflow artifacts — GitHub Docs

Do not promote untrusted artifacts into privileged code

A fork pull request can produce an artifact under a lower-trust event. Downloading that artifact in a later workflow with release secrets does not change its origin. A compressed archive can carry scripts, package metadata, or paths that become active when extracted or executed. GitHub's security guidance warns that privileged workflows fetching and running untrusted pull request code or artifacts can cross the same boundary as a dangerous checkout. Treat the fork artifact as untrusted data; inspect it in an isolated environment without release authority, and rebuild a release candidate from a trusted revision after review.

Repository artifact visibility is another boundary. GitHub says a signed-in person with repository read access can download workflow artifacts. That means test dumps and screenshots are not a private vault within the repository. Scrub secrets, personal data, and environment files before upload, and check the actual paths selected by the action. An organization with many readers should not place a production credential in a report merely because the workflow log would mask it. The stored file has its own access and retention rules.

If release jobs hand candidates between workflows, require a trusted producer event, an expected revision, a passing required verification result, and a specific artifact identity before consumption. Validate the archive contents and destination paths before extraction, and do not execute a file solely because its name matches candidate. If the producer and consumer have different privileges, the boundary check belongs at consumption time. An agent may summarize the lower-trust report, but that report should not be a command source for the privileged release job.

Sources: Secure use reference — GitHub Docs · Downloading workflow artifacts — GitHub Docs · Workflow artifacts — GitHub Docs

Use attestations for origin, then still check behavior

Where artifact attestations are available and configured, they can give a cryptographically verifiable statement about where and how an artifact was built. GitHub describes provenance information including the associated workflow, repository, environment, commit SHA, and triggering event. Generating an attestation is only the producer side; the consuming side must verify it and compare the reported identity with the policy expected for that release. A valid signature from a different repository or an unapproved workflow is not sufficient merely because verification succeeded cryptographically.

Define the expected repository and signer workflow, then require the correct source revision or branch and environment conditions for the candidate. GitHub's verification guidance includes options to constrain the signer repository and workflow when reusable workflows build the artifact. The policy should also account for the actual artifact digest, so the verified statement is about the bytes being consumed. These checks establish a chain from approved source and workflow to the downloaded candidate. They do not prove that tests passed unless the run's required results are checked separately.

GitHub explicitly cautions that an attestation is not a guarantee that software is secure. The same principle applies to an ordinary SHA-256 digest: it helps detect change relative to a recorded value, but an attacker can supply both malicious bytes and a matching digest if the record itself is not trusted. Complete the decision with policy, test results, content review where needed, and the target's observed live revision after deployment. That is the difference between preserving evidence and treating a convenient archive as a release verdict.

Sources: Artifact attestations — GitHub Docs · Using artifact attestations and reusable workflows — GitHub Docs · actions/upload-artifact README — GitHub

Sources and verification

Browse all resources