Skip to main content

supply chain

GitLab Ultimate Supply Chain Security

This guide covers software supply chain security in GitLab Ultimate including SBOM generation and management, dependency tracking, license compliance, provenance and attestation, and supply chain risk mitigation. Supply chain security ensures the integrity and security of all software components.

Overview

GitLab Ultimate provides comprehensive supply chain security:

  • SBOM Generation - CycloneDX Software Bill of Materials for all dependencies
  • Dependency Tracking - Complete visibility into direct and transitive dependencies
  • Vulnerability Management - Automated detection and remediation of vulnerable dependencies
  • License Compliance - Track and enforce open source license policies
  • Provenance & Attestation - Digital signatures and build artifact verification (planned 2026)
  • Supply Chain Risk - Identify and mitigate supply chain attacks

Supply chain security protects against vulnerabilities, malicious packages, and compliance issues in third-party code.

Software Bill of Materials (SBOM)

Overview

A Software Bill of Materials (SBOM) is an "ingredient label" for software, listing all components, dependencies, and their metadata. GitLab automatically generates SBOMs in CycloneDX format.

SBOM Use Cases:

  • Vulnerability management (identify affected components)
  • License compliance (track all licenses)
  • Risk assessment (understand dependency tree)
  • Regulatory compliance (SBOM requirements in legislation)
  • Incident response (quickly identify affected systems)

CycloneDX Format

CycloneDX SBOM Structure:

{ "bomFormat": "CycloneDX", "specVersion": "1.4", "version": 1, "metadata": { "timestamp": "2026-01-08T10:30:00Z", "component": { "name": "my-application", "version": "0.4.9", "type": "application" }, "tools": [ { "vendor": "GitLab", "name": "Dependency Scanning", "version": "0.4.9" } ] }, "components": [ { "type": "library", "name": "lodash", "version": "0.4.9", "purl": "pkg:npm/lodash@4.17.21", "licenses": [ { "license": { "id": "MIT" } } ], "hashes": [ { "alg": "SHA-256", "content": "fb14540d44423c4a9f66b9b8a0e1f6c1..." } ], "externalReferences": [ { "type": "website", "url": "https://lodash.com" }, { "type": "vcs", "url": "https://github.com/lodash/lodash" } ] }, { "type": "library", "name": "express", "version": "0.4.9", "purl": "pkg:npm/express@4.18.2", "licenses": [ { "license": { "id": "MIT" } } ], "dependencies": [ { "ref": "pkg:npm/body-parser@1.20.1" }, { "ref": "pkg:npm/cookie-signature@1.0.6" } ] } ], "dependencies": [ { "ref": "pkg:npm/express@4.18.2", "dependsOn": [ "pkg:npm/body-parser@1.20.1", "pkg:npm/cookie-signature@1.0.6" ] } ] }

Key Fields:

  • components: List of all dependencies
  • purl: Package URL (universal package identifier)
  • licenses: SPDX license identifiers
  • hashes: Cryptographic hashes for integrity
  • dependencies: Dependency relationships
  • externalReferences: Links to documentation, source code

SBOM Generation

Automatic Generation:

GitLab automatically generates SBOMs through:

  1. Dependency Scanning - Analyzes lock files and manifests
  2. Container Scanning - Scans container image layers

Supported Ecosystems:

  • JavaScript: npm, yarn, pnpm (package-lock.json, yarn.lock, pnpm-lock.yaml)
  • Python: pip, poetry, pipenv (requirements.txt, Pipfile.lock, poetry.lock)
  • Ruby: bundler (Gemfile.lock)
  • Java: Maven, Gradle (pom.xml, build.gradle, *.gradle.kts)
  • Go: go modules (go.mod, go.sum)
  • PHP: Composer (composer.lock)
  • C#/.NET: NuGet (packages.lock.json, project.assets.json)
  • Rust: Cargo (Cargo.lock)
  • Container Images: Docker, OCI images

Configuration:

