How to Route GitLab CI Builds Through Chainsaw

Intermediate 20 minutes DevOps / Platform Engineers Advanced Configuration

Configure GitLab CI to install packages through Chainsaw using a Service Token, masked CI/CD variables, and job-local package-manager configuration, with a merge-request gating example.

Overview

GitLab CI jobs run on shared or persistent runners, so generate package-manager configuration inside each job, read credentials from masked CI/CD variables, and avoid persistent global runner state. This page is the GitLab-CI-specific slice of How to Integrate Chainsaw with CI/CD Pipelines; see that guide for GitHub Actions and Jenkins and for the cross-cutting cache-invalidation and policy guidance.

Prerequisites

  • A running Chainsaw instance reachable from your GitLab runners
  • A Chainsaw client credential with Client Type set to Service Token
  • Repository scope set to only the ecosystems the pipeline needs
  • CHAINSAW_CLIENT_ID and CHAINSAW_CLIENT_SECRET defined as masked CI/CD variables (Settings → CI/CD → Variables)
Create separate service tokens for production, staging, and merge-request pipelines. This gives each pipeline isolated audit trails, policy scope, and rotation windows.
Invalidate the GitLab CI cache when switching to Chainsaw. Packages restored from a cache populated before Chainsaw was configured never pass through the firewall. Rotate the cache key once during onboarding and clear vendor/lock files in the first run.

npm

stages: [build]

variables:
  NPM_CONFIG_USERCONFIG: "$CI_PROJECT_DIR/.npmrc"

build_node:
  image: node:20
  stage: build
  before_script:
    - rm -rf node_modules package-lock.json
    - CHAINSAW_NPM_AUTH="$(printf '%s' "$CHAINSAW_CLIENT_ID:$CHAINSAW_CLIENT_SECRET" | base64)"
    - printf "registry=https://chain305.com/chainproxy/repository/@default/npmjs/\n//chain305.com/chainproxy/repository/@default/npmjs/:_auth=%s\n//chain305.com/chainproxy/repository/@default/npmjs/:always-auth=true\n" "$CHAINSAW_NPM_AUTH" > .npmrc
  script:
    - npm ci
    - npm test

pip

stages: [test]

variables:
  PIP_INDEX_URL: "https://chain305.com/chainproxy/repository/@default/pypi/simple/"
  PIP_RETRIES: "5"
  PIP_TIMEOUT: "60"

test_python:
  image: python:3.13
  stage: test
  before_script:
    - printf "machine chain305.com\n  login %s\n  password %s\n" "$CHAINSAW_CLIENT_ID" "$CHAINSAW_CLIENT_SECRET" > "$HOME/.netrc"
    - chmod 600 "$HOME/.netrc"
    - pip cache purge
  script:
    - python -m pip install -U pip
    - pip install -r requirements.txt
    - pytest -q

Maven

build_maven:
  image: maven:3.9-eclipse-temurin-21
  stage: build
  script:
    - rm -rf ~/.m2/repository
    - mkdir -p ~/.m2
    - |
      cat > ~/.m2/settings.xml <<'XML'
      <settings>
        <mirrors>
          <mirror>
            <id>chainsaw</id>
            <url>https://chain305.com/chainproxy/repository/@default/maven-central/</url>
            <mirrorOf>*</mirrorOf>
          </mirror>
        </mirrors>
        <servers>
          <server>
            <id>chainsaw</id>
            <username>${env.CHAINSAW_CLIENT_ID}</username>
            <password>${env.CHAINSAW_CLIENT_SECRET}</password>
          </server>
        </servers>
      </settings>
      XML
    - mvn -B verify

Docker / OCI

build_container:
  image: docker:24-cli
  services:
    - docker:24-dind
  before_script:
    - echo "$CHAINSAW_CLIENT_SECRET" | docker login chain305.com --username "$CHAINSAW_CLIENT_ID" --password-stdin
  script:
    - docker pull chain305.com/library/alpine:3.21

Gating Merge Requests

Run the install/build job on merge-request pipelines and make it a required pipeline so the merge is blocked when a package is denied. A policy block returns HTTP 403, the install step exits non-zero, and the merge request cannot merge.

stages: [firewall]

firewall_install:
  image: node:20
  stage: firewall
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
  before_script:
    - rm -rf node_modules package-lock.json
    - CHAINSAW_NPM_AUTH="$(printf '%s' "$CHAINSAW_CLIENT_ID:$CHAINSAW_CLIENT_SECRET" | base64)"
    - printf "registry=https://chain305.com/chainproxy/repository/@default/npmjs/\n//chain305.com/chainproxy/repository/@default/npmjs/:_auth=%s\n//chain305.com/chainproxy/repository/@default/npmjs/:always-auth=true\n" "$CHAINSAW_NPM_AUTH" > .npmrc
  script:
    # A policy block (e.g. CHW-2002 vulnerability, CHW-2004 typosquat)
    # returns HTTP 403, npm ci exits non-zero, and this job fails the MR.
    - npm ci

Then enable Pipelines must succeed under Settings → Merge requests so a failed firewall job blocks the merge. Scope a stricter policy to the merge-request service token so MRs are gated more aggressively than scheduled builds.

Use a dedicated merge-request service token so failing-pipeline noise and audit trails stay separate from production builds. See How to Create and Manage Client Credentials.

Verify Pipeline Activity

After the first run:

  1. Open Traffic in the Chainsaw dashboard.
  2. Filter by the service token’s Client ID.
  3. Confirm requests use the expected repository and outcome.
  4. Check the cache hit ratio after a warm run.

Next Steps