Enforce policies in CI/CD

Fail a CI/CD build when an SBOM breaks a policy, with a fail-build action and observer analyze --fail.

A policy violation fails a CI/CD build only when all three of these are true:

  1. The violation has the action fail-build. Only Rego and JavaScript policies can set it, on each violation they return; Visual Builder policies report violations without an action.
  2. The pipeline runs observer analyze --fail.
  3. The CLI can sign in to your namespace, so analyze evaluates your namespace's policies. Without that, analyze uses a temporary namespace that doesn't have your policies. The next section shows how to set it up.

If any is missing, violations are printed and the build continues. That is also the way to try out a new policy: run without --fail first and read the output.

Before starting, install Observer CLI in the pipeline.

Give the CLI access to your namespace

Observer CLI has no login command. It reads an access token from the environment variable OBSERVER_TOKEN, and the namespace from OBSERVER_NAMESPACE.

Create an access token

Sign in to SBOM Observer, open the user menu in the lower-left corner, select Access Tokens, and create a token in the namespace whose policies the pipeline should use. Copy it; it is shown only once.

Store it as a pipeline secret

Add the token to your CI/CD system as a secret, for example a repository secret named OBSERVER_TOKEN in GitHub Actions. Never commit it to the repository.

Pass it to the steps that run the CLI

Map the secret to the environment variable OBSERVER_TOKEN in every step that runs observer analyze or observer upload. If the namespace isn't called default, also set OBSERVER_NAMESPACE to its name, the part of the app URL after /workspace/. In GitHub Actions:

env:
  OBSERVER_TOKEN: ${{ secrets.OBSERVER_TOKEN }}
  OBSERVER_NAMESPACE: ${{ vars.OBSERVER_NAMESPACE }}

Other CI systems have the same mechanism under another name, such as masked variables or credentials. To try it in a terminal, run export OBSERVER_TOKEN=<token> first.

Write a policy that fails builds

This JavaScript policy returns a fail-build violation for each vulnerability with severity above 5 and EPSS above 0.5, unless a VEX analysis says it isn't exploitable:

// consider these VEX states as not "exploitable" and therefore not a violation
const nonExploitableVexStates = new Set([
  "not_affected",
  "false_positive",
  "resolved",
  "resolved_with_pedigree",
]);

function Policy({ component, vulnerabilities }) {
  if (vulnerabilities && vulnerabilities.length > 0) {
    return vulnerabilities
      .filter((vulnerability) => vulnerability.severity > 5)
      .filter((vulnerability) => vulnerability.epss > 0.5)
      .filter(
        (vulnerability) =>
          !vulnerability.vex ||
          !nonExploitableVexStates.has(vulnerability.vex.state),
      )
      .map((vulnerability) => ({
        severity: vulnerability.severity,
        message: `${vulnerability.vendorId} with severity > 5 (${vulnerability.severity}) and EPSS > 0.5 (${vulnerability.epss}) is not tolerated for a library`,
        action: "fail-build",
      }));
  }

  return null;
}

Create it under Policies with scope Components. See Write and test policies for the Rego form and testing.

Run it in the pipeline

Generate the SBOM

observer fs -o sbom.cdx.json .

Verify it (optional)

observer verify sbom.cdx.json --artifacts ./dist

Checks the SBOM is well-formed; --artifacts also checks it against the built files.

Analyze with --fail

observer analyze --fail sbom.cdx.json

Exits with code 1 when a fail-build violation is returned, which fails the pipeline step.

Upload

observer upload sbom.cdx.json

Run this only when the previous step passed, so the namespace holds SBOMs of builds that passed. Add retention flags to archive older versions.

For a complete GitHub Actions workflow with these steps, see CI/CD integration.

Next steps