Terraform

Terraform Sensitive Variables: How Output Masking Works and Real-World Pitfalls (Associate Prep)

2026-04-19
NicheeLab Editorial Team

Terraform's sensitive flag is metadata that controls how values are displayed; it is not encryption. Output to the CLI is masked, but the most important fact to remember is that the value is still stored in state.

The Associate exam loves to test masking behavior, what lives in state, and when to reach for outputs vs. functions. In practice, terraform output -json, verbose logs, and CI handling are where teams most often slip up.

Output Masking Basics: Hidden, Not Erased

Setting sensitive = true on a variable or output masks the value in human-facing terraform plan/apply and terraform output. This is a display control to reduce exposure; it does not encrypt or remove the value.

The key point is that the value is persisted in Terraform state. Local state is kept as plaintext-ish JSON, and remote backends do not encrypt at the application layer either (they rely on backend-side encryption and access control).

  • sensitive controls display suppression, not encryption or secrecy.
  • Interactive prompts for sensitive variables do not echo the value.
  • The diff on plan/apply hides concrete values and shows a masked placeholder.
  • State still contains the value, so storage location and permission design are essential.

Where sensitive values flow and where they get masked

tfvars / ENV(TF_)variable(sensitive)resource argumentterraform plan/apply(human view is masked)terraform console(masked by default)Terraform state(value is stored)terraform output (human)shown maskedWhere sensitive values flow and where they get masked
# terraform output (human-facing)  → shown masked
# terraform output -json           → value is included, but with "sensitive": true flag

Basic definition: marking variables and outputs as sensitive

variable "db_password" {
  type      = string
  sensitive = true
}

# Mark the output as sensitive too
output "db_password_masked" {
  value     = var.db_password
  sensitive = true
}

# Interactive prompts do not echo the value, and plan/apply mask it

Variable vs. Output vs. Function vs. Provider Attribute: Picking the Right Tool

Terraform tangles together the variable/output sensitive attribute, the sensitive()/nonsensitive() functions, and the Sensitive attribute on the provider-side schema. Combining them correctly depending on the goal is what matters.

In particular, when you want to treat a value built up inside an expression (such as a local or template result) as sensitive, tag it explicitly with the sensitive() function. Only strip the flag with nonsensitive() when you genuinely intend to expose the value via an output.

  • variable sensitive: how an input is handled (prompts and output masking).
  • output sensitive: how an output is handled. Blocks unintended exposure.
  • sensitive()/nonsensitive(): tag or untag a computed value as sensitive.
  • Provider-side Sensitive attribute: marks the field as sensitive at the resource/data-source schema level.
FeaturePrimary purposeCLI display / outputStored in state
variable sensitiveMask input valuesHides value on plan/apply; no echo on promptValue is stored
output sensitiveMask output valuesShown masked by terraform outputValue is stored
sensitive()/nonsensitive()Tag or untag an expression as sensitiveAffects human-facing display (mask or expose)Value is stored
Provider-side Sensitive attributeSchema-level secrecySuppresses appearance in plan diffs and logsValue is stored

Example: using the functions appropriately

locals {
  raw        = "user"
  secret     = sensitive("p@ssw0rd")
  combined   = "${local.raw}:${local.secret}"   # combined inherits sensitive
  published  = nonsensitive(local.secret)        # explicitly exposed (use sparingly)
}

output "combined_masked" {
  value     = local.combined
  sensitive = true
}

# Only use nonsensitive() when exposure is genuinely required
output "published_plain" {
  value     = local.published
  sensitive = false
}

CLI Behavior: plan/apply, output, output -json, console

Human-facing terraform plan/apply and terraform output mask sensitive values. terraform console shows (sensitive value) by default and keeps the value hidden until you explicitly expose it with nonsensitive().

terraform output -json is designed for automation: it includes the actual value in the JSON while adding a "sensitive": true flag. Take extra care when piping the result into other tools.

  • plan/apply: concrete sensitive values are not printed; diffs are masked.
  • output: human-facing view is masked. -json may contain the value, so it must be protected.
  • console: defaults to (sensitive value); the value is only visible via nonsensitive().
  • Verbose logs such as TF_LOG can leak sensitive values, so do not run them by default.

CLI example (comments describe expected behavior)

# terraform console
> var.db_password
(sensitive value)
> nonsensitive(var.db_password)
"p@ssw0rd"

# terraform output
$ terraform output db_password_masked
sensitive

# terraform output -json (for automation; handle with care)
$ terraform output -json | jq '.db_password_masked'
{
  "sensitive": true,
  "type": "string",
  "value": "p@ssw0rd"  # value is included as expected; must be protected
}

State and Backends: Encryption Is the Backend's Job, Not Terraform's

Terraform keeps each resource's final attribute values in state. Even sensitive values stay in state. Protect local state with file permissions, and for team use rely on a remote backend with proper encryption and access controls.

Backends such as S3, GCS, and Azure Storage support server-side encryption and KMS integration. Combine that with tight IAM, audit logs, and versioning or legal-hold features to limit the blast radius if something does leak.

  • Add local state to .gitignore and manage it with mode 600.
  • For S3 backend, prefer SSE-KMS and restrict access via bucket policies.
  • State contains sensitive values, so apply permission management and auditing to downloads and backups as well.
  • Fully erasing leaked sensitive history is hard; design around rotation and key management.

S3 backend example (encryption and access control as baseline)

