skip to content

A Terraform configuration generates a database password with the random_password resource and passes it to an RDS instance. After apply, where does that password exist in plaintext, and why does marking the corresponding output sensitive = true not change that?

level: middleimportance: must knowfreq 75%

answer

  1. state caches every attribute, unconditionally
  2. the marker governs display, not storage
  3. backup file and saved plan carry it too
  4. protection comes from the backend, not the flag
  5. the strongest fix is never storing it

basics

~20 s

Terraform state caches resource attributes exactly as configured or returned, so the generated password sits in plaintext in the state file, its local backup and any saved plan. sensitive = true only redacts CLI output; it never encrypts or omits storage.

solid answer

~50 s

Terraform's state is a JSON document that records, for every resource, its real identifier plus a cached copy of its attributes — and that caching is unconditional. The value produced by `random_password` is stored under its `result` attribute, and the value handed to the RDS instance's `password` argument is stored too, both in the clear. The same plaintext appears in `terraform.tfstate.backup`, in a saved plan created with `terraform plan -out`, and in anything derived from them such as `terraform show -json` or `terraform state pull`. The `sensitive = true` marker is a *display* feature: it makes Terraform print `(sensitive value)` in plan diffs and refuse to print the output without `-raw`. It changes nothing about what is written to disk or to the backend. The real protections are backend access control and encryption at rest, keeping state out of Git, and — better — never routing the secret through Terraform at all.

code

json · 18 lines
json
{
  "resources": [
    {
      "mode": "managed",
      "type": "random_password",
      "name": "db",
      "instances": [
        {
          "attributes": {
            "length": 32,
            "special": true,
            "result": "Qk3vT8sZrLp9wXn2"
          }
        }
      ]
    }
  ]
}

go deeper

for a junior

Recall the headline fact and say it without hedging: Terraform state is plain-text JSON, so any password it manages is readable by anyone who can read the file. Know that state must never be committed to Git.

for a middle

Explain the mechanism — state caches configured arguments and computed attributes so the next plan can diff against them — and be precise that the sensitive marker only redacts CLI output while state, backups and saved plans keep the raw value.

for a senior

Show you reason about the whole exposure surface: prior object versions in a versioned bucket, saved plan artifacts in CI, verbose provider logs. Then give the mitigation ladder ending in provider-managed or write-only secrets rather than stopping at encryption.

for a principal

Own the policy: decide which classes of secret are allowed to pass through Terraform at all, who is trusted with state read access given that it is equivalent to credential access, and how you enforce that across many repositories and pipelines.

## Why the secret is in state at all Terraform state is the record that lets Terraform answer "what do I already manage, and what does it currently look like?". For each resource address it stores the provider that owns it, the real object's identifier, and a cached copy of the resource's attributes — both the arguments you configured and the values the provider computed. That cache is what the next plan diffs your configuration against. There is no filtering step in that write. If a resource has an attribute whose value is a secret, the secret is what gets cached. `random_password` computes its generated string into `result`; `aws_db_instance` persists the `password` argument you gave it. Both land in the JSON as ordinary strings: ```json "attributes": { "length": 32, "result": "Qk3vT8sZr..." } ``` This is documented behaviour, not a bug: HashiCorp states plainly that Terraform stores state values in plain text and that anyone with access to state must be treated as having access to the secrets in it. ## What the sensitive marker actually does `sensitive = true` on a variable or output — and the automatic sensitivity that some provider attributes carry — is about *rendering*. Its effects are: - plan and apply diffs show `(sensitive value)` instead of the string; - `terraform output <name>` prints `<sensitive>`, and you must ask for `-raw` to get the value; - sensitivity propagates through expressions, so a value derived from a sensitive one is also redacted. Its non-effects are the ones that matter here: it does not encrypt anything, does not omit anything from state, and does not stop `terraform output -json` from including the value (it merely tags it). Treating the marker as a storage control is the single most common misconception on this topic. ## Every place the plaintext copy ends up Thinking of "the state file" as one object under-counts the exposure: - `terraform.tfstate` and the automatically written `terraform.tfstate.backup` when running with local state; - the object in the remote backend — and, if the bucket has versioning enabled, *every previous version* of that object; - a saved plan file from `terraform plan -out=tfplan`, which embeds the prior state plus planned attribute values and is not encrypted; - anything derived from those: `terraform show -json`, `terraform state pull`, CI job artifacts, a developer's working directory; - provider debug logs, since `TF_LOG=trace` can record request and response bodies. Rotating the credential later does not retroactively clean any of these copies. ## What actually protects it Three layers, in increasing order of strength. **Contain it.** Never commit state or plan files. Use a remote backend with encryption at rest and a tight access policy, so the set of identities that can read state is the set you would trust with production credentials. Prefer short-lived pipeline credentials over static keys. **Reduce what is in there.** Do not pass secrets through Terraform when the platform can generate and hold them itself. For RDS, `manage_master_user_password = true` has AWS create and store the master password in Secrets Manager, so no password value ever traverses your configuration or state. **Keep it out of state structurally.** Terraform 1.10 added ephemeral values and 1.11 added write-only resource arguments, which accept a value for the duration of a single apply and are never persisted to state or to a plan file. Where a provider implements them, this is the only mechanism that makes the plaintext genuinely absent rather than merely protected. It is also worth knowing the ecosystem contrast: OpenTofu added client-side state encryption in 1.7, so its state can be encrypted before it leaves the machine. Terraform has no equivalent — its at-rest protection is entirely whatever the backend provides. ## The interview answer Say where the value lives (state, backup, saved plan, every prior object version), say what the marker does (redaction, not storage), and then name the mitigation ladder: access control on the backend, no state in version control, provider-managed secrets, and ephemeral or write-only arguments where available. A candidate who says "we mark it sensitive so it is safe" has answered the display question and missed the storage question entirely.

  • Beyond the state object itself, where else does that same plaintext value end up?
    In `terraform.tfstate.backup`, in every prior object version if the state bucket is versioned, in a saved plan file from `terraform plan -out`, in anything derived from them (`terraform show -json`, `terraform state pull`), in CI job artifacts, and potentially in provider debug logs when `TF_LOG` is set to a verbose level.
  • If the password must not be in state at all, what are your options?
    Stop routing it through Terraform. For RDS, `manage_master_user_password = true` has AWS generate and hold the master password in Secrets Manager, so Terraform never sees it. Otherwise use write-only arguments (Terraform 1.11+) or ephemeral values (1.10+), which accept a value for one apply and persist nothing to state or plan.
  • Does using a remote backend instead of a local file solve this?
    It improves it but does not solve it. A remote backend gives you somewhere to apply encryption at rest, access policy and audit logging, which a laptop file does not have. The object still contains plaintext, so anyone who can read the object can read the secret — the control is authorization, not confidentiality of the contents.

saying these in an interview costs you the question

  • Says sensitive = true encrypts the value inside the state file
  • Believes Terraform automatically strips passwords out of state
  • Thinks only outputs land in state, not resource attributes
  • Assumes a remote backend means the secret is unreadable
  • Forgets the local terraform.tfstate.backup holds the same plaintext

context