Run reports
Send job summaries, test results, attestations and artifacts from GitHub Actions, GitLab CI, Bitbucket Pipelines, Azure Pipelines and any other CI system to Prodgator
The Prodgator report action for GitHub Actions, the Prodgator component for GitLab CI, the Prodgator pipe for Bitbucket Pipelines and the Prodgator task for Azure Pipelines each send a job's summary, test results, coverage, SBOMs, scan results, custom evidence and artifacts to Prodgator. The first three authenticate with their CI system's OIDC token; Azure Pipelines uses the pipeline's job token. No API key to create or rotate.
Other CI systems (CircleCI, Buildkite, Jenkins and more) use the command line with an OIDC trust or an API key. That data is self-reported. See Any CI.
Why
GitHub does not expose a job's through any API. The only way to get it, or test results and other evidence, into Prodgator is a step in the workflow that sends them directly. The action does that.
GitHub Actions
Add and a step that runs after your tests and build:
permissions:
id-token: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test
- run: npm run build
- uses: prodgator/prodgator-action@v1
if: always()
with:
junit: 'reports/**/*.xml'
artifacts: 'dist/**'Action inputs
| Input | Default | Description |
|---|---|---|
| (none) | Markdown to send. Default: what earlier steps of the job wrote to . | |
| (none) | Path to a markdown file in the workspace to send instead. | |
| Report name within the job. Give each step its own name to send several reports from one job. | ||
| Job identifier. | ||
| (none) | In matrix jobs, pass so each leg keeps its own report. | |
| (none) | Globs of JUnit XML files. Matched files are summed into one attestation. | |
| (none) | The name of the attestation the matched files are summed into. Default: . See Test results and source paths. | |
| (none) | Globs of files to upload, newline or comma separated. | |
| (none) | One attestation or an array, as inline JSON or the path of a JSON file. See Attestation kinds and status and Send a custom attestation. | |
| (none) | Path of a file or a Prodgator ownership JSON file. See Ownership reports. | |
| Prodgator API URL. | ||
| the origin of the URL in use | OIDC audience. Set it only if you need something other than the API origin. | |
| Fail the step when the report cannot be sent, instead of warning and continuing. |
Outputs: (the Prodgator report id) and (a link to the run in Prodgator). These are the only inputs: an attestation's kind, name, status and data go in the JSON.
sends the report even when an earlier step fails, so a broken build still shows its test results. A or attestation can also name the scanner's own output file (for example a SARIF file); the action uploads it with the report and links it from the attestation. See the action README for every input, all six attestation kinds, matrix jobs and troubleshooting.
GitLab CI
Include the Prodgator component. It adds a job that runs after every other stage, even when a job fails, with the artifacts of the earlier jobs:
include:
- component: gitlab.com/prodgator/prodgator-component/report@1
inputs:
junit: test-results/**/*.xml
artifacts: dist/*.tgz
attestations: attestations.jsonMake the files you report job artifacts (). For a summary, append Markdown to in a job and keep it as an artifact. The project's top-level group must be connected to Prodgator. See the component README for every input and for reporting from inside a job with .
Bitbucket Pipelines
Give the step an OIDC token for the Prodgator API and run the pipe in , which also runs when the step fails:
- step:
name: Test
oidc:
audiences:
- https://api.prodgator.io
script:
- npm test
after-script:
- pipe: prodgator/prodgator-pipe:1.0.0
variables:
JUNIT: 'test-results/**/*.xml'The workspace must be connected to Prodgator. Prodgator names the report after the step. See the pipe README for every variable.
Both the GitLab component and the Bitbucket pipe run the public image at their own version. Each version is tagged , and . The image holds the same CLI as the npm package . If your runners can only pull from an internal registry, mirror that image there, or run inside a job instead.
Azure Pipelines
Azure DevOps has no OIDC token Prodgator can verify, so the report is authenticated with the pipeline's own job token (). Prodgator checks the token with Azure DevOps once to confirm which organization, project and build it belongs to, and discards it. The organization must be connected to Prodgator.
Install the Prodgator report extension from the Visual Studio Marketplace into your organization, then add the task after your tests. sends the report even when an earlier step fails:
# azure-pipelines.yml
- task: ProdgatorReport@1
condition: always()
inputs:
junit: 'test-results/**/*.xml'
artifacts: 'dist/*.tgz'Without the extension, run the CLI in a script step and map the job token yourself. The token is not in the step's environment unless you add it:
- script: npx --yes @prodgator/report --junit 'test-results/**/*.xml'
condition: always()
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)If the token is not mapped, the CLI stops and tells you to add the line. The public image holds the same CLI, for agents that cannot reach npm.
Do not use that image as an Azure Pipelines container job ( on a job). It is Alpine with an , which Azure DevOps cannot run its agent steps in. Run it from a script step on the agent instead, with the sources mounted and the variables the CLI reads passed through:
- script: |
docker run --rm \
-v "$(Build.SourcesDirectory):/work" -w /work \
-e TF_BUILD -e SYSTEM_ACCESSTOKEN -e SYSTEM_COLLECTIONURI -e SYSTEM_TEAMPROJECTID \
-e SYSTEM_JOBID -e SYSTEM_JOBATTEMPT -e SYSTEM_JOBDISPLAYNAME \
-e BUILD_BUILDID -e BUILD_SOURCEVERSION -e AGENT_NAME -e BUILD_SOURCESDIRECTORY=/work \
--entrypoint prodgator-report \
ghcr.io/prodgator/report:1.2.3 --junit 'test-results/**/*.xml'
condition: always()
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)Use a version tag from the image's tags (, or ). passes a variable from the step's environment into the container; is how the CLI knows it runs in Azure Pipelines.
Task inputs
| Input | Default | Description |
|---|---|---|
| (none) | Markdown to send. | |
| (none) | Path to a markdown file in the workspace to send instead. | |
| Report name within the job. Give each report from one job its own name. | ||
| the pipeline's job name | Name shown for the job. | |
| (none) | JSON object that tells matrix legs apart, for example . | |
| (none) | Globs of JUnit XML files, one per line, relative to the sources directory. Matched files are summed into one attestation. | |
| (none) | Merge every matched file into one attestation with this name. Default: . See Test results and source paths. | |
| (none) | Globs of files to upload with the report, one per line. | |
| (none) | Path to a JSON file with extra attestations. See Send a custom attestation. | |
| (none) | Comma separated variable names whose values become in the report. | |
| Prodgator API URL. | ||
| Off by default: the job token is only sent to and addresses. Turn it on only if your Prodgator runs on its own https address. | ||
| Fail the task when the report cannot be sent, instead of warning and continuing. |
( when you run yourself, in GitLab, Bitbucket and Azure Pipelines alike) covers a report that cannot be sent. A usage error, such as a mistyped command or an argument the command line does not take, always fails the step, whatever says.
The run's page in Prodgator shows the Summary and attestations once the report arrives.
Classic pipelines
A classic (designer) build pipeline sends the same report from one Command line task after your test tasks:
-
In the agent job settings, turn on Allow scripts to access the OAuth token.
-
Add a Command line task with this script:
npx --yes @prodgator/report --junit "test-results/**/*.xml" -
Under the task's Environment Variables, add with the value .
-
Under Control Options, set Run this task to Even if a previous task has failed.
Classic release pipelines are not supported: Prodgator does not read them, and a report from a release does not map to a run. Use YAML pipelines with environments for deployments.
If your pipeline has approvals
Send the report before the first step that waits for a person, so the run in Prodgator shows the summary and attestations while the approval is pending, and the AI release risk can read them. If the pipeline has no manual or approval-gated steps, the end of the pipeline is the right place and the examples above already do that. A report cannot include results of steps that run after the approval, such as the deployment itself.
Your CI system, not Prodgator, knows which steps wait, so you place the report yourself:
-
GitHub Actions: a job that uses an environment with required reviewers does not start until someone approves. Report from the build or test job, or from a job that the build and test jobs, never the deploy job.
jobs: build: runs-on: ubuntu-latest steps: - run: npm test - uses: prodgator/prodgator-action@v1 if: always() with: junit: 'reports/**/*.xml' deploy: needs: build runs-on: ubuntu-latest environment: production # required reviewers steps: - run: ./deploy.sh -
GitLab CI: the component's job runs in , which does not start while a manual or protected-environment job is blocking the pipeline. Add to the report job so it starts as soon as the jobs that make your files finish:
include: - component: gitlab.com/prodgator/prodgator-component/report@1 inputs: junit: test-results/**/*.xml prodgator-report: needs: [build, test] # not the gated deploy job deploy: stage: deploy environment: production # protected environment with approvers script: ./deploy.shThe job gets artifacts only from the jobs in . As an alternative, set the input to a stage that sits before the deploy stage in your list. The default stays because it is valid in every pipeline.
-
Bitbucket Pipelines: run the pipe in the of a step before the or permissioned deployment step, not in the gated step or after it.
- step: name: Build and test oidc: audiences: - https://api.prodgator.io script: - npm test after-script: - pipe: prodgator/prodgator-pipe:1.0.0 variables: JUNIT: 'test-results/**/*.xml' - step: name: Deploy deployment: production trigger: manual script: - ./deploy.sh -
Azure Pipelines: put the report in a job of a stage before the stage that deploys to a gated environment, not in the deployment job. A stage waits for the environment's approvals and checks, the Prodgator gate included, before it starts.
stages: - stage: Build jobs: - job: Test steps: - script: npm test - task: ProdgatorReport@1 condition: always() inputs: junit: 'test-results/**/*.xml' - stage: Deploy dependsOn: Build jobs: - deployment: Production environment: production strategy: runOnce: deploy: steps: - script: ./deploy.sh
Secrets in summaries
GitHub masks secrets in the step summaries it stores, and the action sends those masked copies. GitLab masked variables and Bitbucket secured variables are masked in job logs only: a file a job writes keeps what was written to it. The GitLab component and the Bitbucket pipe replace the values of the OIDC token, the GitLab job token family and every variable you list in (GitLab) or (Bitbucket) with in the summary and in attestations. Azure Pipelines secret variables are masked in logs only, the same way. The Prodgator task and CLI replace the job token and every variable you list in with . Artifact files are sent as they are: do not upload files that contain secrets.
Files outside the workspace
The action, component, pipe, task and CLI read files only from the job's workspace (the checkout directory): the summary file, the attestations file, JUnit files, scanner files, the ownership file and artifacts. A file whose real path is somewhere else, such as a symlink in the repository that points at a file on the runner, is skipped with a warning and the report is sent without it. An attestations file that is skipped stops the report the same way a missing one does. Nothing under a directory is read. Write a summary file inside the workspace, not in the runner's temp directory: an absolute path outside the workspace is skipped too.
What appears where
- The pipeline card and the pipeline detail page show a Summary section with the job's markdown summary and its attestations.
- A deployment's approval card shows the same attestations, plus any recorded for the release's commit from other runs, in an evidence row.
Attestation kinds and status
Prodgator derives the status of every attestation kind except , so a report can't claim while its own numbers say otherwise:
| Kind | when | when |
|---|---|---|
| no failed or errored tests | any failed or errored test | |
| , or with no threshold set | ||
| always, unless the input says | the input says | |
| no count at or above (default ) is greater than zero, or when is | a count at or above is greater than zero | |
| the input's own | the input's own | |
| SLSA provenance with SHA-256 subjects and source commit and repository matching the authenticated run | missing subjects or a missing or different source identity | |
| always | never |
When a attestation names a SARIF file and gives no counts, the report client counts the file the way Prodgator reads its findings, so the attestation and the Security page agree: the score on the result, else on its rule (rules inside included, as ASH writes them), else the ( is high, medium, low). Suppressed results (every suppression with no status or status accepted) go in a separate count, which is shown in the attestation headline and never changes the status. Results of kind , or , and results with , are not counted, and a result repeated across runs counts once.
Each attestation is . An optional names the file the result came from (workspace-relative, up to 200 characters; the report is rejected if it is absolute or leaves the workspace). is required for and ignored for the kinds Prodgator derives. To bring an outside check such as a ServiceNow change ticket into a release policy, see Send a custom attestation.
Test results and source paths
Every matched JUnit file is summed into one attestation, so a failing file always counts in the totals. Its name is the setting when you give one, otherwise , so a release policy that names the attestation keeps working. To choose the name yourself, set:
| Where | Setting |
|---|---|
| GitHub Action | |
| GitLab component | |
| Bitbucket pipe | |
| Azure Pipelines task | |
| CLI | , or the environment variable |
Every attestation can carry a source path: where it was read from, relative to the workspace. For JUnit it is the pattern when you passed one, otherwise the file when only one was read, and is left out when several files were read by several patterns. It is never an absolute path, never contains , and is at most 200 characters. A request with an invalid source path is rejected. The path is kept with the attestation but is not part of the data policies read.
The source path shows in the Details line of a report in the provenance tree and in the tooltip of an attestation badge on the pipeline and deployment pages. Attestations sent before this was added show their name and job name only.
Build provenance reports
Send an bundle with in the input. Copy the bundle into the workspace first if the action wrote it outside the checkout. The client also accepts a bare DSSE envelope or an in-toto statement, as JSON no larger than 1 MiB. A Sigstore bundle can be at most 64 KiB, and the report stops with an error when it is larger. Unreadable files, paths outside the workspace and malformed statements stop the report.
The client sends only a summary: SLSA predicate type (v1 or v0.2), up to 64 named SHA-256 subjects, source repository, full source commit, source ref, workflow path, builder id and invocation id, together with the Sigstore bundle itself. Other producers may supply that summary directly using , (each with and ), , , , , and , and the bundle as . A summary with no bundle is stored, unverified. A value sent by a client is ignored. Subject names allow 200 characters, repository, builder and invocation strings allow 2,000, and ref and workflow path allow 1,000.
The server ignores a supplied status and compares the source against the authenticated report's commit and repository. When the bundle verifies, it rebuilds the summary from the signed statement first. Missing or different source identities are stored as , with set to or . URL and owner/name repository forms are accepted; comparisons ignore case. Empty subjects also fail.
The run's existing trust rules still apply: a verified signature does not make a fork or report trusted.
Signature checks
A provenance attestation counts as signed only when Prodgator verified the Sigstore bundle itself. The report client sends the bundle that wrote (at most 64 KiB), and Prodgator checks it offline against pinned trust roots, with no outside request:
- the signature over the in-toto statement, made with the key in the signing certificate
- the certificate chain up to the Fulcio certificate authority of the trust root
- a trusted signing time: a Rekor transparency log entry for the public Sigstore instance, or GitHub's timestamp authority for repositories that sign with GitHub's own root. The certificate must have been valid at that time
- the certificate issuer is GitHub Actions ()
- the source repository in the certificate is the repository of the run that sent the report, and its repository and owner ids are the ids in the run's sign-in token
- the source commit in the certificate is the commit the attestation is filed under, and agrees with the commit named in the statement. For a run the attestation is filed under the pull request head while GitHub builds and signs a temporary merge commit (): the commit in the run's sign-in token is accepted too, and the verified result keeps the commit the certificate names
- the statement is SLSA provenance and every subject has a SHA-256 digest
- the builder id agrees with the certificate. The workflow writes the builder id itself, so an id that is a workflow URL must be the workflow the certificate names, and an id or that names a runner type ( or ) must be the certificate's runner type
A verified attestation reports a builder id only when the certificate proves it: the signer workflow URL (, what writes), or exactly or matching the certificate's runner type. Any other id is not trusted: the attestation still verifies, but is null and the id is stored as , so a rule with builders fails. A signer workflow URL says nothing about the runner type; read in custom Rego to require one.
The subjects, source commit, workflow, builder id and invocation id shown for a verified attestation come from the signed statement, not from what the client sent. The client's own field is ignored. The result is stored with the attestation as : is (with the signer workflow, trust root, signing time, the certificate's runner type and whether the certificate was issued to the run that sent the report) or (with a code and a ). A bundle another run signed for the same commit, such as a separate release workflow, still verifies; policies can tell it apart with .
On a provenance attestation that is not verified, policies see the fields the client wrote under other names (, , , ), and is always the server's result, so custom Rego cannot mistake a client's claim for a checked value.
Private repositories sign with GitHub's trust root, public repositories with the public Sigstore instance. Both roots are built into Prodgator. GitHub Enterprise Cloud with data residency signs with an issuer of its own (), which Prodgator does not support yet: those attestations stay unverified with the reason "the certificate was not issued to a GitHub Actions workflow".
The status of the attestation (pass or fail) still depends only on the subjects, commit and repository. Whether the signature verified is a separate result, and a policy decides what to do with it: the Build provenance rule has a Require a verified signature setting.
Why a signature is not verified
| Reason shown | What it means |
|---|---|
| no Sigstore bundle was sent | The report carried a summary, a bare envelope or a statement with no signature material. It is stored, unverified. |
| signatures are checked for GitHub Actions runs only | The run is on another provider. |
| the Sigstore bundle is larger than 64 KiB | Prodgator does not check larger bundles. |
| the Sigstore bundle is not valid, the signed payload is not an in-toto statement, the bundle has no signing certificate | The bundle is not a Sigstore bundle of a signed statement. |
| the signing certificate is not from Sigstore or GitHub | The certificate was issued by another authority. |
| the signing certificate does not chain to a trusted authority | The chain does not lead to a trusted certificate authority. |
| the signing certificate was not valid when it signed | The signing time falls outside the certificate's validity. |
| the signature does not match the signed statement | The statement was changed after signing. |
| no trusted timestamp shows when it was signed, the signing timestamp does not verify | The bundle has no verifiable timestamp. |
| no transparency log entry was sent, the transparency log entry does not verify | The public Sigstore entry is missing or does not match. |
| the certificate was not issued to a GitHub Actions workflow | The issuer is not GitHub Actions, for example a data residency issuer. |
| the certificate does not name a GitHub workflow and source repository | The certificate identity is incomplete. |
| it was signed for a different repository, it was signed for a different commit | The signed identity does not match the run or the commit of the report. |
| it was signed for a different commit than the run's code | A run sent a bundle whose certificate names neither the pull request head nor the merge commit the run built. |
| the builder id in the statement does not match the signing certificate | The statement names another workflow as its builder, or a runner type that is not the one in the certificate. |
| the signed statement is not SLSA provenance, a signed subject has no sha256 digest | The statement does not have the expected form. |
| trust root out of date: refresh needed | The bundle was signed with a key that Sigstore or GitHub announced and this Prodgator release does not hold yet. Send the report again after the next release. A key nobody announced is reported as not from Sigstore or GitHub, or as a timestamp or log entry that does not verify. |
| the signature could not be checked | An unexpected error. Send the report again. |
| it was recorded before signatures were checked | The attestation was stored before Prodgator verified signatures. |
A bundle may hold at most 64 KiB and the whole provenance attestation at most 96 KiB (other attestation kinds keep 32 KiB). A statement can name up to 64 subjects.
Ownership reports
Code owners rules on pull requests read your file from an ownership report. Send it with the input of the GitHub Action, from a workflow that runs on pushes to your base branches:
on:
push:
branches: [main]
jobs:
ownership:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- uses: prodgator/prodgator-action@v1
with:
ownership: .github/CODEOWNERSThe action uploads the file as an artifact of the report and adds an attestation with (, or for a file) and (the file's path). Once the upload completes, Prodgator reads the file and stores it for the commit. For a push to a branch it also becomes the branch's latest report, and open pull requests into that branch are evaluated again (up to 20, most recently updated first).
The file can be at most 256 KB with 5,000 rules. Owners are or ; email owners are skipped. When the file cannot be read, the report shows a warning such as and nothing is stored. A file uses this format:
{ "version": 1, "rules": [{ "pattern": "infra/**", "owners": ["@acme/platform"] }] }Untrusted sources
A report from a or event, or from a whose head repository is a fork, is stored but marked untrusted. These contexts run with the base repository's identity while commonly processing fork-supplied data. On GitLab, a merge request pipeline whose source project is a fork, and external pull request pipelines, are marked untrusted. Bitbucket does not run pull request pipelines for forks in the target repository, so Bitbucket reports are trusted. The attestations rule of a release policy counts only trusted attestations by default; whoever edits the policy can turn that off per rule.
Reports from other CI systems
Reports sent from CircleCI, Buildkite, Jenkins or another CI system through Any CI are self-reported, not trusted. A policy ignores them unless its rule sets Count self-reported results from other CI (not verified), and even then it uses one only when the commit has no matching result from a connected provider. Prodgator refuses build provenance and ownership reports from other CI systems.
Limits by plan
| Free | Team | Business | Enterprise | |
|---|---|---|---|---|
| Reports per run | 25 | 100 | 250 | 1,000 |
| Largest artifact file | 25 MB | 250 MB | 1 GB | 5 GB |
| Artifact bytes per run | 100 MB | 1 GB | 5 GB | 20 GB |
| Upload bytes per organization per month | 2 GB | 50 GB | 250 GB | unlimited |
Every plan is also capped at 20 attestations and 50 artifacts per report, and 300 report requests per minute per organization.
Retention
Reports, attestations and artifacts follow your plan's data retention, the same as pipeline runs: 7 days on Free, 30 on Team, 90 on Business, 365 on Enterprise. Moving to a shorter retention does not shorten data already written; it expires on the old schedule.
Learn more
- Any CI: run, report and gate commands for CircleCI, Buildkite, Jenkins and other CI systems.
- Action README: every input, all attestation kinds with examples, matrix jobs and troubleshooting.
- Component README: every input for GitLab CI.
- Pipe README: every variable for Bitbucket Pipelines.
- Send a custom attestation: check ServiceNow, Jira or a release window in CI and require the result in a policy.
- Deployment Approvals: where attestations show up on a pending deployment.
- Release Policies: require passing attestations before a deployment is approved.
Approve from the Pipelines list
Approve or reject a run that waits on a deployment approval without leaving the Pipelines page.
Any CI
Record runs, send reports and attestations, and hold a release for approval from CircleCI, Buildkite, Jenkins or any other CI system, with the prodgator-report command line.