← Back to Terraform Plan Secret Exposure Check

Why a Terraform plan can print a secret even though the schema has a sensitive flag

The mechanism

Terraform marks an attribute's value sensitive in two places: the provider's own schema (Sensitive: true on the field definition in the provider's Go source) and, for a value that flows in from a variable or another resource, propagated sensitivity through the graph. When either is set, terraform plan's human-readable output and the JSON from terraform show -json both replace the value with (sensitive value) or mark the corresponding path in before_sensitive/after_sensitive as true.

That only works if the schema author remembered to mark the field. It is a manual annotation on every attribute of every resource a provider exposes, and providers expose thousands of them. HashiCorp's own tracker carries recurring reports of exactly this gap: a provider adds a new attribute that can hold a credential -- a connection string, a generated password, a private key -- and it prints in plaintext until someone notices and files a fix (see, for example, hashicorp/terraform#16554, #26185, and #27577 for three separately-reported instances of this same shape of bug).

Where the plaintext value actually goes

A missed sensitive flag does not just affect the terminal running terraform apply interactively. The same value prints into:

That is a much wider audience than the apply itself ever reaches, which is what makes a missed flag more consequential than the underlying credential simply existing in state (state has its own, separate handling and risks).

What this tool looks for, and why those four shapes

Rather than trying to guess which fields "look important", this checks every string value left in the plan (after Terraform's own sensitivity marks are respected) against four fixed, high-confidence patterns:

These were chosen over a generic "random-looking string" heuristic on purpose: a fixed format has an extremely low false-positive rate (an AMI ID, a hash, or a UUID does not coincidentally match an AWS key prefix), so a flag from this tool is worth acting on rather than something to learn to ignore. The cost is that a secret in a shape none of these four match will not be caught -- this is one gate, not a guarantee.

What this is not

This is not a Terraform state scanner, a secrets-manager audit, or a policy engine like Sentinel or OPA. It reads exactly one file -- the structured plan JSON you already have from your own terraform show -json -- and reports pattern matches against unmarked values in it. It makes no claim about your state file, your provider's source code, or whether a value it does not flag is actually safe.

Common questions

Why doesn't Terraform mark every secret-holding attribute sensitive automatically?

Because sensitivity is a manual annotation the provider's schema author adds to each attribute, not something Terraform can infer from a value's shape at plan time. Thousands of attributes across hundreds of providers means misses are a documented, recurring bug class rather than a rare edge case.

Does a missed sensitive flag only matter for an interactive terraform apply?

No. The same plaintext value prints into whatever CI log captured the plan step, a bot's pull-request comment, or a saved plan file shared to debug an issue — a much wider audience than the apply itself ever reaches.

Why check for only four secret shapes instead of any long random-looking string?

A fixed format — an AWS key prefix, a PEM header, a JWT's three dot-separated segments, a URL's embedded credentials — has an extremely low false-positive rate, so a flag is worth acting on. A generic random-string heuristic would flag AMI ids, hashes, and UUIDs constantly and teach people to ignore the report.