Skip to main content
RunBook Academy

TerraformXX · Supply Chain: Providers and ModulesProduction Terraform

Evaluating Third-Party Modules

Intermediate⏱ ~14 minbash

What you'll learn

  • Apply a five-axis checklist to evaluate a third-party module before adoption
  • Distinguish maintenance signals that predict a module will survive the next year from signals that predict abandonment
  • Run the evaluation on a cadence: on adoption, on every major version bump, and quarterly
  • Define an approval workflow that signs off the evaluation before any consumer stack pins to the module
  • Identify the cost of an under-evaluated module in concrete incident terms

Prerequisites

Verified against Terraform CLI 1.9.x · OpenTofu 1.7.x · HCL 2.0 · bpg/proxmox provider 0.66+ · hashicorp/local provider 2.5+ · hashicorp/null provider 3.2+ · hashicorp/random provider 3.6+ · hashicorp/http provider 3.4+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-13

Not yet marked complete on this device.

The public Terraform Registry has tens of thousands of modules. Most are unmaintained. A small fraction are production-grade. The difference is not visible from a single webpage; it is visible from a 30-minute review of the source, the maintainer, and the history.

Adopting a third-party module is a supply-chain decision. The cost of an under-evaluated module is not “we picked the wrong library”; the cost is “a maintainer’s account got compromised and our state was recycled”. The evaluation is the cheapest insurance you can buy.

The five axes

Every evaluation runs across five axes. The axes are ordered: each one disqualifies the module if it fails.

1. Maintainer       — who is behind it?
2. Maintenance      — is it being maintained?
3. Surface          — what does it expose?
4. Source           — what is in the code?
5. Licence          — can we use it?

A module that fails axis 1 or 2 is not adopted, regardless of how nice axis 3 looks. A module that fails axis 4 is not adopted, regardless of how nice axes 1-3 look. A module that fails axis 5 is not adopted, regardless of how nice axes 1-4 look.

Axis 1: Maintainer

The maintainer is the actor whose decisions you are trusting. Three signals:

  • Verified publisher. The Terraform Registry marks some publishers as “Verified”. The verification is by HashiCorp and indicates the publisher has signed a partnership agreement. It is not a guarantee of quality, but it is a higher bar than an unverified namespace.
  • Sponsor / employer. A module backed by a company (Cloudflare’s cloudflare/cloudflare, AWS’s terraform-aws-modules) has continuity. A module maintained by one person with no sponsor depends on that person’s available time. The latter is acceptable if the module is small and the person is responsive; the latter is risky if the module is core to your infrastructure.
  • Issue response time. The maintainer responds to issues in days, not months. Open issues from a year ago with no response is a signal of abandonment.
# Severity: READ-ONLY
# Inspect the maintainer's recent activity.
gh repo view terraform-aws-modules/terraform-aws-vpc \
  --json stargazerCount,createdAt,pushedAt,license
{"stargazerCount":3127,"createdAt":"2018-04-12T00:00:00Z",
 "pushedAt":"2026-08-09T15:32:00Z","license":{"key":"apache-2.0"}}

A repository whose pushedAt is more than six months old is a signal. A repository whose pushedAt is in the last week is a stronger signal.

Axis 2: Maintenance

Maintenance is the cadence of releases and the resolution of issues. Four signals:

  • Release cadence. A module that releases a new minor version every quarter is alive. A module that has not released in a year is suspect.
  • Open issues. A module with 30 open issues and no recent close is overloaded. A module with 5 open issues and a steady close rate is healthy.
  • Deprecation discipline. When the maintainer deprecates a feature, do they provide a migration guide? When they remove a feature, do they cut a major version?
  • Terraform core compatibility. When a new Terraform core version ships, does the maintainer update the module within weeks, or does the module drift behind?
# Severity: READ-ONLY
# Inspect recent releases.
gh release list --repo terraform-aws-modules/terraform-aws-vpc \
  --limit 5
v6.0.0  Latest  2026-07-21T10:14:00Z
v5.9.0           2026-05-30T09:02:00Z
v5.8.0           2026-04-12T08:01:00Z
v5.7.0           2026-02-19T11:30:00Z
v5.6.0           2025-12-04T07:45:00Z

Five releases in eight months is a healthy cadence. The v6.0.0 is a major version bump; the CHANGELOG entry explains the breaking change.

Axis 3: Surface

Surface is the API the module exposes to consumers. The smaller and more stable the API, the easier to maintain and the easier to upgrade.

A good module:

  • Has a small number of required variables. Most configuration has a sensible default.
  • Has no optional variables that change behaviour silently. An enable_feature_x = true that flips resources behind the consumer’s back is a footgun.
  • Has a stable set of outputs. Outputs are part of the consumer’s contract; renaming an output is a MAJOR change.
  • Has a README.md that documents every variable, every output, and at least one example.

