The Terraform Lock File Is Code: Review It Before You Init
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 is mailing DevOps candidates Terraform take-home repos whose lock file points at a fake registry. terraform init then runs the attacker's provider.
Treat .terraform.lock.hcl as executable input, because that is what it is. SentinelOne Labs describes a campaign attributed to TraderTraitor (also tracked as UNC4899), a DPRK-linked Lazarus subgroup, in which fake job interview repositories carry a weaponized lock file. When the candidate runs terraform init, Terraform treats the attacker's registry as the source of truth and installs a provider that is really a backdoor loader. The fix is cheap: read the hostnames in the lock file before you init, and configure Terraform so it can only install from hosts you chose.
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, such as registry.hashicorp-aws[.]com. 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. 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.
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
$ 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.
# ~/.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 method, so init has nothing to install it with. 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, 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. 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.
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
initin CI? Add a host allowlist check as the first step and fail the build on any host outsideregistry.terraform.ioor your own mirror. - Managing engineer laptops or runners? Ship a
~/.terraformrcwithprovider_installationthat names your mirror and excludes everything else. - Reviewing a pull request that touches
.terraform.lock.hcl? Read the hostname on every changedproviderline, 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.
Get the DevOps Troubleshooting Cheat Sheet
Subscribe and get our free one-page reference for the errors that eat an afternoon — CrashLoopBackOff, OOMKilled, Terraform state locks, and more — plus new guides as we publish them.
EU CRA Article 14: What DevOps Teams Should Wire In Now
Since 11 September 2026 the 24-hour clock for actively exploited vulnerabilities is live. The owner, the decision path, the SBOM lookup, and the runbook you need this month.
Kubernetes Readiness Probes Lie During Rolling Updates
A green readiness probe means the probe endpoint answered, nothing more. The gaps that cause 502s during rollouts, and the Deployment settings that close them.
More from Infrastructure
Explore more articles in this category
Perplexity Left DynamoDB for CobbleDB: When Should You?
Perplexity built its own key-value store because DynamoDB's read path and bill stopped fitting 50 KB search items. Here is the checklist for when leaving is justified.
Redis vs Memcached: Choosing a Cache in 2026
Both are fast in-memory stores, and both get picked by habit more than by requirements. Here is what actually differs and when each one is the right call.
Vault vs AWS Secrets Manager vs Doppler: Choosing a Secrets Tool
One is a full secrets platform, one is AWS-native and hands-off, and one is built for developer workflow. Picking by feature list alone misses the real tradeoff.
You might have missed
Evergreen posts worth revisiting.