6 minutes read

Playbook: GitLab pipelines

A start-to-finish guide for running Offing in a GitLab pipeline: add the component, read what it reports, gate merges on severity, accept known findings with a baseline, and run it on a self-managed instance. For the component's inputs, permissions, and network behavior in full, see GITLAB.md. For fixing a finding by hand or with an agent, see cli.md and agent.md.

Before you start

  • Find the GitLab version of your instance. CI/CD components and $[[ inputs.* ]] interpolation need a release that supports them. On GitLab.com they are available.
  • The component is not in the CI/CD Catalog yet. Host a copy of templates/offing.yml in a GitLab project of your own, on GitLab.com or on a self-managed instance, and reference it as $CI_SERVER_FQDN/<group>/<project>/offing@<version>. Section 5 covers the self-managed details.
  • Confirm the runner can pull a Node.js 22 or newer image and reach an npm registry that serves offing. The job needs both before the scan starts.
  • Run the scan once from a workstation before you gate on it, so you see the findings you are about to inherit. npx @velella/offing scan . --json is the same scan the job runs.

1. Add the job

Add the component to .gitlab-ci.yml:

include:
  - component: $CI_SERVER_FQDN/<group>/<project>/offing@<version>
    inputs:
      fail_on_severity: high

Pin <version> to a release. The component's version input decides which offing package the job runs, and it defaults to the version the component shipped with. Pinning the component and leaving the default in place keeps the rules you tested and the rules the pipeline uses the same. To change the rules a pipeline uses, change the version, not the data: the job does not run offing data update.

The job runs in the test stage by default. Set stage if your pipeline names its stages differently.

2. Read the result

The job writes two files and keeps them as artifacts for 14 days, even when the gate fails:

File Input that names it What it is for
offing-report.json report_name The full JSON report. Use it for audits and scripts.
gl-code-quality-report.json code_quality_report_name The GitLab Code Quality report. GitLab shows its findings in the merge request.

Open the merge request's Code Quality widget first. Each finding names its rule and file. Then open the job log: the human-readable summary lists the same findings with their replacements.

To reproduce a result locally, use the same version and gate as the job:

npx --yes @velella/offing@<version> scan . --fail-on high

A local run and a pipeline run disagree only when the repository, the version, or the gate differs. If they disagree, compare the three before you assume a bug.

3. Gate the merge on severity

fail_on_severity is the lowest severity that fails the job. It defaults to high. The options are none, info, low, medium, high, and critical.

Start with the default. Move to high once the existing findings are either fixed or accepted in a baseline. Moving to medium or lower fails on findings a team may not yet have a plan for, so do it only after the backlog is clear.

When the job fails, the exit status is the gate's, and the reports are still there. Read the Code Quality widget, then pick the path:

  • Fix it by hand. Follow cli.md from the explain step.
  • Hand it to an agent. Follow agent.md: one finding per branch, verified by the project's tests and a fresh scan.
  • It is wrong. Do not work around it. Produce the report with offing report-issue '<finding-id>' . --output issue.md and file it.

4. Accept known findings with a baseline

A baseline is a file committed to the repository that lists findings you have accepted, each with an expiry date. Set baseline to its path relative to path:

include:
  - component: $CI_SERVER_FQDN/<group>/<project>/offing@<version>
    inputs:
      baseline: .offing-baseline.json
      baseline_warn_days: "14"
      on_expired: fail
  • baseline_warn_days warns when a suppression expires within that many days.
  • on_expired decides what happens after the date. resurface reports the finding again. fail also fails the job, so an accepted risk cannot quietly stay accepted past its date.

The baseline format and expiry behavior are the same as in the GitHub Action. Review every baseline change in the merge request like any other code change: a new suppression is an accepted risk, and it should carry a reason and a date a person chose.

To warn before a finding's support ends, set min_support_days. Leave it empty to report only what the data already marks.

5. Run it on a self-managed instance

Self-managed runners often cannot reach Docker Hub or the public npm registry. Work through these, in order:

  1. Host the component. Copy templates/offing.yml into a project on your instance, then reference it as in step 1, with $CI_SERVER_FQDN/<group>/<project>/offing@<version>.
  2. Mirror the image. Copy a Node.js 22 or newer image into your registry and set the image input to it.
  3. Point npm at your registry. Set NPM_CONFIG_REGISTRY as a CI/CD variable so npx finds offing at the pinned version on your proxy.
  4. Or bake the package in. On an air-gapped runner, put offing in the image and set version to match. The job's npx --yes call then uses the local copy. The scan itself never needed the network.

Keep the component version and the package version identical. A mismatch runs a rule set you did not choose.

Failure modes

Symptom Likely cause What to do
Job fails before any scan output The runner cannot pull image, or npx cannot download the package Mirror the image or configure the npm registry. Step 5.
No report artifact after a failed job The job stopped before the scan ran, so nothing was written Read the job log from the top. Reports exist only once the scan has run.
The job passes, but a .gitlab-ci.yml image is end-of-life Image values in CI files are reported as unclassified, never as findings Treat it as unknown, not safe. Check the image with offing check or read COMPATIBILITY.md.
A baseline entry does not suppress a finding The baseline path is relative to path, not the repository root, or the finding's rule or location changed Check the path in the job log. Re-run the scan to get the current finding ID.
A suppression fails the job after its date on_expired: fail is working as designed Fix the finding, or extend the suppression with a reason in a reviewed merge request.

Privacy boundaries

  • The job uses no access token, project or group variable, or secret, and makes no GitLab API call. It never edits the repository or opens a merge request.
  • The scan makes no network requests. The only network use is the image pull and the npm download, and neither carries repository contents.
  • The reports leave the runner only as job artifacts. They name the files and lines where findings occur, so treat them as project metadata: anyone who can read the job's artifacts can read them. Check the project's visibility settings before you rely on the report staying private.
  • Artifacts expire after 14 days. Download anything you need to keep.

Check it works

Before you rely on the gate, run the job against a repository with a known finding, such as a dependency on a package or runtime that is past end of life. Then confirm two things:

  • The Code Quality widget in the merge request lists that finding.
  • The job's exit status matches the gate: it fails when a finding is at or above fail_on_severity, and passes when none is.
View Markdown

Use with an agent
View Markdown

Open in