Security Scanning - Secrets, Dependencies, and Container Image Scans in Pipelines

Status: Active
Last Updated: 2026-08-26
Category: CI/CD - Phase 2: Supply Chain
Prerequisites: woodpecker-first-pipeline, ci-secrets-management, dockerfile-guide
Time: 3 hours
Tags: security, scanning, trivy, gitleaks, dependencies, woodpecker, supply-chain

Summary

Add automated security gates to your Woodpecker pipelines: scan commits for leaked secrets, check dependencies for known CVEs, and scan built container images with Trivy or Grype before anything is published. Fail fast, fail loudly, and keep the noise manageable.

๐ŸŽฏ What You'll Learn

By the end of this article, you'll be able to:


Table of Contents

  1. Context / Why This Matters
  2. Implementation / Core Content
  3. Practical Examples
  4. Troubleshooting & Common Pitfalls
  5. Next Steps / Ops Actions
  6. Sources & Related

Context / Why This Matters

Your CI pipeline touches everything sensitive: source code, deploy keys, registry credentials (ci-secrets-management). It's also the last automated checkpoint before an image runs on production hosts. Three classes of risk map naturally onto pipeline stages:

  1. Secrets in code โ€” a committed API key outlives the commit forever once pushed. Detect at push time.
  2. Vulnerable dependencies โ€” CVEs land in libraries you already use. Detect on every build.
  3. Image vulnerabilities โ€” base images age even when your code doesn't change. Detect before publish.

These checks complement host-level hardening (security-hardening, fail2ban-setup); they reduce what you ship instead of defending what already runs.

Implementation / Core Content

Stage 1: secret scanning with gitleaks

steps:
  secrets:
    image: zricethezav/gitleaks:v8
    commands:
      # full history scan is expensive; diff mode for PRs, detect for pushes
      - gitleaks detect --source . --verbose --redact
    when:
      event: [push]

For pull requests, scan only the new commits so reviewers see a targeted report:

  secrets-pr:
    image: zricethezav/gitleaks:v8
    when:
      event: [pull_request]
    commands:
      - git fetch origin ${CI_COMMIT_TARGET_BRANCH}
      - gitleaks detect --source . --log-opts="--origin/${CI_COMMIT_TARGET_BRANCH}..HEAD" --verbose --redact

Baseline existing leaks instead of failing forever: run gitleaks detect --report-format sarif once, triage genuinely leaked credentials (rotate them โ€” do not just delete the file; history retains it), then add false positives to .gitleaksignore.

Stage 2: dependency vulnerability scanning

For language ecosystems with audit tooling:

  deps-audit:
    image: node:22
    commands:
      - npm ci
      - npm audit --omit=dev --audit-level=high || true   # warn first, tighten later

Start permissive (|| true, log-only) for one sprint, review the volume of findings, then remove the || true and set --audit-level=critical. Raising the bar incrementally prevents the "pipeline is permanently red, everyone ignores it" anti-pattern.

Stage 3: image scanning with Trivy

Scan the image after build, before publish:

steps:
  build:
    image: plugins/docker
    settings:
      dry_run: true            # don't publish yet
      repo: registry.fogserv.cloud/homelab/myapp
      tags: "${CI_COMMIT_SHA}"

  scan:
    image: aquasec/trivy:latest
    depends_on: [build]
    commands:
      # import the locally built image or scan by tag after a real push
      - trivy image --exit-code 1 --severity CRITICAL,HIGH \
          --ignore-unfixed registry.fogserv.cloud/homelab/myapp:${CI_COMMIT_SHA}

  publish:
    image: plugins/docker
    when:
      event: [tag]
    depends_on: [scan]
    settings:
      repo: registry.fogserv.cloud/homelab/myapp
      tags: ["${CI_COMMIT_TAG}"]
      username:
        from_secret: registry_user
      password:
        from_secret: registry_pass

Key Trivy flags:

Grype is the drop-in alternative if you prefer its DB:

grype registry.fogserv.cloud/homelab/myapp:${CI_COMMIT_SHA} --fail-on high

Keep .trivyignore in the repo root with one CVE per line plus a comment explaining each suppression โ€” auditable, unlike verbal waivers.

Where to gate

Check Push PR Tag/release
Secret scan block block block
Dependency audit log log block (critical only)
Image scan log log block (CRITICAL/HIGH)

Blocking only at release time keeps development velocity while guaranteeing published artifacts are clean.

Practical Examples

Example: complete gated release workflow

when:
  event: [push, tag]

steps:
  test:
    image: python:3.12
    commands:
      - pip install -e . pytest && pytest

  secrets:
    image: zricethezav/gitleaks:v8
    commands:
      - gitleaks detect --source . --redact --verbose

  build:
    image: plugins/docker
    settings:
      dry_run: true
      repo: registry.fogserv.cloud/homelab/myapp
      tags: "${CI_COMMIT_SHA}"

  scan:
    image: aquasec/trivy:latest
    depends_on: [build]
    commands:
      - trivy image --exit-code 1 --severity CRITICAL --ignore-unfixed \
          --scanners vuln,secret registry.fogserv.cloud/homelab/myapp:${CI_COMMIT_SHA}
      - trivy image --format table registry.fogserv.cloud/homelab/myapp:${CI_COMMIT_SHA} > trivy-report.txt
        && echo "Full report in artifacts" || true

  publish:
    image: plugins/docker
    when:
      event: [tag]
    depends_on: [test, secrets, scan]
    settings:
      repo: registry.fogserv.cloud/homelab/myapp
      tags: ["${CI_COMMIT_TAG}", "stable"]
      username:
        from_secret: registry_user
      password:
        from_secret: registry_pass

Expected behavior: any commit containing a secret fails instantly; tags with CRITICAL unfixed... er, fixable CVEs never reach the registry.

Troubleshooting & Common Pitfalls

Problem Cause Fix
Pipeline permanently red from day one Thresholds too strict for current dependency state Ship log-only first; ratchet severity down over time
Trivy can't find the built image dry_run build never pushed; scanner pulls remotely Build+load locally (plugins/docker with settings.repo + local daemon) or push to a private staging tag first
False-positive secret in test fixtures Test tokens/keys trigger detectors Add exact fingerprint to .gitleaksignore; never weaken the ruleset globally
Old leak still found after deleting file Git history retains it Rotate the credential; rewrite history only if rotation is impossible
Trivy DB download slow/failing on runner Rate limits or offline runner Schedule trivy image --download-db-only via cron on the runner, mount cache dir as volume (see caching-strategies)
Findings ignored because nobody reads logs Report-only steps produce no signal Attach reports to step output and alert on failures via ci-monitoring

Next Steps / Ops Actions

Sources & Related

External references consulted:

Related knowledge-base articles:

Change Log

2026-08-26

Choose Theme

Your selection is saved locally.

Neural Cacophony
Aperture v2
Flux v1
Mosaic Chaos
Nexus v1
Nexus Zest
Prism v2
Synapse