Skip to content
Pipelines and Pizza 🍕
Go back

Dependabot and Supply Chain Security

12 min read

Your terraform plan looks clean. Your GitHub Actions workflow is green. Your Docker image builds without errors. But what about the Terraform provider you downloaded last week? The GitHub Action pinned to v4? The npm package three levels deep in your dependency tree?

Modern infrastructure doesn’t exist in isolation. Every Terraform module, every pip package, every GitHub Action you reference is a door you’ve opened into your environment. Most of those doors lead to well-maintained, trustworthy code. Some don’t. And you won’t know the difference until something goes wrong — unless you set up the right guardrails.

That’s where Dependabot and a broader supply chain security strategy come in.


Why Supply Chain Attacks Matter

If you write infrastructure code, you’re a consumer of other people’s code whether you realize it or not. A Terraform provider, an Ansible Galaxy role, an npm package — these are all links in your supply chain. And attackers have figured out that compromising one popular dependency is far more efficient than targeting individual organizations.

Here are real-world examples that should keep you up at night:

IncidentYearWhat HappenedImpact
SolarWinds2020Attackers compromised the build system and injected malicious code into the Orion software update18,000+ customers, including US government agencies
event-stream2018A maintainer handed off a popular npm package to an attacker who injected a cryptocurrency-stealing dependencyMillions of downloads before detection
Codecov2021Attackers modified the Bash Uploader script via a Docker image creation flaw, exfiltrating CI environment variables29,000+ enterprise customers affected
tj-actions/changed-files2025Attackers pushed malicious commits and updated 350+ Git tags to point to code that dumped runner secrets23,000+ repositories using the action

The tj-actions incident in March 2025 is especially relevant for DevOps engineers. An attacker compromised a widely-used GitHub Action, retroactively modified version tags to point at malicious code, and every workflow referencing those tags silently started leaking secrets. If you were pinning to a tag like v45 instead of a commit SHA, you were exposed.


The Attack Vectors You Should Know

Before we talk about defenses, let’s understand the common attack patterns:

Dependency confusion exploits how package managers resolve names. If your organization uses an internal package called infra-utils and an attacker publishes infra-utils on the public npm or PyPI registry, your package manager might pull the public (malicious) version instead. Security researcher Alex Birsan demonstrated this in 2021 against companies like Apple, Microsoft, and Tesla.

Typosquatting is exactly what it sounds like. An attacker publishes reqeusts instead of requests on PyPI, or lodahs instead of lodash on npm, and waits for someone to make a typo. In 2023, researchers found 900 typosquats targeting just 40 popular PyPI packages.

Compromised maintainers happen when a trusted maintainer’s account is taken over, or when a popular but abandoned project is handed off to someone with bad intentions. The event-stream attack followed exactly this pattern.

Tag mutability is the GitHub Actions-specific variant. Git tags are mutable — they can be deleted and recreated pointing to completely different code. When you pin an action to v4, you’re trusting that tag will always point to safe code. The tj-actions attack proved that trust is misplaced.


Enter Dependabot

Dependabot is GitHub’s built-in tool for keeping dependencies current. It does two things:

  1. Version updates — Opens PRs when newer versions of your dependencies are available
  2. Security alerts — Notifies you when a known vulnerability affects a dependency in your project

You configure it by adding a .github/dependabot.yml file to your repository. Here’s a comprehensive example covering the ecosystems most DevOps engineers care about:

# .github/dependabot.yml
version: 2
updates:
  # Keep GitHub Actions up to date
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"
      day: "monday"
    labels:
      - "dependencies"
      - "github-actions"
    commit-message:
      prefix: "ci"

  # Terraform providers and modules
  - package-ecosystem: "terraform"
    directory: "/infrastructure"
    schedule:
      interval: "weekly"
    labels:
      - "dependencies"
      - "terraform"
    reviewers:
      - "infra-team"
    commit-message:
      prefix: "chore"

  # Python dependencies (pip, pipenv, poetry all use "pip")
  - package-ecosystem: "pip"
    directory: "/scripts"
    schedule:
      interval: "weekly"
    labels:
      - "dependencies"
      - "python"
    commit-message:
      prefix: "chore"

  # npm packages
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 10
    labels:
      - "dependencies"
      - "javascript"
    commit-message:
      prefix: "chore"

  # Docker base images
  - package-ecosystem: "docker"
    directory: "/"
    schedule:
      interval: "weekly"
    labels:
      - "dependencies"
      - "docker"
    commit-message:
      prefix: "chore"