A bad module:

  • Has 30 variables, half of them required, with no defaults.
  • Has outputs that depend on which optional variables are set. The consumer’s module.X.y fails if var.something is false.
  • Has no README, or a README that lists the inputs and outputs without descriptions.
# A good module's variable signature.
variable "vpc_cidr" {
  type        = string
  description = "CIDR block for the VPC. Example: 10.0.0.0/16."

  validation {
    condition     = can(cidrnetmask(var.vpc_cidr))
    error_message = "vpc_cidr must be a valid CIDR block."
  }
}

The validation block is a contract. The consumer who passes "not-a-cidr" fails at plan time, not at apply time. The validation block is part of the surface.

Axis 4: Source

Source is what is in the code. Reading 500 lines of HCL is not feasible for every adoption; reading the high-traffic paths is.

The review checklist:

  • Resource creation. Which resources does the module create? Are they all expected? A module that creates an S3 bucket the consumer did not ask for is oversharing.
  • IAM permissions. Does the module create IAM roles or policies? If so, are they least-privilege? A module that grants *:* is a security incident waiting for a tag change.
  • Lifecycle hooks. Does the module use lifecycle { create_before_destroy = true } where appropriate? Does it use prevent_destroy on resources the consumer would not want deleted?
  • State assumptions. Does the module assume the state has certain resources already created (an existing VPC, an existing KMS key)? If so, the assumption must be documented.
  • Backdoors. Does the module expose a triggers variable that calls terraform taint on resources? Does the module use null_resource or local-exec to do work that should be in the configuration?
# Severity: READ-ONLY
# Inspect the module's resources.
grep -rE '^resource ' modules/network/ | wc -l
# 8

grep -rE 'Action.*\*' modules/network/ | head
# (no output = no wildcard IAM actions)

Axis 5: Licence

The licence determines whether the module can be used in your organisation, and whether derivative works can be published. Three classes:

  • Permissive. MIT, Apache 2.0, BSD-2-Clause, BSD-3-Clause, MPL 2.0. These allow commercial use, modification, and distribution with attribution.
  • Copyleft. GPL, LGPL, AGPL. These impose obligations on derivative works; AGPL in particular affects network-distributed software. For an internal IaC module the impact is usually minimal, but legal review is warranted.
  • Unlicensed / custom. The repository has no LICENSE file, or the licence is “all rights reserved”. These are not safe to use in a commercial product. The right answer is to find a different module.
# Severity: READ-ONLY
# Confirm the licence.
gh repo view terraform-aws-modules/terraform-aws-vpc \
  --json license --jq '.license.key'
# "apache-2.0"

A repository without a LICENSE file is an unlicensed repository. The default copyright applies: all rights reserved. The legal team will not approve it.

The evaluation record

The evaluation produces a document. The document is committed to the team’s infra-evaluations repository. The document contains:

  1. The module’s source address and version.
  2. The maintainer and the maintenance signals observed.
  3. The surface reviewed: variable count, output count, examples.
  4. The source review notes: resources created, IAM permissions, lifecycle hooks.
  5. The licence.
  6. The approval: who signed off, and on what date.
# Evaluation: terraform-aws-modules/vpc/aws v5.9.0

## Maintainer
terraform-aws-modules, sponsored by Anton Babenko.
Verified publisher. Active since 2018.

## Maintenance
Last release 2026-05-30. Five releases in eight months.
8 open issues, 142 closed in the last year. Terraform 1.9
compatibility confirmed in v5.9.0.

## Surface
18 variables, 8 with defaults. 14 outputs, all stable
since v5.0.0. README documents every input and output.

## Source
Creates: VPC, 6 subnets across 3 AZs, 3 route tables,
2 NAT gateways, Internet gateway, VPC flow logs to
CloudWatch. No IAM. Uses `create_before_destroy` on
subnets. No backdoors.

## Licence
Apache 2.0.

## Approval
Signed off by @platform-lead, @security-lead on 2026-08-13.

The record is the audit trail. When the module’s maintainer publishes a new version, the next evaluation diffs against this one. When the maintainer’s account is compromised, the audit trail tells you which stacks are exposed.

The cadence

The evaluation runs on three triggers:

  1. On adoption. Before any consumer stack pins to the module. The first evaluation is the most thorough.
  2. On every major version bump. When the maintainer publishes v6.0.0, the module is re-evaluated. The CHANGELOG entry is the trigger; the audit confirms whether to upgrade.
  3. Quarterly. A spot check of every third-party module in use. The check asks: has the maintenance signal changed? Has the licence changed? Has the source moved?