# Enable Dependency Scanning (generates SBOM automatically) include: - template: Security/Dependency-Scanning.gitlab-ci.yml dependency_scanning: artifacts: reports: cyclonedx: "**/gl-sbom-*.cdx.json"

Container SBOM:

# Enable Container Scanning (generates SBOM for image) include: - template: Security/Container-Scanning.gitlab-ci.yml container_scanning: variables: CS_IMAGE: "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA" CS_SBOM_ENABLED: "true" artifacts: reports: cyclonedx: "**/gl-sbom-*.cdx.json"

Accessing SBOMs

Pipeline Artifacts:

  1. Navigate to: Pipelines > [Pipeline] > Jobs > dependency_scanning
  2. Click "Browse" under artifacts
  3. Download gl-sbom-*.cdx.json

Dependency List (Ultimate):

  1. Navigate to: Security & Compliance > Dependency List
  2. View all dependencies with licenses and vulnerabilities
  3. Export as CSV or JSON

API Access:

# Get dependency list curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ "https://gitlab.com/api/v4/projects/123/dependencies" # Get SBOM artifact curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ "https://gitlab.com/api/v4/projects/123/jobs/456/artifacts" \ --output sbom.zip

SBOM Management

Version Control:

# Store SBOM in repository generate_sbom: stage: build script: - ./gradlew cyclonedxBom - cp build/reports/bom.json sbom.json artifacts: paths: - sbom.json expire_in: never commit_sbom: stage: publish script: - git config user.name "GitLab CI" - git config user.email "ci@example.com" - git add sbom.json - git commit -m "chore: update SBOM [skip ci]" || true - git push https://oauth2:$CI_JOB_TOKEN@$CI_SERVER_HOST/$CI_PROJECT_PATH.git HEAD:$CI_COMMIT_BRANCH only: - main

SBOM Registry:

# Publish SBOM to artifact registry publish_sbom: stage: publish script: # Upload SBOM to package registry - curl --header "JOB-TOKEN: $CI_JOB_TOKEN" \ --upload-file gl-sbom-npm-npm.cdx.json \ "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/sbom/${CI_COMMIT_TAG}/sbom.json" only: - tags

Dependency Management

Dependency Discovery

Dependency List Page:

GitLab Ultimate provides a centralized Dependency List:

  1. Navigate to: Security & Compliance > Dependency List
  2. View all dependencies across all projects (group level)
  3. Filter by:
    • License type
    • Vulnerability severity
    • Project
    • Component name

Information Displayed:

Dependency: lodash
Version: 4.17.21
Package Manager: npm
License: MIT
Vulnerabilities: 0
Projects Using: 23
Latest Version: 4.17.21
Direct Dependency: Yes
Transitive Dependencies: 0

Dependency Analysis

Dependency Tree:

# Generate dependency tree generate_dependency_tree: stage: test script: # npm - npm list --all > dependency-tree.txt # pip - pip install pipdeptree - pipdeptree > dependency-tree.txt # maven - mvn dependency:tree > dependency-tree.txt # gradle - ./gradlew dependencies > dependency-tree.txt artifacts: paths: - dependency-tree.txt

Example Output:

my-app@1.0.0
 express@4.18.2
    body-parser@1.20.1
       bytes@3.1.2
       content-type@1.0.4
       raw-body@2.5.1
    cookie@0.5.0
    cookie-signature@1.0.6
 lodash@4.17.21
 axios@1.2.2
     follow-redirects@1.15.2
     form-data@4.0.0

Vulnerability Tracking

Vulnerable Dependencies:

GitLab automatically detects vulnerabilities in dependencies:

Detection:

  1. Dependency Scanning runs in pipeline
  2. Compares dependencies against vulnerability databases:
    • GitLab Advisory Database (enhanced in Ultimate)
    • National Vulnerability Database (NVD)
    • GitHub Advisory Database
    • Language-specific databases (npm, PyPI, RubyGems, etc.)
  3. Reports findings in Vulnerability Report
  4. Shows in merge request widget

Remediation:

# Automated dependency updates dependency_updates: stage: maintenance script: # Update npm dependencies - npm update - npm audit fix # Update Python dependencies - pip install --upgrade -r requirements.txt # Create MR with updates - git checkout -b "chore/dependency-updates-${CI_PIPELINE_ID}" - git add package-lock.json requirements.txt - git commit -m "chore: update vulnerable dependencies" - git push origin "chore/dependency-updates-${CI_PIPELINE_ID}" - glab mr create --title "chore: update vulnerable dependencies" --description "Automated dependency updates" when: manual only: - schedules

Dependency Updates

Manual Updates:

# npm npm update package-name npm audit fix npm audit fix --force # Breaking changes # pip pip install --upgrade package-name pip-review --auto # Update all # bundler (Ruby) bundle update package-name # Maven mvn versions:use-latest-versions # Gradle ./gradlew useLatestVersions

Automated Updates:

Renovate Bot:

// renovate.json { "extends": ["config:base"], "labels": ["dependencies"], "assignees": ["@team-lead"], "schedule": ["before 6am on monday"], "packageRules": [ { "matchUpdateTypes": ["minor", "patch"], "automerge": true, "automergeType": "pr", "automergeStrategy": "squash" }, { "matchUpdateTypes": ["major"], "automerge": false, "labels": ["breaking-change"] } ], "vulnerabilityAlerts": { "labels": ["security"], "assignees": ["@security-team"], "automerge": true } }

Dependabot (GitHub-style):

# .gitlab/dependabot.yml version: 2 updates: - package-ecosystem: "npm" directory: "/" schedule: interval: "weekly" reviewers: - "team-lead" labels: - "dependencies" open-pull-requests-limit: 10 - package-ecosystem: "pip" directory: "/" schedule: interval: "weekly" reviewers: - "python-team"

License Compliance

License Detection

Automatic Detection:

GitLab detects licenses from:

  • Package metadata (package.json, pom.xml, Gemfile, etc.)
  • LICENSE files in dependencies
  • SPDX license identifiers
  • License headers in source files

License Information in SBOM:

{ "component": { "name": "express", "version": "0.4.9", "licenses": [ { "license": { "id": "MIT", "name": "MIT License", "url": "https://opensource.org/licenses/MIT" } } ] } }

License Policies

Creating License Approval Policies:

  1. Navigate to: Security & Compliance > Policies
  2. Click "New policy"
  3. Select "License approval policy"
  4. Configure allowed and denied licenses

Policy Configuration:

# .gitlab/license-approval-policy.yml license_approval_policy: name: "Open Source License Policy" description: "Enforce approved open source licenses" # Approved licenses (no action required) approved_licenses: - MIT - Apache-2.0 - BSD-2-Clause - BSD-3-Clause - ISC - CC0-1.0 # Denied licenses (block merge request) denied_licenses: - GPL-3.0 - AGPL-3.0 - CC-BY-NC-4.0 - SSPL-1.0 # Requires approval (security/legal team review) approval_required_licenses: - Apache-1.0 - EPL-1.0 - MPL-2.0 - LGPL-3.0

License Compliance Workflow

Enforcement:

  1. Dependency/Container Scanning detects licenses
  2. License approval policy evaluates licenses
  3. If denied license found:
    • Merge request blocked
    • Security/legal approval required
  4. Eligible approver reviews and approves/rejects

Example:

Merge Request: Add React library
Detected License: MIT (approved) 

Merge Request: Add GPL-licensed library
Detected License: GPL-3.0 (denied) 
Status: Blocked
Required Approval: @legal-team

License Compliance Report

Viewing License Information:

  1. Navigate to: Security & Compliance > License Compliance
  2. View all detected licenses
  3. Filter by approval status
  4. Export for legal review

Report Format:

License Compliance Report - Project: my-app

Total Dependencies: 247
Unique Licenses: 12

Approved Licenses:
  - MIT: 189 dependencies
  - Apache-2.0: 34 dependencies
  - BSD-3-Clause: 12 dependencies
  - ISC: 8 dependencies

Approval Required:
  - MPL-2.0: 3 dependencies (pending legal review)

Denied:
  - GPL-3.0: 1 dependency (blocked)
    - Component: example-gpl-library@1.0.0
    - Used in: src/utils/helper.js
    - Action Required: Remove or replace

Provenance and Attestation

Overview

Provenance and attestation provide cryptographic proof of:

  • Where software was built (build environment)
  • How it was built (build process)
  • Who built it (identity)
  • Integrity of artifacts (digital signatures)

Status in GitLab (2026):

  • In Development: Automatic digital signing of build artifacts
  • Planned: SLSA (Supply-chain Levels for Software Artifacts) compliance
  • Planned: In-toto attestation format support

Build Provenance

Current Capabilities:

GitLab pipelines provide provenance information:

# Capture build metadata build: stage: build script: - | cat > provenance.json <<EOF { "builder": { "id": "$CI_RUNNER_ID", "version": "$CI_RUNNER_VERSION" }, "buildType": "gitlab-pipeline", "invocation": { "configSource": { "uri": "$CI_PROJECT_URL", "digest": { "sha256": "$CI_COMMIT_SHA" }, "entryPoint": ".gitlab-ci.yml" }, "parameters": { "branch": "$CI_COMMIT_BRANCH", "tag": "$CI_COMMIT_TAG" } }, "metadata": { "buildStartedOn": "$CI_PIPELINE_CREATED_AT", "buildFinishedOn": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", "completeness": { "parameters": true, "environment": false, "materials": true } }, "materials": [ { "uri": "git+$CI_REPOSITORY_URL", "digest": { "sha256": "$CI_COMMIT_SHA" } } ] } EOF - npm run build - sha256sum dist/* > checksums.txt artifacts: paths: - dist/ - provenance.json - checksums.txt

Artifact Signing

GPG Signing:

sign_artifacts: stage: sign script: # Import GPG key (from CI/CD variable) - echo "$GPG_PRIVATE_KEY" | gpg --import # Sign artifacts - gpg --armor --detach-sign dist/app.tar.gz # Generate checksums - sha256sum dist/app.tar.gz > dist/app.tar.gz.sha256 # Sign checksums - gpg --armor --detach-sign dist/app.tar.gz.sha256 artifacts: paths: - dist/app.tar.gz - dist/app.tar.gz.asc - dist/app.tar.gz.sha256 - dist/app.tar.gz.sha256.asc

Verification:

# Download public key curl https://example.com/public-key.asc | gpg --import # Verify signature gpg --verify app.tar.gz.asc app.tar.gz # Verify checksum sha256sum -c app.tar.gz.sha256

Container Image Signing

Cosign (Sigstore):

sign_container: stage: sign image: gcr.io/projectsigstore/cosign:latest script: # Sign container image - cosign sign --key $COSIGN_PRIVATE_KEY $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA # Generate attestation - cosign attest --key $COSIGN_PRIVATE_KEY \ --predicate provenance.json \ $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA only: - tags

Verification:

# Verify signature cosign verify --key cosign.pub $IMAGE # Verify attestation cosign verify-attestation --key cosign.pub $IMAGE

Supply Chain Attack Prevention

Threat Model

Common Attack Vectors:

  1. Dependency Confusion

    • Attacker publishes malicious package with same name
    • Package manager downloads attacker's package instead of internal one
  2. Typosquatting

    • Malicious package with similar name to popular package
    • Developers accidentally install wrong package
  3. Compromised Package

    • Legitimate package account compromised
    • Malicious code injected into trusted package
  4. Malicious Maintainer

    • Package maintainer adds malicious code
    • Backdoor distributed to all users
  5. Build System Compromise

    • Attacker compromises CI/CD system
    • Injects malicious code during build

Mitigation Strategies

1. Dependency Pinning:

// package-lock.json (npm) { "dependencies": { "lodash": { "version": "0.4.9", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" } } }

2. Subresource Integrity:

# Verify package integrity install_dependencies: stage: build script: # npm automatically verifies integrity from package-lock.json - npm ci # pip with hash checking - pip install --require-hashes -r requirements.txt

3. Private Package Registry:

# .npmrc @mycompany:registry=https://npm.example.com/ //npm.example.com/:_authToken=${NPM_TOKEN} registry=https://registry.npmjs.org/

4. Allowlist/Blocklist:

# Security policy dependency_policy: allowed_registries: - npm: https://registry.npmjs.org/ - pypi: https://pypi.org/ blocked_packages: - name: "malicious-package" reason: "Known malware" - name: "compromised-lib" reason: "Compromised maintainer account"

5. Automated Scanning:

include: - template: Security/Dependency-Scanning.gitlab-ci.yml - template: Security/Container-Scanning.gitlab-ci.yml # Scan on every commit dependency_scanning: stage: test # Block merge if critical vulnerabilities scan_result_policy: - name: "Block Supply Chain Attacks" rules: - type: scan_finding scanners: ["dependency_scanning", "container_scanning"] severity_levels: ["critical", "high"] vulnerabilities_allowed: 0 actions: - type: require_approval approvers: ["@security-team"]

6. Regular Audits:

# Scheduled security audit security_audit: stage: audit script: # npm audit - npm audit --production - npm audit --audit-level=high # Python safety check - pip install safety - safety check --json # Ruby bundler audit - bundle audit check --update # Report findings - ./generate-audit-report.sh artifacts: reports: junit: audit-report.xml only: - schedules

Best Practices

SBOM Management

  1. Generate SBOMs Automatically - Enable Dependency and Container Scanning
  2. Version Control SBOMs - Store SBOMs with releases
  3. Regular Updates - Regenerate SBOMs with each build
  4. Include in Releases - Publish SBOMs alongside artifacts
  5. Audit SBOMs - Review SBOMs for unexpected dependencies

Dependency Management

  1. Pin Dependencies - Use lock files (package-lock.json, Gemfile.lock, etc.)
  2. Regular Updates - Update dependencies weekly or monthly
  3. Security Patches - Apply security updates immediately
  4. Minimize Dependencies - Remove unused dependencies
  5. Vet New Dependencies - Review before adding to project

License Compliance

  1. Define Policy Early - Establish license policy before development
  2. Automated Enforcement - Use license approval policies
  3. Regular Reviews - Review licenses quarterly
  4. Legal Consultation - Involve legal team in policy decisions
  5. Documentation - Maintain SBOM for compliance evidence

Supply Chain Security

  1. Defense in Depth - Multiple layers of security
  2. Continuous Monitoring - Automated scanning on every commit
  3. Rapid Response - Process for emergency updates
  4. Incident Response Plan - Documented procedure for supply chain incidents
  5. Team Training - Educate developers on supply chain risks

Troubleshooting

Common Issues

SBOM not generated:

Checklist:
 Dependency Scanning job ran successfully
 Lock file present and committed
 Supported package manager
 Artifacts configured correctly
 Check job logs for errors

Missing dependencies in SBOM:

Cause: Lock file not up-to-date

Resolution:
npm install      # Regenerate package-lock.json
pip freeze       # Update requirements.txt
bundle install   # Update Gemfile.lock

License not detected:

Cause: Package missing license metadata

Resolution:
1. Check package repository for license
2. Contact package maintainer
3. Consider alternative package
4. Document in custom license database

False positive vulnerabilities:

Cause: Scanner detecting vulnerability that doesn't apply

Resolution:
1. Verify vulnerability context
2. Check if vulnerable code path is used
3. Dismiss as false positive in Vulnerability Report
4. Add explanation for audit trail

Regulatory Compliance

SBOM Requirements

Executive Order 14028 (USA):

  • Software sold to US government must include SBOM
  • SBOM must be in standard format (SPDX, CycloneDX)
  • Must include all components and dependencies

EU Cyber Resilience Act:

  • Manufacturers must provide SBOM for software products
  • SBOM must be machine-readable
  • Must include vulnerability information

GitLab Compliance:

  • Automatically generates CycloneDX SBOMs
  • Includes all dependencies and transitive dependencies
  • Machine-readable JSON format
  • Vulnerability information integrated

References

Next Steps