A few things to note: each package-ecosystem entry targets a specific directory where the manifest lives. For Terraform, that’s wherever your .tf files are. For Docker, it’s where your Dockerfile is. You can have multiple entries for the same ecosystem pointing at different directories — useful for monorepos with Terraform configs in /infra/dev, /infra/staging, and /infra/prod.

By default, Dependabot opens a maximum of five pull requests for version updates per ecosystem. If you have a lot of outdated dependencies, bump open-pull-requests-limit so you’re not drip-fed updates over months.


Terraform-Specific Considerations

Dependabot monitors your .tf files for provider and module version constraints. When a new version of hashicorp/azurerm or a module you reference is released, Dependabot opens a PR updating the version constraint.

# Dependabot will detect this and propose updates
terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.85.0"
    }
  }
}

module "networking" {
  source  = "Azure/network/azurerm"
  version = "5.3.0"
  # ...
}

When Dependabot opens a PR, it includes the changelog and release notes from the provider so you can assess what changed before merging. This is especially valuable for Terraform providers where a minor version bump can change resource behavior.

For repositories with multiple Terraform directories, add a separate entry per directory:

updates:
  - package-ecosystem: "terraform"
    directory: "/infra/networking"
    schedule:
      interval: "weekly"
  - package-ecosystem: "terraform"
    directory: "/infra/compute"
    schedule:
      interval: "weekly"

Pinning GitHub Actions to SHA

This is the single most impactful thing you can do for CI/CD supply chain security. Instead of referencing actions by tag:

# Risky -- tags are mutable
steps:
  - uses: actions/checkout@v4
  - uses: hashicorp/setup-terraform@v3

Pin them to a full commit SHA:

# Secure -- commit SHAs are immutable
steps:
  - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
  - uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2

The commit SHA is immutable. Even if an attacker compromises the repository and moves the v4 tag, workflows pinned to the SHA won’t pull the malicious code.

