Sensitive Data in State
Terraform state stores resource attributes in plaintext, and some of those attributes are secrets — RDS master passwords, generated private keys, access tokens. Marking a value sensitive hides it from CLI output and does nothing whatsoever to the bytes on disk.
Protecting secrets in Terraform is therefore not about the sensitive flag. It is about controlling the state backend — encryption, access, audit — and minimizing how many secrets ever land in state to begin with.
What Ends Up in State
State records every attribute a resource exposes, including the ones you most want hidden. An aws_db_instance with an inline password, a tls_private_key's PEM, a random_password result — all of them sit in the state file in the clear, exactly as the provider returned them. There is no per-attribute filter that keeps a "secret" attribute out of state; if Terraform manages the resource, the secret is in its state.
The sensitive Flag's Real Scope
The sensitive flag controls display, not storage. On a variable or an output it replaces the value with (sensitive value) in plan and apply output and in terraform output, which keeps the secret out of your terminal and out of CI logs. That is genuinely worth doing — leaked logs are a real exposure path — but it stops at the screen. The same value is written to state in full plaintext, so anyone who can read the state file reads the secret regardless of the flag.
output "db_password" { value = aws_db_instance.main.password sensitive = true # hides it from CLI/logs; still plaintext in state }
Securing the Backend
Because the flag does not protect the file, the file itself is what you protect. On an S3 backend that means encryption at rest with SSE-KMS, a bucket policy that grants read to the smallest possible set of principals, blocked public access, and access logging so you can see who read state. KMS encryption is the part that actually keeps the plaintext secrets from being readable by anyone who merely lists the bucket. This is the real control; sensitive is cosmetic by comparison.
Minimizing Secrets in State
The strongest move is to keep secrets out of state in the first place. Rather than having Terraform generate a database password with random_password and hold it forever, store the secret in SSM Parameter Store or Secrets Manager and have the application read it at runtime. Terraform manages the secret's container and access policy, not the secret value, so the plaintext never lands in state. Every secret you keep out of state is one you do not have to encrypt, lock down, and rotate after a leak.
# Terraform manages the secret container, not the value data "aws_secretsmanager_secret_version" "db" { secret_id = "prod/db/password" } resource "aws_db_instance" "main" { username = "admin" password = data.aws_secretsmanager_secret_version.db.secret_string }
OpenTofu State Encryption
This is one of the genuine divergences between the two tools. OpenTofu ships client-side state encryption: it can encrypt the state file's contents before they ever reach the backend, so the secrets are not plaintext even inside the bucket. Terraform does not have this yet — its protection stops at the backend's own encryption at rest, which the cloud provider holds the keys to. If client-side state encryption is a hard requirement, that is a concrete reason to weigh OpenTofu.
- Believing
sensitive = trueencrypts the value, then leaking it through a state file stored without encryption. - Generating database passwords with
random_passwordand leaving them in state with no backend encryption or access control. - Outputting a secret without
sensitive, printing it into CI logs that are retained and searchable. - Granting broad read access to the state bucket, so anyone with bucket read can extract every secret in plaintext.
- Never rotating a secret that lived for months in an under-protected state file, leaving a known-exposed credential in use.
- Encrypt state at rest with S3 and KMS, and lock the bucket down to the smallest set of principals.
- Keep secrets out of state where possible — reference Secrets Manager or SSM at runtime rather than managing the value in Terraform.
- Mark every secret output
sensitiveso it does not print into logs. - Audit and rotate any secret that has lived in an under-protected state file.
- Weigh OpenTofu's client-side state encryption when plaintext-in-the-bucket is unacceptable for your threat model.
Knowledge Check
What does marking a value sensitive actually do?
- Suppresses it from plan, apply, and output display — it does not encrypt the value in state
- Encrypts the value inside the state file using a KMS key you supply
- Keeps the value out of the state file entirely, so that it is never once persisted to disk anywhere
- Rotates the value automatically on every apply that touches the resource
Why is state a secrets-exposure risk even when values are marked sensitive?
- The flag only hides values from output; the same secrets are written to the state file in plaintext
- Sensitive values are encrypted in state, but the decryption key is stored right next to them in the same file
- The flag works only for input variables and never for resource attributes in state
- It is not actually a risk, since sensitive values are omitted from the state file
What most reduces the number of secrets stored in state?
- Storing secrets in Secrets Manager or SSM and reading them at runtime instead of generating them in Terraform
- Marking every single secret output and variable as
sensitiveacross every module in the full configuration tree - Enabling object versioning on the S3 bucket that holds the state file
- Increasing the parallelism setting so that state is written out less often
How do Terraform and OpenTofu differ on state encryption?
- OpenTofu offers client-side state encryption; Terraform relies on the backend's encryption at rest and lacks it
- Terraform encrypts state client-side, while OpenTofu has no such feature and relies only on the backend's at-rest encryption
- Both tools encrypt the state file client-side automatically by default
- Neither tool can encrypt state at all; both store it in plaintext only
You got correct