Hardening Your Self-Hosted CI/CD Pipeline Against Supply Chain Attacks

Your CI/CD pipeline has more access than most engineers. Learn how to harden Gitea Actions, Jenkins, and self-hosted runners against credential theft and pipeline injection.

Y
Yash Pritwani
13 min read read

Hardening Your Self-Hosted CI/CD Pipeline Against Supply Chain Attacks

Your CI/CD pipeline has access to your source code, your container registry credentials, your deployment keys, your cloud provider tokens, and your production infrastructure. It is the single most privileged component in your software delivery chain.

And for most teams, it is also the least hardened.

The Trivy supply chain attack in March 2026 was a wake-up call: attackers hijacked 75 container image tags of a security scanning tool, injecting malware that harvested credentials from CI/CD environments. If your pipeline pulls third-party images, runs third-party actions, or installs third-party packages — and every pipeline does — you are exposed.

The CI/CD Attack Surface

What Attackers Target

  1. Pipeline secrets — environment variables containing API keys, deployment tokens, database credentials
  2. Build artifacts — inject malware into the binary/image that gets deployed to production
  3. Dependency resolution — poison a package that the build pulls at install time
  4. Pipeline configuration — modify the CI config to exfiltrate secrets or inject backdoors
  5. Runner infrastructure — compromise the machine running the builds to pivot into the network

The Trust Chain Problem

Your pipeline trusts:

  • The code in the repository (any contributor with push access)
  • The CI configuration file (usually committed to the repo)
  • Every GitHub Action, Gitea Action, or Jenkins plugin referenced
  • Every Docker image used as a build environment
  • Every package installed during the build
  • Every external service called during tests

Each link in this chain is an attack vector. Supply chain attacks exploit the weakest link.

Hardening Self-Hosted Runners

Ephemeral Runners

The most important hardening measure: do not reuse runners between jobs.

# Gitea Actions: use Docker containers as ephemeral runners
labels:
  - "ubuntu-latest:docker://node:20-slim"

# Each job gets a fresh container
# No state persists between jobs
# A compromised job cannot affect subsequent jobs

If you run bare-metal or VM runners, use VM snapshots: boot from a clean snapshot for each job, discard the VM after.

Network Isolation

Your runner should not have unfettered network access:

Get more insights on Security

Join 2,000+ engineers who get our weekly deep-dives. No spam, unsubscribe anytime.

# Create an isolated Docker network for CI
docker network create --driver bridge \
  --subnet 172.30.0.0/24 \
  ci-network

# Allow: package registries, container registries, your Git server
# Block: internal services, production databases, admin interfaces

# iptables rules for the runner host
iptables -A FORWARD -s 172.30.0.0/24 -d 10.0.0.0/8 -j DROP
iptables -A FORWARD -s 172.30.0.0/24 -d 172.16.0.0/12 -j DROP
iptables -A FORWARD -s 172.30.0.0/24 -d 192.168.0.0/16 -j DROP
iptables -A FORWARD -s 172.30.0.0/24 -j ACCEPT

This prevents a compromised build from scanning your internal network.

Resource Limits

A crypto miner in your CI pipeline will consume all available CPU:

# Docker Compose for self-hosted runner
services:
  runner:
    image: gitea/act_runner:latest
    deploy:
      resources:
        limits:
          cpus: "2.0"
          memory: 4G

Set time limits on jobs. No legitimate build takes 6 hours:

jobs:
  build:
    timeout-minutes: 30

Securing Pipeline Secrets

Principle of Least Privilege

Every secret should be scoped to the narrowest possible context:

# BAD: All jobs get all secrets
env:
  AWS_ACCESS_KEY_ID: ${{ secrets.AWS_KEY }}
  DOCKER_PASSWORD: ${{ secrets.DOCKER_PASS }}
  DATABASE_URL: ${{ secrets.DB_URL }}

# GOOD: Each job gets only what it needs
jobs:
  build:
    env:
      DOCKER_PASSWORD: ${{ secrets.DOCKER_PASS }}

  deploy:
    needs: build
    env:
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_KEY }}

OIDC Over Long-Lived Credentials

Use OIDC tokens instead of long-lived credentials wherever possible. AWS, GCP, and Azure all support OIDC authentication from CI runners. This eliminates the need to store cloud credentials as CI secrets entirely.

Secret Rotation

