The key

Add TRUSTIVAN_API_KEY as a project CI/CD variable, masked and protected. Masked keeps it out of the job log. Protected is the one that actually matters: an unprotected variable is readable by anyone who can open a merge request from a branch, which on most projects is everyone. Scopes: scans:create, scans:read, verdicts:request. Nothing else.

Variables

Exit codes

The job fails on both 1 and 2, but they mean different things: On 2 the job log says so explicitly, so an outage does not read as a wave of new vulnerabilities.

Scanning the repository instead of an image

A private project needs a stored git credential for the GitLab host — a deploy token is the right shape. See Scan a private repository. The pipeline’s own CI_JOB_TOKEN is not used: it lives for the job, and a scan that outlives it could not re-fetch.

Air-gapped runners

The template downloads the CLI from GitHub releases. A runner without that route should use the backend image, which carries the same binary at /app/trustivan: