Pin every Terraform module to a fixed registry version or a git ref, and prefer a full commit SHA when the source is a repository you do not control. Without a pin, Terraform uses the repository default branch, so upstream changes can reach your next plan unreviewed.
Why modules deserve attention
A module can create IAM roles, open networks and run code at plan time through its providers. Your root configuration trusts it completely. The provider lock file does not cover this, because it tracks only provider dependencies.
The two source types
Registry modules use a version argument with a version constraint. An exact version such as 5.1.2 fixes it. A range such as ~> 5.1 allows newer releases within the range.
Git modules do not use version. HashiCorp's documentation says Terraform clones the repository and uses the default branch unless you specify otherwise. You choose a revision with the ref query parameter, which accepts a branch, SHA-1 hash or tag. The documentation shows examples pinned to a tag and to a full SHA-1.
Comparing the options
| Pin style | Moves silently? | Notes |
|---|---|---|
| No ref (git) | Yes, tracks default branch | Avoid |
| Branch ref | Yes, branch moves | Avoid for anything outside your team |
| Tag ref | Only if the tag is moved | Readable, but tags can be re-pointed |
| Full commit SHA | No | Immutable, harder to read |
| Exact registry version | No, per registry rules | Easy to review |
| Registry version range | Within the range | Upgrades arrive on init |
A tag is a reasonable choice for modules owned by your own organization where tag changes are controlled. For outside repositories, the SHA is the safest option, with a comment noting which release it matches.
Handling the checksum gap
HashiCorp's module sources page does not mention checksums or integrity checks for module sources. That means no lock file will warn you if a tag is re-pointed or a registry version is replaced. Pinning narrows the window, and review fills the rest:
- Review the module diff when you bump a pin, not just the pin line.
- Vendor or mirror modules you depend on heavily.
- Prefer modules from publishers you can name, with active maintenance.
Variables in source
Input variables used in source or version must set const = true, because Terraform has to resolve the source during terraform init. This also means module versions cannot be changed casually through run time variables, which helps.
A workable standard
- Every module call has an explicit pin.
- External git modules pin to a SHA with a comment.
- Internal modules pin to a tag released through your own process.
- Bumps arrive as their own pull request with the module diff attached.
- A lint rule or policy fails any module block without a pin.
Safeguard's policies and gates can enforce rules like step five at pull request time, and its dependency scanning can list the modules and providers in a repository.
Quick check
Does a tag pin guarantee the code never changes?
No. A tag can be moved by whoever controls the repository. A full commit SHA cannot.
Can I use the version argument with a git source?
No. Per HashiCorp, git sources use the ref query parameter. The version argument is for registry modules.
Sources
Last reviewed: 19 October 2026.