Automate credential rotation. If a secret in your CI has not been rotated in 90 days, it is a liability. Run rotation scripts as separate cron jobs — never inside the CI pipeline itself.

Pinning Dependencies

Pin Container Images by Digest

# BAD: Tag can be overwritten (this is how Trivy was attacked)
- uses: docker://trivy:latest
- uses: docker://node:20

# GOOD: Digest is immutable
- uses: docker://aquasec/trivy@sha256:abc123...
- uses: docker://node@sha256:def456...

Pin Actions by Commit SHA

# BAD: Tag can be moved to a malicious commit
- uses: actions/checkout@v4

# GOOD: Commit SHA is immutable
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11

Pin Package Versions

# BAD
RUN pip install requests

# GOOD
COPY requirements.txt .
RUN pip install --no-deps -r requirements.txt
# requirements.txt: requests==2.31.0 --hash=sha256:abc123...

Use pip --require-hashes, npm ci (uses lockfile), cargo --locked. Never run install without a lockfile in CI.

Pipeline Configuration Protection

Protect the CI Config File

The CI configuration file is the most dangerous file in your repository. Anyone who can modify it can exfiltrate every secret available to the pipeline.

# CODEOWNERS
.gitea/workflows/** @security-team
.github/workflows/** @security-team
Jenkinsfile @security-team
Dockerfile @security-team

Restrict Fork Builds

Fork PRs should never have access to your secrets. Configure your CI to only inject secrets on pushes to trusted branches and PRs from the same repository.

SBOM and Provenance

Generate a Software Bill of Materials

Every build should produce an SBOM documenting exactly what went into the artifact:

- name: Build container image
  run: docker build -t myapp:${{ github.sha }} .

- name: Generate SBOM
  run: syft myapp:${{ github.sha }} -o spdx-json > sbom.json

- name: Sign the image
  run: cosign sign --key cosign.key myapp:${{ github.sha }}

- name: Attach SBOM to image
  run: cosign attach sbom --sbom sbom.json myapp:${{ github.sha }}

SLSA Provenance

Free Resource

Infrastructure Security Audit Template

The exact audit template we use with clients: 60+ checks across network, identity, secrets management, and compliance.

Get the Template

SLSA (Supply-chain Levels for Software Artifacts) defines levels of supply chain security:

  • SLSA 1: Build process is documented
  • SLSA 2: Build service is hosted (not local machines)
  • SLSA 3: Build service is hardened, provenance is non-forgeable
  • SLSA 4: Two-person review, hermetic builds

Most teams should aim for SLSA 2-3.

Monitoring Your Pipeline

What to Alert On

  • Builds running unusually long (possible crypto mining)
  • Builds triggered from unexpected branch patterns
  • Unusual rate of secret access
  • Failed deployments followed by successful ones (possible attack-then-cleanup)
  • New dependencies added without review

Audit Logs

Log every secret access, every deployment, every image push. When an incident happens, you need to trace exactly what the pipeline did and when.

The Minimum Security Checklist

If you implement nothing else, do these five things:

  1. Pin everything — container images by digest, actions by SHA, packages by lockfile hash
  2. Ephemeral runners — no state persists between jobs
  3. Scope secrets — each job gets only the secrets it needs
  4. Protect CI config files — require review for changes to workflow files
  5. Network isolate runners — block access to internal networks

These five steps eliminate the vast majority of CI/CD attack vectors. Everything else is defense in depth.

The Bottom Line

Your CI/CD pipeline is the highest-value target in your infrastructure. It has access to everything: source code, secrets, production deployment keys, and the authority to push artifacts that your users trust.

Treat your pipeline with the same security rigor you apply to your production servers. Pin dependencies. Isolate runners. Scope secrets. Monitor anomalies. And rotate credentials before they become liabilities.

The supply chain attacks will keep coming. The question is whether your pipeline is hardened enough to survive them.

#ci-cd#supply-chain-security#gitea#github-actions#devsecops#pipeline-security#self-hosted

Related Service

Security & Compliance

Zero-trust architecture, compliance automation, and incident response planning.

Need help with security?

TechSaaS provides expert consulting and managed services for cloud infrastructure, DevOps, and AI/ML operations.

We Will Build You a Demo Site — For Free

Like it? Pay us. Do not like it? Walk away, zero complaints. You will spend way less than hiring developers or any agency.

47+ companies trusted us
99.99% uptime
< 48hr response

No spam. No contracts. Just a free demo.