CI gates: build, IaC and registry¶
Three commands gate a pipeline before something is published or deployed. Each writes a
JSON receipt, prints a verdict and exits 0 (passed), 1 (blocked) or 2 (incomplete or
error). Treat 2 as a failure, never as a pass.
None of them run your workload, start a container, call a cloud API on your behalf or upload anything. Sending the receipt to the console is a separate, authenticated step.
Build scan¶
Run untrust buildscan after the artifact is built:
untrust buildscan \
--artifact dist/rootfs.tar --target container \
--commit "$GIT_SHA" --run-id "$RUN_ID" \
--fail-on high \
--output dist/build-report.json --sarif dist/build-report.sarif
--target |
--artifact |
|---|---|
container, gcp, nvidia |
An uncompressed, flattened root-filesystem tar, for example from docker export of a stopped container. Not a docker save archive or an OCI layout. |
nitro |
The enclave image file (EIF) from nitro-cli build-enclave. |
vm-snp, vm-tdx |
A tar of the VM's boot artifacts plus a manifest.json ("schema": "untrust.vm-bundle/v1") naming the firmware and, optionally, kernel, initrd, command line, root filesystem and the expected launch measurement. |
To produce a root-filesystem tar from an image your build already created:
container_id=$(docker create "$BUILT_IMAGE")
docker export --output dist/rootfs.tar "$container_id"
docker rm -v "$container_id"
What it checks: source and configuration in the artifact (Python, shell, Ruby, JavaScript and Go files, service units, Dockerfiles, sensitive file names) against the build rules. For VM bundles it also checks the firmware, the measured kernel command line, root-filesystem integrity, baked-in SSH keys and passwords, and computes the launch measurement. It does not verify signatures, run the workload, scan dependencies for CVEs or inspect model weights.
Useful options:
--fail-onsets the lowest severity that blocks (critical,high(default),medium,low,info).--image <registry>/<repo>@sha256:<digest>records the image digest your registry reported, so the console can join this build to the registry artifact.--custom-rules rules.jsontests draft custom rules locally. See Custom rules.
Commit and run labels are supplied by the caller. They are labels, not proof of what was scanned.
Limits¶
Source files up to 8 MB, selected content and decompressed ramdisks up to 512 MB, 50,000
entries, EIF input up to 1 GB, root-filesystem tar up to 8 GB, and a 120-second analysis
deadline by default (--timeout, at most 600). Anything over a limit returns ERROR;
nothing is silently treated as clean.
IaC scan¶
Run untrust iacscan on the infrastructure plan before it is applied:
terraform show -json plan.tfplan > plan.json
untrust iacscan --plan plan.json \
--commit "$GIT_SHA" --run-id "$RUN_ID" \
--fail-on high --output iac-report.json
The format is detected automatically: Terraform plan JSON, CloudFormation, Azure ARM,
Azure Bicep, GCP Deployment Manager or a Kubernetes manifest. The input must be a regular
UTF-8 file of at most 2 MB. --source-dir adds a best-effort file:line to each finding by
searching your configuration directory as text. Nothing is executed.
A Bicep parameter or other expression is not evaluated, so a rule that depends on one is
not assessed. Resources that a rule cannot resolve from the plan (for example a security
group supplied by a module output) read SKIP, never PASS, and are left to the live scan.
The rule list is in the IaC rule reference.
Registry scan¶
untrust registry scan resolves each artifact to its digest and verifies it against a trust
policy: signed by a trusted identity, deployed by digest rather than a mutable tag, and,
for enclave images, the measurement your key policy expects.
# With a local trust policy file
untrust registry scan --policy registry-policy.json --output registry.json
# Or fetch the policy you keep in the console, using the CI identity
untrust registry scan --policy-url https://app.untrustsecurity.com/v1/ci-policy/<org>/<binding> \
--github-oidc --output registry.json
# Or inspect one image with keyless signing expectations
untrust registry scan --image <registry>/<repo>@sha256:<digest> \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity <workflow-identity> --json
OCI signature verification uses the cosign binary (on PATH, or UNTRUST_COSIGN).
Then:
untrust registry pins --registry registry.json --format kmsprints a Nitro KMS key policy condition that admits only the verified PCR0 values;--format wifprints the equivalent Confidential Space WIF condition onimage_digest.untrust registry bind --registry registry.json --runtime report.jsonchecks that the measurements a deployment runs belong to verified artifacts (REG-BIND-01).
Pull-request feedback¶
untrust ci-feedback posts a gate result back to the pull request. It is off unless you add
it.
| Provider | What is posted | Permission |
|---|---|---|
| GitHub | A Check Run with a summary and annotations on the PR head | checks: write |
| GitHub | SARIF to code scanning | security-events: write |
| GitLab | One merge-request note, edited on re-runs, and a commit status | A project access token with api scope in UNTRUST_GITLAB_TOKEN |
untrust ci-feedback --kind build --report dist/build-report.json \
--scanner-exit "$GATE_EXIT" --summary-file "$GITHUB_STEP_SUMMARY" --exit-with-verdict
--mode regression fails the job only on blocking findings that are new or worse than
--base-report, the base branch's last report. ERROR, armed custom-rule blocks and an
unusable base are never relaxed: they fall back to the full gate. Take the base report only
from trusted runs on the base branch, never from an artifact a pull request can write, and
never run this under pull_request_target.
--rule-settings applies your organization's rule levels (the JSON from
GET /v1/organizations/<org>/rule-overrides) so the pull-request check agrees with what
the console will compute. Load that file from the API at run time, not from the repository
being gated. --dry-run prints exactly what would be sent.
Tokens are read only from the job environment, sent only in a request header and never printed. Redirects are refused.
Rule levels¶
Administrators can set each built-in build or IaC rule to block, warn, report or
off, change its severity, and pick a profile: standard (default), audit (every
non-floor rule reports only) or strict (blocks at medium and above). Every per-rule
setting needs a reason and an expiry of at most 365 days. Findings are never dropped: a
level changes the gate verdict only, and the receipt records the verdict before and after
levels were applied. Floor rules, the attestation-critical ones, always block.
Recommended pipeline shape¶
- Build the artifact.
- Run
untrust buildscan(anduntrust iacscanon the plan). - Keep the JSON receipts as job artifacts, even when the gate fails.
- Send the receipts to the console with the pipeline's OIDC identity.
- Publish or deploy only when the gate job succeeded. Do not add
continue-on-errorto the gate.
The console's pipeline setup generates GitHub Actions and GitLab CI configuration for each target, artifact path and threshold. Download it and merge it into your pipeline. For GitLab step by step, and for Jenkins, Buildkite and other systems, see CI systems.