Skip to main content
The Terraform Lock File Is Code: Review It Before You Init

The Terraform Lock File Is Code: Review It Before You Init

KU
· Founder & Chief Editor
• 8 min read•Updated •19 views

A DPRK-linked group is mailing DevOps candidates Terraform take-home repos whose lock file points at a fake registry. terraform init then runs the attacker's provider.

Key takeaways

  • A DPRK-linked group ships fake Terraform interview repos whose .terraform.lock.hcl points at a lookalike registry, so terraform init installs a backdoored provider.
  • Before init, check that every provider host in the lock file is registry.terraform.io or your own mirror.
  • Enforce it with provider_installation in the CLI config and a CI host check.

Treat .terraform.lock.hcl as executable input. A DPRK-linked group is sending fake interview repos whose lock file points at a lookalike registry, so terraform init installs a backdoored provider. Before you init a stranger's repo, read every provider hostname in the lock file, and configure Terraform so it can only install from hosts you chose.

Verified against

Terraform 1.17 (1.17.1, released 2026-10-28), against the developer.hashicorp.com dependency lock file and CLI configuration pages and SentinelOne Labs' TraderTraitor report of September 2026. Checked October 2026.

SentinelOne Labs attributes the campaign to TraderTraitor (also tracked as UNC4899), a Lazarus subgroup. The fix is cheap, and most of it fits in an afternoon.

What the attack actually does

The lure is a take-home exercise for a cloud or infrastructure-as-code role. SentinelOne lists repository names such as terraform-candidate-repo, Northwind-IAC and novacart-interview. Each contains a .terraform.lock.hcl that points provider installs at a typosquatted registry. The published examples are lookalikes of HashiCorp's own domain: registry.hashicorp-aws[.]com, registry.hashicorp-aws[.]io and registry.hashicorp-terraform[.]io. Run terraform init and Terraform downloads the provider and, on plan or apply, executes it as a local process with your user's privileges.

The payloads are two Rust backdoors for Apple Silicon Macs, FLATROOF and ROOFDECK, which harvest browser data, terminal history and keychain contents. SentinelOne found them on the machine of a DevOps engineer at an India-based IT services provider with no cryptocurrency ties. Most of us filed this actor under crypto exchanges and moved on. A cloud engineer with source control access and cloud credentials is a better target than a wallet, and the report's own advice is to treat that group as sensitive for endpoint monitoring.

Why the lock file is the weak spot

Everyone reviews main.tf. Almost nobody reads the lock file, because it is generated, verbose and full of h1: and zh: hashes that look like noise. A reviewer skimming a pull request sees a wall of base64 and scrolls past it.

The lock file also carries the one field an attacker needs. Every entry starts with the full provider source address, hostname included, like provider "registry.terraform.io/hashicorp/aws". Change the hostname and the hashes that follow are hashes of the attacker's package, which will verify perfectly against themselves. Checksums prove that the package you got matches the lock file. They say nothing about whether the lock file itself was honest. HashiCorp's own docs call the model "trust on first use", and a stranger's repo means the first use was theirs.

This is the same trust problem we covered in hardening CI against npm worms: a lockfile pins what you resolve, and it is only as trustworthy as whoever wrote it. When the repo comes from a stranger, the lock file is part of the attack surface, not a defence.

Read the hosts before you run anything

Before terraform init on any repository you did not write, list the registry hostnames the lock file names. The upstream default is registry.terraform.io, and the SentinelOne guidance is to treat anything else as suspect.

Also check required_providers in the .tf files, since a source address can name a host too. Here is a check you can run locally or drop into CI:

scripts/check-lock-hosts.sh
#!/usr/bin/env bash
set -euo pipefail
[ $# -gt 0 ] || exit 0
allowed='^(registry\.terraform\.io|tf-mirror\.example\.com)$'
bad=$(grep -hoE '^provider "[^"]+"' "$@" | cut -d'"' -f2 | cut -d/ -f1 | sort -u | grep -vE "$allowed" || true)
if [ -n "$bad" ]; then
  echo "Unexpected provider registry hosts:" >&2
  echo "$bad" >&2
  exit 1
fi
Bash
chmod +x scripts/check-lock-hosts.sh
find . -name .terraform.lock.hcl -not -path '*/.terraform/*' -print0 | xargs -0 ./scripts/check-lock-hosts.sh
grep -rnE 'source\s*=\s*"[^"]*\.[^"/]+/' --include='*.tf' .

The first command gates the lock files. The last line finds explicit hostnames in source arguments, which you should read by eye. A mismatch against your allowlist is a failed build, and it should fail before any job that holds cloud credentials runs init. Run it as the first step of the pipeline, ahead of the provider download.

Make Terraform refuse the wrong hosts

A grep is a review habit, and habits lapse. The durable control is the CLI configuration file, where provider_installation decides which install methods apply to which providers. Terraform's documentation describes three methods: direct, filesystem_mirror and network_mirror. Each takes include and exclude patterns, and when both are set the exclusions win.

HCL
# ~/.terraformrc
provider_installation {
  network_mirror {
    url     = "https://tf-mirror.example.com/providers/"
    include = ["registry.terraform.io/*/*"]
  }
  direct {
    include = ["registry.terraform.io/hashicorp/*"]
  }
}

With this config, a provider whose address sits on any other host matches no installation method, so init has no permitted way to fetch it and fails. A poisoned lock file turns into an error instead of a compromise. For CI, use the mirror alone and drop direct entirely, so runners never talk to a public registry. Terraform reads this file from your home directory on Unix (terraform.rc under %APPDATA% on Windows, or wherever TF_CLI_CONFIG_FILE points), so bake it into the runner image, not the repository.

Two related habits pay off here. Run terraform providers lock -platform=linux_amd64 -platform=darwin_arm64 when you generate your own lock file, so it records checksums for every platform your team uses and nobody regenerates it from an unknown source. And generate it from a mirror you control. If your org also publishes internal modules, the same host discipline applies to versioning modules in a shared registry. Our notes on fixing provider version conflicts cover the lock file's normal behaviour, which is worth knowing before you start treating diffs in it as security events.

Run untrusted repos somewhere disposable

Everything above still assumes you noticed. For interview exercises and unknown samples, the safer default is not to run them on your laptop at all. Use a throwaway VM or a devcontainer with no mounted home directory, no cloud credentials in the environment, and no access to your SSH agent or keychain. The backdoors in this campaign went after browser data and keychains, and a container that never sees them has nothing to hand over.

Questions people ask

Can a Terraform lock file run malicious code?

Not by itself, but it decides what runs. Each entry names a provider by full address, hostname included. If that host is an attacker's registry, terraform init downloads their provider, and plan or apply executes it as a local process with your privileges. The checksums only confirm you got the package the lock file described, so a forged lock file verifies cleanly.

How do I check which registry a Terraform lock file uses?

Grep the provider "..." lines in every .terraform.lock.hcl and look at the part before the first slash. Anything other than registry.terraform.io or your own mirror is suspect, per SentinelOne's guidance. Also check source arguments in required_providers, since they can name a host too. The script above automates both checks for CI.

Should I commit .terraform.lock.hcl?

Yes, for your own repositories. HashiCorp recommends committing it so every run installs the same provider versions and checksums. The risk is lock files you did not generate. Create yours with terraform providers lock against a registry or mirror you trust, and review hostname changes in pull requests as carefully as code.

The decision, concretely

  • Received a Terraform repo from someone you do not know? Read the lock file hosts first, and run it only in a disposable VM or devcontainer with no credentials.
  • Running init in CI? Add a host allowlist check as the first step and fail the build on any host outside registry.terraform.io or your own mirror.
  • Managing engineer laptops or runners? Ship a ~/.terraformrc with provider_installation that names your mirror and excludes everything else.
  • Reviewing a pull request that touches .terraform.lock.hcl? Read the hostname on every changed provider line, not just the version.

The call we'd make

Ship the provider_installation config to every runner and laptop, run the host check in CI, and stop initializing strangers' repos on a machine that holds credentials. The first two take an afternoon and turn this whole attack class into a failed build. The third is a habit, and it is the one that would have protected the victim SentinelOne found.

Sources

React
Share this post
Cite this article
Plain text

Kiril Urbonas (2026). The Terraform Lock File Is Code: Review It Before You Init. DevOpsNess. Updated 2026-10-31. https://www.devopsness.com/blog/terraform-lock-file-supply-chain-attack

Markdown

[The Terraform Lock File Is Code: Review It Before You Init](https://www.devopsness.com/blog/terraform-lock-file-supply-chain-attack) (Kiril Urbonas, DevOpsNess, updated 2026-10-31)

KU

Kiril Urbonas

Founder & Chief Editor

590 articles
View all articles by Kiril Urbonas

You might have missed

Evergreen posts worth revisiting.