AI-generated image: Two cyan grid planes with cyan cubes on top, red cube in middle section between planes
Image generated by AI

A Terraform CVE and the File Nobody Reviews

I have not looked at my .terraformignore file in about two years, and until a CVE in August I did not think that was a problem.

I wrote it once, when I first set up a pipeline that pushes configuration to Terraform Cloud, copied a few lines from a colleague’s repo, added *.tfstate and .terraform/, and never opened the file again. It has sat there since, doing whatever it does, and I genuinely could not have told you with confidence that it still worked.

Terraform v1.15.9, released on 19 August 2026, patches CVE-2026-14978, a Unicode normalization bug in go-slug, the library that packages a Terraform configuration directory before it gets uploaded anywhere. The bug let certain files bypass .terraformignore entirely: a filename that normalizes differently under Unicode rules could slip past the ignore pattern meant to exclude it, and end up inside the uploaded config package anyway, invisible to anyone who only ever checked that the ignore file existed and had the right lines in it.

The scope is narrower than that sounds, and it is worth being exact about. HashiCorp’s bulletin limits it to affected macOS filesystems, to non-ASCII filenames or patterns that differ only in their Unicode normalization form, and to someone who already has local control over the working directory. go-slug v0.4.0 through v0.18.2 are affected, and v0.18.3 fixes it. If your pipeline runs on Linux and your filenames are plain ASCII, this particular bug never touched you. The lesson below holds either way.

OpenTofu published its own release the same day, v1.12.6, fixing separate issues in OCI registry credential and URL resolution. Different bug, same day, no relation to the .terraformignore problem, worth knowing about if you run OpenTofu but not part of this story.

What .terraformignore actually does

It is a plain text file, one pattern per line, syntax borrowed from .gitignore. When Terraform packages your configuration directory for upload, to a Terraform Cloud run, a module registry, anywhere a config gets bundled and sent somewhere, it skips anything matching a pattern in that file. State files, .tfvars with secrets in them, local .terraform provider caches, anything you do not want leaving your machine as part of the package.

It only runs at package time, not at plan time or apply time, which is part of why it is so easy to lose track of. You do not see it act. There is no log line that says “excluded 4 files,” there is just an upload that either does or does not contain something it should not, and the only way to know which is to go look.

Most repos I have looked at have one, written once during setup, and never opened again. That is exactly the profile of file where a bypass bug does the most damage: nobody is watching it closely enough to notice when it stops working.

The actual lesson is bigger than this one CVE

The parts of an infrastructure repo that get reviewed are the parts that change often: the resource definitions, the variable files, anything a pull request touches on a normal week. The parts that never get reviewed are the ones written once and trusted forever. .terraformignore is one example. A .gitignore is another. A credential exclusion pattern in a CI config is a third. None of them come up in a normal code review, because nothing about them looks different from last month, right up until the tool reading them has a bug.

Checking the upload instead of trusting the file

The habit that actually catches this class of problem is checking what is really in the package, not re-reading the ignore file and assuming it worked.

t t e a r r r a - f t o z r f m $ p ( l t a e n r r - a d f e o t r a m i _ l c e o d n - f e i x g i _ t p c a o c d k e a g e ) | s o r t

List the actual contents of what got bundled, and diff it against what you expect to see. If a .tfvars file or a state file shows up in that list despite a matching line in .terraformignore, the ignore file is not doing its job, whatever the reason. This is a five-minute check the first time you set up a pipeline that packages Terraform, and about thirty seconds every time after, and it catches a normalization bug, a mistyped pattern, or a pattern that matched under an old Terraform version and stopped matching after an upgrade, all with the same one check.

I put this check into the pipeline itself now, a step that fails the run if a file matching a small deny list, *.tfstate, *.tfvars, anything under .terraform/, shows up in the packaged output regardless of what .terraformignore says. It is redundant with the ignore file on a good day. On a bad day, it is the thing that actually catches what the ignore file missed, and redundancy is the whole point of a check like this.

Where the design tool fits, and where it does not

Architect generates Terraform from a visual canvas for AWS, Azure, GCP, Hetzner and Cloudflare. RunPod, DeepInfra and Smart Inference are also deployment targets in it, but those three are managed through their own APIs rather than generated Terraform, so a canvas with all eight providers on it is not producing eight sets of .tf files. And generated configuration is still configuration: it goes through the same packaging step, the same .terraformignore, and it still needs a human to read it before it runs anywhere. Nothing about drawing the resources on a canvas instead of typing them by hand changes what leaves your machine when the package gets built. A tool that writes the Terraform for you does not exempt you from checking what actually left the building.