Terraform secrets in state and plans: redaction, persistence and access
Understand where Terraform persists sensitive values, why CLI redaction is insufficient, and how ephemeral values, backend permissions and artifact retention change exposure.
TL;DR: Treat ordinary Terraform state and saved plans as sensitive artifacts.
sensitive = truechanges display behavior; keeping a value out of persistent artifacts requires a supported ephemeral or write-only path, plus controls over every other place the value travels.
Follow one value through the run
A database password may enter Terraform through a CI variable, be passed to a provider and appear in resource state. Marking the variable sensitive reduces accidental display in ordinary CLI output. It does not encrypt the password inside a local state file. Machine-readable commands such as terraform show -json can reveal sensitive values, so a job that uploads their output can undo the protection of a masked console.
HashiCorp distinguishes display redaction from omission in its sensitive-data guidance. Saved plans also need protection: the plan file documentation warns that the file contains information including input values and can hold sensitive data in cleartext.
The diagram follows a conventional persisted value. An ephemeral path changes which arrows exist, but the provider and the destination system can still receive the value. Secret handling must account for both.
Reproduce redaction without a real credential
In a disposable local directory, save this as main.tf. The value is an intentionally non-secret teaching marker. This configuration needs no cloud provider and declares only an output:
variable "demo_value" {
type = string
sensitive = true
default = "DEMO-NOT-A-CREDENTIAL"
}
output "demo_value" {
value = var.demo_value
sensitive = true
}
Run the following in that directory. Keep this exercise separate from any real Terraform checkout:
umask 077
terraform init -input=false
terraform plan -input=false -out=demo.tfplan
terraform show -json demo.tfplan > demo-plan.json
Inspect demo-plan.json locally and find the marker. The ordinary plan display hides the output value, while the saved plan's JSON exposes it. The exercise applies nothing and creates no remote infrastructure. Delete the disposable directory afterward. With a real secret, do not paste the JSON into a ticket to prove the same point; record whether a protected check found it.
Reduce persistence where support exists
Terraform introduced ephemeral values in 1.10 and write-only managed-resource arguments in 1.11. Their use depends on the Terraform version, provider schema and allowed expression context. Ephemeral values can feed supported temporary contexts and write-only arguments; they cannot simply replace every ordinary persisted resource attribute. A root-module output cannot be declared ephemeral. Verify these restrictions against the language documentation and the installed provider before designing the handoff.
| Control | Benefit | Remaining exposure |
|---|---|---|
| Sensitive marking | Redacts supported display paths | Ordinary state, plans and JSON inspection |
| Ephemeral/write-only path | Omits supported values from state and plan persistence | Destination, provider behavior and external logs |
| Encrypted remote backend | Protects storage and centralizes access | Authorized readers and copied artifacts |
| Short-lived provider identity | Reduces long-lived CI credential storage | Permissions held during the valid session |
Prefer workload federation for provider access where supported, so CI does not require a long-lived cloud key. That choice does not automatically remove application passwords managed by the same Terraform run; they are different values with different lifecycles.
Handle a leaked plan as an exposure
Imagine a fictional pipeline uploading its saved production plan into an artifact store readable by every repository contributor. The console contains no plaintext password, but the artifact contains a database credential. Restrict the artifact and investigate who could retrieve it. Rotate or revoke the exposed credential according to its actual use, then verify consumers and database access. Deleting the visible file alone cannot invalidate a copied credential.
Include backend object versions, backups and downloaded plans in the exposure inventory. Retention supports disaster recovery but also preserves older secrets. Set a documented retention policy and access boundaries instead of disabling backups to make the current state file appear safer. Encryption at rest helps against storage theft; it does not prevent an authorized artifact reader from receiving decrypted bytes.
An interview answer should name the concrete access path: who can download state, who can read CI artifacts, and which principal decrypts them. “It is in a private bucket” leaves all three unanswered.
Self-check: a variable is sensitive, the backend is encrypted, and a job publishes terraform show -json as a public diagnostic. Is the value protected? No. The diagnostic is a new disclosure path. Remove that publication path, review the affected output and rotate any actual credential exposed; neither the display flag nor backend encryption protects a plaintext copy elsewhere.