terraform {
  backend "s3" {
    bucket         = "my-tf-state-bucket"
    key            = "env/prod/terraform.tfstate"
    region         = "ap-northeast-1"
    encrypt        = true            # enable SSE
    kms_key_id     = "arn:aws:kms:ap-northeast-1:123456789012:key/xxxx"
    dynamodb_table = "tf-state-lock" # locking recommended
  }
}

# Reference: state retains values like this (conceptual)
# {
#   "resources": [
#     {
#       "type": "example_resource",
#       "instances": [
#         { "attributes": { "password": "p@ssw0rd" } }
#       ]
#     }
#   ]
# }

Best Practices for CI/CD and Logging

In CI, pass variables through TF_VAR_ environment variables or the CI's built-in secrets feature, and keep tfvars files out of the repository. Be especially careful with terraform output -json: mask values and restrict access so they do not leak through logs or artifacts.

Verbose logs (TF_LOG=DEBUG/TRACE) can accidentally include sensitive values. Enable them only temporarily, restrict the destination, and dispose of the output promptly.

  • Use the CI's secrets store and inject via TF_VAR_xxx.
  • When values appear in logs or artifacts, configure masking rules.
  • Keep outputs minimal and short-lived; avoid long-term retention.
  • Integrate with Vault or cloud Secret Manager and prefer dynamic credentials.

GitHub Actions example (secrets and masking)

jobs:
  plan:
    runs-on: ubuntu-latest
    env:
      TF_VAR_db_password: ${{ secrets.DB_PASSWORD }}
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
      - run: terraform init -input=false
      - run: terraform plan -input=false -no-color
      # If automation needs the value, take care: -json may include it; restrict the destination
      - run: |
          terraform output -json \
            | jq -r '.db_password_masked.value' \
            | awk '{print "::add-mask::" $0}'   # mask before using it

Common Pitfalls and How to Verify

Expressions that include a sensitive value generally propagate the sensitive flag. To avoid unintended exposure, mark outputs as sensitive and avoid reaching for nonsensitive(). Only fall back to nonsensitive() when the downstream consumer truly needs the plaintext.

Also, assigning a sensitive value to a non-sensitive output produces an error. That is an intentional safety net against implicit leaks.

  • String concatenation, format(), and templates also propagate sensitivity by default — that is safe. Mixing in nonsensitive() is what exposes the value.
  • The -json family of commands is the easiest leak vector when integrating with other tools. Restrict both transit and storage.
  • Past values that remain in state are not easy to erase. Plan around rotation and key updates.

Examples: blocking accidental exposure and intentionally exposing a value

# NG: assigning a sensitive value to a non-sensitive output (errors at plan time)
# output "leak" {
#   value     = var.db_password        # var is sensitive=true
#   sensitive = false
# }

# OK: keep it masked on output
output "db_password_safe" {
  value     = var.db_password
  sensitive = true
}

# Explicit exposure (only when truly required)
output "db_password_plain" {
  value     = nonsensitive(var.db_password)
  sensitive = false
}

Check Your Understanding

Associate

問題 1

Which statement correctly describes what happens when a Terraform variable is marked sensitive = true?

  1. Values are masked in human-facing CLI output, but they may still be stored in plaintext in state
  2. Values are automatically encrypted in state, so no backend configuration is required
  3. Every provider automatically treats the attribute as sensitive, so the value never appears in plans or storage
  4. terraform output -json always omits the value and returns only the flag, so it is safe to use freely

正解: A

sensitive controls display masking, not encryption. Human-facing CLI output is masked, but the value is stored in state, and encryption and access control are the backend's responsibility. There is no guarantee that the value is omitted from -json output or that providers will never log it either.

Frequently Asked Questions

If I mark a variable sensitive, will it never appear in CI logs?

Human-facing Terraform output is masked, but values can still leak through -json output, third-party tool logs, or verbose logs (TF_LOG). In CI, combine a secrets store, masking rules, and restricted artifact publishing to keep values protected.

How do I remove a past password that is still recorded in state?

Fully erasing history is hard, so the standard answer is to rotate the credential. Drop any unneeded outputs and, if necessary, run terraform state rm to remove the resource from state and recreate it. Cleaning up versioned history on a remote backend depends on backend features such as deletion or legal-hold release.

Should I use the sensitive() function or the variable sensitive attribute?

Use the variable sensitive attribute for inputs, and the sensitive() function for computed or composed values (locals, template results, etc.). Only strip the flag with nonsensitive() when you explicitly need to expose the value through an output.

Check what you learned with practice questions

Practice with certification-focused question sets

無料で問題を解いてみる
Author

NicheeLab Editorial Team

NicheeLab editorial team focused on data engineering and cloud certification learning. Content is structured around practical study needs and official exam domains.


Related articles
Terraform

HCL Syntax: Terraform's Configuration Language (2026)

HCL2 fundamentals for Terraform — blocks, attributes, expres...

Terraform

Terraform Authoring & Operations Pro: Complete Guide (2026)

Tactics for the Terraform Pro exam — module authoring, works...

Terraform

Terraform Providers: Plugin Management Fundamentals (2026)

Provider mechanics — required_providers, versions, mirrors, ...

Terraform

Terraform Resource Blocks: Declarative Infra Units (2026)

Resource block fundamentals — addresses, references, common ...

Terraform

Terraform Data Sources: Read-Only External Data (2026)

Data source basics — declaration, refresh behavior, dependen...

Browse all Terraform articles (102)
© 2026 NicheeLab All rights reserved.