The comment at the end (e.g., # v4.2.2) is just for human readability — Git ignores it. And here’s the best part: Dependabot understands SHA-pinned actions. When a new version is released, Dependabot opens a PR updating both the SHA and the comment. You get immutability and maintainability.

To find the SHA for an action, check the releases page on GitHub or run:

# Get the commit SHA for a specific tag
git ls-remote --tags https://github.com/actions/checkout.git v4.2.2

As of August 2025, GitHub also supports organizational policies that enforce SHA pinning — workflows using tag-based references will fail, which is a great way to enforce this across a team.


Lockfiles and Reproducible Builds

If you manage any application code alongside your infrastructure, lockfiles are non-negotiable. A package-lock.json (npm), poetry.lock (Python), or .terraform.lock.hcl (Terraform) records the exact versions of every dependency and sub-dependency.

Why this matters for security:

  • Without a lockfile, npm install might resolve to a different (potentially compromised) version than what you tested
  • Lockfiles include integrity hashes that verify the package content hasn’t been tampered with
  • CI should always use npm ci (not npm install) to enforce strict lockfile adherence
# In CI -- installs exactly what's in the lockfile, fails on mismatch
npm ci

# For Python with pip
pip install --require-hashes -r requirements.txt

# Terraform initializes and writes .terraform.lock.hcl
terraform init

Commit your lockfiles. Every single one. I’ve seen teams .gitignore their lockfiles because they cause merge conflicts. That’s trading a minor inconvenience for a real security gap.


GitHub’s Security Toolkit Beyond Dependabot

Dependabot is one piece of a larger security surface that GitHub provides. Here’s the full picture:

FeatureWhat It DoesCost
Dependency GraphMaps all dependencies in your repo, including transitive onesFree
Dependabot AlertsNotifies you when a known CVE affects your dependenciesFree
Dependabot Security UpdatesAutomatically opens PRs to fix vulnerable dependenciesFree
Dependabot Version UpdatesOpens PRs to keep dependencies current (not just security fixes)Free
Secret ScanningDetects API keys, tokens, and credentials committed to the repoFree for public repos
Push ProtectionBlocks pushes that contain detected secrets before they reach the repoFree for public repos
Code Scanning (CodeQL)Static analysis to find vulnerabilities in your codeFree for public repos
SBOM ExportGenerates a Software Bill of Materials from your dependency graphFree

Enable the dependency graph and Dependabot alerts at minimum. They’re free and require zero configuration beyond flipping them on in your repository settings under Settings > Code security and analysis.


Software Bill of Materials (SBOM)

An SBOM is a structured inventory of every component in your software — every library, every transitive dependency, every version number. Think of it as the ingredient list on the side of a food package.

Why you should care:

  • Vulnerability response: When the next Log4j drops, an SBOM lets you instantly answer “are we affected?” instead of spending days auditing repos
  • Compliance: Government agencies and enterprise customers increasingly require SBOMs (per CISA and Executive Order 14028)
  • Transparency: You can evaluate your own exposure before an incident, not after

GitHub can export SBOMs directly from the dependency graph in SPDX format. Go to your repository, click Insights > Dependency graph > Export SBOM.

For automated generation in CI:

- name: Generate SBOM
  uses: anchore/sbom-action@d94f46e13c6c62f59525ac9a1e147a99dc0b9bf5 # v0.17.2
  with:
    format: spdx-json
    output-file: sbom.spdx.json

Evaluating Dependencies with OpenSSF Scorecard

Before you add a new dependency, how do you know if it’s well-maintained? The OpenSSF Scorecard project gives you a data-driven answer. It runs automated checks against open source projects and assigns scores from 0-10 across categories like:

  • Whether the project uses branch protection
  • Whether dependencies are pinned
  • Whether the project has active maintainers
  • Whether CI/CD pipelines run security checks
# Install and run Scorecard against a dependency
brew install scorecard

scorecard --repo=github.com/hashicorp/terraform-provider-azurerm

You can also check scores at scorecard.dev without installing anything. Make it a habit to check the Scorecard before adding a new Terraform module, GitHub Action, or npm package to your project.


Hands-On Lab: Set Up Dependabot on a Repository

Step 1: Create the configuration file

mkdir -p .github
cat > .github/dependabot.yml <<'YAML'
version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"
      day: "monday"
    commit-message:
      prefix: "ci"

  - package-ecosystem: "terraform"
    directory: "/"
    schedule:
      interval: "weekly"
    commit-message:
      prefix: "chore"
YAML

Step 2: Pin your existing GitHub Actions to SHAs

Find each uses: line in your workflows and replace the tag with the full commit SHA. For example:

# Look up the SHA for actions/checkout v4.2.2
git ls-remote --tags https://github.com/actions/checkout.git | grep 'v4.2.2'

# Update your workflow
# Before: uses: actions/checkout@v4
# After:  uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

Step 3: Enable security features in the repository

Navigate to Settings > Code security and analysis and enable:

  • Dependency graph (usually enabled by default)
  • Dependabot alerts
  • Dependabot security updates
  • Secret scanning (and push protection if available)

Step 4: Commit and push

git add .github/dependabot.yml
git commit -m "ci: add Dependabot configuration for Actions and Terraform"
git push

Within minutes, Dependabot will scan your repository and start opening PRs for outdated dependencies. Review them, check the changelogs, and merge with confidence.

Step 5: Verify it’s working

Go to Insights > Dependency graph > Dependabot in your repository. You should see each ecosystem listed with its last check time and any open PRs.


Troubleshooting Guide

ProblemCauseFix
Dependabot not opening PRsdependabot.yml not in .github/ directory on default branchEnsure the file is at .github/dependabot.yml and merged to main
PRs not appearing for TerraformWrong directory pathThe directory must point to where your .tf files live, not the repo root (unless that’s where they are)
Too many PRs at onceDefault limit is 5 per ecosystemAdjust open-pull-requests-limit or use ignore rules for low-priority packages
Dependabot can’t access private modulesNo access to private registries or reposConfigure registries in dependabot.yml and grant Dependabot access in org settings
SHA-pinned action shows no updatesDependabot doesn’t recognize the actionEnsure the SHA comment includes the version tag (e.g., # v4.2.2) for Dependabot to track
Security alerts but no auto-fix PRsDependabot security updates not enabledEnable under Settings > Code security and analysis > Dependabot security updates

FAQ:

Q: Does Dependabot work with OpenTofu? A: Yes. Dependabot’s Terraform ecosystem support works with both Terraform and OpenTofu .tf files since they share the same syntax.

Q: Can I ignore certain dependencies? A: Yes. Add an ignore block to skip specific packages or version ranges:

updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
    ignore:
      - dependency-name: "aws-sdk"
        versions: ["3.x"]

Q: How is Dependabot different from Renovate? A: Both keep dependencies current. Dependabot is built into GitHub and requires no installation. Renovate (by Mend) offers more configuration options, grouping strategies, and works across multiple Git platforms. For most GitHub-hosted projects, Dependabot is the simpler starting point.


What’s Next

Next post: Monorepos vs Polyrepos. We’ll dig into the tradeoffs of keeping everything in one repository versus splitting across many — and how your choice affects CI/CD, code ownership, and dependency management.

Happy automating!