Skip to content

CI systems: GitLab and others

The CI gates are plain commands with exit codes, so they run in any CI system. Pull request feedback and uploading evidence to the console depend on the system.

What works where

Gates (buildscan, iacscan, registry scan) PR or MR feedback (ci-feedback) Upload evidence to the console Configuration generated by the console
GitHub Actions on GitHub.com Yes Check Run, annotations, code scanning Yes, with the job's OIDC token Yes
GitLab CI/CD on GitLab.com Yes MR note and commit status Yes, with the job's OIDC token Yes
Self-managed GitLab Yes MR note and commit status No No
GitHub Enterprise Server Yes Check Run, if GITHUB_API_URL points at your server No No
Jenkins, Buildkite, CircleCI, Azure Pipelines, Bitbucket Pipelines and others Yes No No No

Uploads are accepted only with an OIDC token from one of two issuers: https://token.actions.githubusercontent.com and https://gitlab.com. The service has no API-key route for CI evidence. From any other system, keep the receipts as build artifacts and, if you want them in the console, import the JSON on the Build pipeline page. An imported receipt has no pipeline identity behind it.

Run metadata (trigger, branch, base branch and run URL) is recorded only on GitHub Actions and GitLab CI/CD. On other systems a receipt carries just the commit and run ID you pass.

GitLab CI/CD

1. The gate job

Install the CLI in the job from the wheel you were given (see Install), run the gate and keep the receipt even when the gate fails:

untrust_build_gate:
  stage: test
  image: python:3.12-slim
  timeout: 10m
  allow_failure: false
  before_script:
    - python -m pip install ./vendor/untrust-<version>-py3-none-any.whl
  script:
    - >
      untrust buildscan
      --artifact dist/rootfs.tar --target container
      --commit "$CI_COMMIT_SHA" --run-id "$CI_PIPELINE_ID"
      --fail-on high
      --output .untrust/build.json --sarif .untrust/build.sarif
  artifacts:
    when: always
    expire_in: 1 week
    paths:
      - .untrust/build.json
      - .untrust/build.sarif
  • --commit must be the full commit SHA, so use $CI_COMMIT_SHA, not a short SHA.
  • --run-id must be the plain $CI_PIPELINE_ID. The service compares it with the pipeline_id in the OIDC token when the receipt is uploaded.

For an IaC gate, swap the command for untrust iacscan --plan plan.json ... --output .untrust/iac.json.

2. Merge request feedback

To post one merge request note (edited on re-runs) and a commit status, create a project access token with the api scope and the Reporter role. CI_JOB_TOKEN can't write notes or statuses. Store it as a masked, protected CI/CD variable named UNTRUST_GITLAB_TOKEN, and keep it out of the scanner's environment:

  script:
    - |
      set +e
      env -u UNTRUST_GITLAB_TOKEN untrust buildscan \
        --artifact dist/rootfs.tar --target container \
        --commit "$CI_COMMIT_SHA" --run-id "$CI_PIPELINE_ID" \
        --fail-on high --output .untrust/build.json --sarif .untrust/build.sarif
      rc=$?
      set -e
      untrust ci-feedback --kind build --report .untrust/build.json \
        --provider gitlab --scanner-exit "$rc" --exit-with-verdict

With --exit-with-verdict, ci-feedback becomes the job's gate and exits 0, 1 or 2 for pass, fail or error.

For regression mode, add --mode regression --fetch-base. The base report is the last report of the same job on the target branch, fetched with CI_JOB_TOKEN. If it can't be fetched, the full gate applies. An ERROR is never relaxed.

3. Upload the receipt

Register a CI binding for the project first. Then set two protected CI/CD variables from the saved binding, UNTRUST_SUBMIT_ENDPOINT and UNTRUST_CI_AUDIENCE, and add a delivery job that asks GitLab for an ID token with that audience:

untrust_deliver:
  stage: deploy
  needs: [untrust_build_gate]
  image: python:3.12-slim
  timeout: 10m
  id_tokens:
    UNTRUST_CI_ID_TOKEN:
      aud: $UNTRUST_CI_AUDIENCE
  before_script:
    - python -m pip install ./vendor/untrust-<version>-py3-none-any.whl
  script:
    - python -m untrust.enterprise_ci_client --endpoint "$UNTRUST_SUBMIT_ENDPOINT" --build .untrust/build.json

The client reads the token from UNTRUST_CI_ID_TOKEN. Add --iac .untrust/iac.json for an IaC receipt.

A binding accepts runs from:

  • a branch push (refs/heads/...);
  • a run started from the web UI;
  • a scheduled pipeline.

It doesn't accept merge request pipelines or tag pipelines.

The binding pins:

  • the project and namespace IDs;
  • the branch;
  • the trigger;
  • the pipeline configuration file and its commit.

Copy the subject GitLab issues, for example project_path:<group>/<project>:ref_type:branch:ref:<branch>, rather than typing it.

The console's pipeline setup can generate this whole configuration for GitLab.com, including the job templates, for build targets container, nitro, gcp and nvidia, and for IaC.

Other CI systems

On Jenkins, Buildkite, CircleCI, Azure Pipelines, Bitbucket Pipelines or anything else, run the same commands and gate on the exit code:

untrust buildscan \
  --artifact dist/rootfs.tar --target container \
  --commit "$(git rev-parse HEAD)" --run-id "$BUILD_ID" \
  --fail-on high --output build.json --sarif build.sarif
Exit code Meaning What the pipeline should do
0 Passed Continue.
1 Blocked by a finding at or above --fail-on Fail the build.
2 Incomplete or error Fail the build. Never treat it as a pass.

registry scan uses the same codes: 0 all verified, 1 a confirmed failure, 2 not assessable.

  • Pass the commit as a full SHA. The run ID can be any label of 1 to 200 characters, such as $BUILD_ID on Jenkins or $BUILDKITE_BUILD_ID on Buildkite.
  • Archive the JSON receipt and the SARIF file as build artifacts, even when the step fails. Many systems can display SARIF in their own code scanning view.
  • Run untrust ci-feedback --provider none --summary-file summary.md to get a Markdown summary of the result without posting anything.
  • Don't mark the gate step as allowed to fail.

See Environment variables for the variables the gates read, and Troubleshooting for common failures.