The cadence is the discipline. Without it, the first evaluation is the only evaluation; the module drifts without anyone noticing.

The approval workflow

The approval is two roles:

  • Platform lead. Signs off on axes 1-3 (maintainer, maintenance, surface). The platform lead is responsible for the module’s place in the architecture.
  • Security lead. Signs off on axes 4-5 (source, licence). The security lead is responsible for the module’s blast radius.

Both sign-offs are required. A single sign-off is a single point of failure; the second reviewer is the gate.

The sign-off is recorded in the evaluation document. The document is the artefact the auditor reads.

The cost of under-evaluating

Concrete failure modes of a module that was not evaluated:

  • Wildcard IAM in iam module. A team adopts a third-party IAM module without reading the source. The module grants Action: "*". A consumer’s workload assumes that role; an attacker assumes that role. The blast radius is the entire AWS account. Recovery is account-wide.
  • Backdoor in database module. A module maintains a null_resource that calls local-exec to write a public SSH key to the bastion host. The attacker holds the bastion. Recovery is key rotation, host rebuild, and a forensic review of every SSH session.
  • Abandoned network module. A module’s maintainer walks away. The next Terraform core release breaks the module. The consumer is stuck on the old core. Recovery is a fork or a rewrite.
  • Licence violation in frontend module. The module’s licence changes from MIT to a commercial licence. The consumer is out of compliance. Recovery is a rewrite or a paid licence.

Each of these is a real incident class. Each is prevented by a 30-minute evaluation that costs less than the post-mortem.

Production failure modes

  1. Evaluation skipped for “trusted” publishers. A team adopts a Verified Publisher module without reading the source. The module grants Action: "*" because the maintainer assumed a permissive environment. The fix is to evaluate every module, regardless of publisher.

  2. Major bump not re-evaluated. A module ships a v6.0.0. The consumer’s ~> 5.0 is unaffected, so the team does not re-evaluate. The new version introduces a feature that calls a paid AWS API; the consumer’s bill spikes. The fix is to evaluate every major version, not just the ones the consumer pins.

  3. Quarterly audit skipped. The team runs an evaluation on adoption and never again. Eighteen months later, the module’s maintainer account is compromised. The team’s quarterly audit would have caught the maintenance signal change. The fix is to enforce the cadence.

  4. Single-reviewer approval. The platform lead approves a module without security review. The module has wildcard IAM. The fix is to require both sign-offs.

  5. Evaluation record not committed. The evaluation happens in a Slack thread. The audit trail is in DMs. The fix is to commit the record to the infra-evaluations repository.

  6. No follow-through on the diff. A new major version is released; the team notes it in the evaluation record but never re-evaluates. The fix is to require an updated evaluation before the consumer bumps the constraint.

Security implications

  • The evaluation is the supply-chain gate. A team that evaluates every module on adoption has a known risk surface; a team that does not has an unknown one.
  • The source review is the only place where wildcard IAM or backdoors are caught. A 30-minute review is the cheapest detection mechanism available.
  • The licence review is the legal gate. A module whose licence is unknown is a legal risk regardless of its technical merit.

Performance implications

  • The evaluation takes 30 minutes for a small module, two hours for a large one. The cadence is quarterly, so the cost is bounded.
  • The CI pipeline can automate parts of the evaluation: tfsec for security signals, checkov for compliance, tflint for style. The automation produces the inventory; the human produces the judgement.

What comes next

The next lesson covers the specific risks of third-party modules in production: how a registry compromise spreads, how an unpinned version recycles your plan, and how to recover from a supply-chain incident.

Verification

  • Every third-party module in use has an evaluation record committed to infra-evaluations.
  • Every evaluation record has two sign-offs: platform and security.
  • The cadence is enforced: a quarterly check, plus a re-evaluation on every major bump.
  • The evaluation record is updated within 30 days of any major version release from a tracked module.
  • No module in use has a wildcard IAM grant in its source.

Knowledge check · 7 questions

  1. Q1. Which axis of the evaluation is a hard veto?

  2. Q2. When should a third-party module be re-evaluated?

  3. Q3. Verified Publisher status on the Terraform Registry is a partnership signal, so a module carrying it still needs a source review before adoption.

  4. Q4. Who must sign off on a third-party module evaluation?

  5. Q5. Which of the following are maintenance signals for a third-party module? (Select all that apply.)

  6. Q6. A module's source creates an S3 bucket the consumer did not request. What does this tell you?

  7. Q7. An 18-month-old evaluation record exists for a third-party module. Today the maintainer's account is compromised and a malicious version is published. What control limits the blast radius?

Passing score: 75%. Answers are checked in this browser.