Terraform Secret Exposure: Preventing Credential Leaks
Secret exposure occurs when credentials, tokens, or sensitive data leak into Terraform state files, CI logs, plan output, variable defaults, or artifact storage. This is a critical security failure mode.
Symptoms of Secret Exposure
- Secret values appear in plan output or logs
- Credentials are defined in variable defaults
- Sensitive outputs are printed in CI
- Generated passwords are stored in state unintentionally
Exposure Paths
- Hardcoded defaults in
variables.tf - State persistence — secret-bearing resources whose values are persisted in state
- Logging —
terraform showoutputs without redaction - Artifact retention — policies that keep plan/state exports too long
Prevention Baseline
- Never set secret defaults in code
- Source secrets from managed secret stores at runtime
- Mark secret variables and outputs as
sensitive = true - Restrict state backend access aggressively
- Avoid publishing raw plan JSON as broadly accessible artifact
Runtime Patterns (Preferred Order)
- External secret manager lookup (AWS Secrets Manager, HashiCorp Vault, etc.)
- Workload identity federation for providers
- Short-lived credentials from trusted broker
Avoid long-lived static credentials in repository or runner config.
Understanding sensitive and write_only
sensitive = truemasks display but the value can still exist in state depending on provider behaviorwrite_onlyarguments (where supported) reduce state persistence risk- Always verify provider docs before assuming secret material is excluded from state
Good Example: Secret Manager Integration
variable "db_admin_username" {
description = "Database admin user"
type = string
}
data "aws_secretsmanager_secret_version" "db_password" {
secret_id = "prod/db/admin"
}
resource "aws_db_instance" "core" {
identifier = "core-db-prod"
username = var.db_admin_username
password = jsondecode(
data.aws_secretsmanager_secret_version.db_password.secret_string
)["password"]
}
Bad Example: Plaintext Default
variable "db_password" {
type = string
default = "ChangeMe123!" # NEVER do this
}
Rotation Playbook
- Create new secret version in manager
- Update application to consume new version
- Roll infrastructure safely
- Revoke old credential
- Verify no leaked copies remain in logs/artifacts
LLM Mistake Checklist
Common model mistakes the Terraform skill corrects:
- Assumes
sensitivealone means "not in state" - Proposes plaintext defaults for demo convenience
- Uses outputs that expose full connection strings in PR comments
- Forgets artifact retention and access controls in CI
Verification Commands
terraform plan -out=plan.bin
terraform show -json plan.bin > plan.json
# Ensure secret fields are not emitted to shared artifacts/logs