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

URL: https://www.devopsness.com/blog/terraform-lock-file-supply-chain-attack
Author: Kiril Urbonas
Published: 2026-09-19
Updated: 2026-10-31
Categories: Infrastructure
Tags: Terraform, Security, DevSecOps, Iac, Infrastructure

Summary: 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.

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

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]
> 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](/blog/npm-supply-chain-ci-hardening): 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:

```bash
# 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](/blog/terraform-module-versioning-shared-registries). Our notes on [fixing provider version conflicts](/blog/fix-terraform-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

- [SentinelOne Labs: TraderTraitor backdoors resurface on victim with no crypto ties](https://www.sentinelone.com/labs/dont-call-us-well-call-your-apis-tradertraitor-backdoors-resurface-on-victim-with-no-crypto-ties/)
- [Terraform dependency lock file reference](https://developer.hashicorp.com/terraform/language/files/dependency-lock)
- [Terraform CLI configuration file: provider installation](https://developer.hashicorp.com/terraform/cli/config/config-file)
- [terraform providers lock command](https://developer.hashicorp.com/terraform/cli/commands/providers/lock)

