Terraform State Exposure in Bug Bounty: From Leak to Cloud Keys
terraform.tfstate is a treasure map. Resource IDs, outputs, database passwords, and sometimes raw provider credentials land there. When state is public in an S3 bucket, committed to git, or downloadable from a CI artifact, you are holding infrastructure memory the target never meant to share.
Where state leaks
- S3 backend buckets without encryption gates or with public ACL leftovers
- HTTP backends and GitLab / GitHub stored artifacts named
tfstate - Old commits; force-add exceptions around
*.tfstate - Debug endpoints that zip the whole repo including state
- Misconfigured Terraform Cloud run logs echoing outputs
- Copied state files in support tickets or public Notion exports linked from docs
Search for "version": 4 JSON, terraform_version, and resource addresses like aws_db_instance. Unique strings beat generic password greps. Partial state from a failed apply can still hold usable outputs.
Read it like evidence, not loot
Download only what you need. Prefer outputs and resource attributes that establish severity: an RDS password for a staging box in scope, an HMAC for webhooks you can test against your own callback, or an AKIA key. Strip unrelated customer PII from your notes.
If the state contains cloud keys, GetCallerIdentity is the clean proof. If it contains a database URL, connect only if the program allows and only to a non-production instance—or demonstrate login with a canary user the program provides. Do not run terraform destroy "as a joke." That is how hunters get banned.
Provider blocks sometimes inline tokens. Those deserve the same minimal validation path as access keys found elsewhere. Workspace-specific backends (one state file per env) help contain damage—note which workspace you opened so remediation can prioritize prod first.
Partial JSON from a truncated download can still contain outputs while missing resources. Do not wait for a perfect file if the outputs alone prove credential exposure. If the backend uses DynamoDB locking, an open lock table is not the same as state read access—do not conflate them in the writeup.
State that is encrypted still matters
Backend encryption at rest does not help if your anonymous principal can read the object. KMS-encrypted objects that still return ciphertext to the world are a different bug. Focus on unauthorized read of usable plaintext.
Versioned buckets may retain old state after a panic rotation. Check whether previous object versions still reveal the old secret. Mention versioning in remediation if they do. Soft-deleted Git commits are not gone either—mirror clones keep history.
Write the report
Explain discovery path, object URL or commit hash, which fields matter, and the single validation call you made. Recommend private backends with tight IAM, state encryption, no secrets in outputs when avoidable, sensitive = true treated seriously, remote state access logging, and immediate rotation of every secret that appeared in the file. Move teams toward short-lived cloud roles for apply instead of static keys in state.
Terraform state exposure is rarely subtle. Keep your handling boring and your proof sharp so the program can rotate fast.