Skip to main content
ship it and sleep

Static Analysis, SAST, and Dependency Scanning as Hard Gates

4 min read Chapter 46 of 66

Static Analysis, SAST, and Dependency Scanning as Hard Gates

Security scanning that runs but does not block is decoration. The scan finds 47 vulnerabilities. The developer glances at the output, sees it passed, and merges. Three weeks later, a critical CVE in a transitive dependency makes it to production.

Hard gates block the merge. No exceptions for “we’ll fix it later.” The pipeline fails, the PR cannot be merged, and the vulnerability does not reach production.

Supply chain security gates

The Failure

The team added Trivy to their pipeline as an informational step. The scan ran, produced a JSON report, and uploaded it as an artifact. No one read the artifacts. Over six months, 23 high-severity CVEs accumulated in production containers. When the security audit happened, the team spent two weeks remediating vulnerabilities that could have been caught at merge time.

The fix: make Trivy a required status check. If Trivy finds a HIGH or CRITICAL vulnerability, the pipeline fails and the PR cannot be merged.

The Mechanism

Scanning Layers

LayerToolWhat It ScansWhen
Source codeCodeQLLogic bugs, SQL injection, XSSPR, push to main
DependenciesTrivy, OWASP DCKnown CVEs in librariesPR, scheduled
Container imageTrivyOS packages, app dependenciesAfter build
IaCTrivy, CheckovKubernetes manifests, DockerfilesPR
SecretsGitleaksHardcoded credentials, API keysPR, pre-commit

Gate vs Advisory

A gate blocks the pipeline. A PR cannot merge until the gate passes. An advisory produces a report but does not block. Used for informational findings or new rules being evaluated.

Start with advisories. Measure false positive rates. When the false positive rate is acceptable, promote to a gate.

The Implementation

Trivy Container Scanning

# .github/workflows/security.yml
# HARDENED: Trivy as a hard gate
name: Security Scan
on:
  pull_request:
    branches: [main]

jobs:
  trivy-image:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

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

      - name: Trivy image scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ${{ github.repository }}:${{ github.sha }}
          format: "sarif"
          output: "trivy-results.sarif"
          severity: "CRITICAL,HIGH"
          exit-code: "1" # Fail on findings

      - name: Upload SARIF
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: "trivy-results.sarif"

CodeQL for Source Analysis

# .github/workflows/codeql.yml
# HARDENED: CodeQL as a required check
name: CodeQL Analysis
on:
  pull_request:
    branches: [main]
  schedule:
    - cron: "0 6 * * 1" # Weekly full scan

jobs:
  analyze:
    runs-on: ubuntu-latest
    permissions:
      security-events: write
    strategy:
      matrix:
        language: ["javascript", "go", "java"]
    steps:
      - uses: actions/checkout@v4

      - name: Initialize CodeQL
        uses: github/codeql-action/init@v3
        with:
          languages: ${{ matrix.language }}
          queries: +security-extended

      - name: Build
        uses: github/codeql-action/autobuild@v3

      - name: Perform analysis
        uses: github/codeql-action/analyze@v3
        with:
          category: "/language:${{ matrix.language }}"

Gitleaks Pre-Commit and CI

# .github/workflows/secrets.yml
# HARDENED: Block commits with secrets
name: Secret Detection
on:
  pull_request:
    branches: [main]

jobs:
  gitleaks:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Gitleaks scan
        uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Trivy IaC Scanning

- name: Trivy IaC scan
  uses: aquasecurity/trivy-action@master
  with:
    scan-type: "config"
    scan-ref: "k8s/"
    format: "sarif"
    output: "trivy-iac.sarif"
    exit-code: "1"
    severity: "CRITICAL,HIGH"

The Gate

Security scans are required status checks in GitHub branch protection:

Settings → Branches → main → Require status checks:
  ✓ trivy-image
  ✓ codeql-analysis
  ✓ gitleaks

No PR merges to main without all three passing.

The Recovery

Trivy blocks a PR for a vulnerability in a base image: Update the base image. If no fix is available, add the CVE to .trivyignore with an expiration date and a linked tracking issue.

CodeQL produces false positives: Suppress with // codeql[query-id] inline comments. Track suppressions. Review them quarterly.

Gitleaks flags a test fixture as a secret: Add the pattern to .gitleaks.toml allowlist. Never add real secrets to allowlists.