← All articles

Terraform State Files Are Exposing Your Secrets: What to Do About It

June 16, 2026

The Secret Hiding in Plain Sight: Your Terraform State

Infrastructure-as-code has become the default way teams provision cloud resources. Terraform is the dominant tool. But there is a persistent, under-discussed risk baked into how Terraform works: state files store the full plaintext representation of every resource they manage — including secrets, passwords, tokens, and private keys that were passed in as inputs or generated as outputs.

If your terraform.tfstate file — or the remote state backend behind it — is misconfigured, you may already be leaking credentials that grant access to your databases, cloud accounts, and third-party services.

Why Terraform State Contains Sensitive Values

Terraform needs to track the real-world state of every resource it creates so it can plan future changes. That means it records the actual values returned by your cloud provider after provisioning — not just what you passed in.

Common examples of what ends up stored in state:

  • RDS / database passwords — set via password arguments in aws_db_instance or google_sql_user
  • Generated IAM access keysaws_iam_access_key writes both id and secret into state
  • TLS private keystls_private_key stores the private key material in plaintext
  • Initial admin passwords — resources like azurerm_kubernetes_cluster can write kubeconfig data (including credentials) into state
  • Output values marked sensitive = true — this flag prevents terminal display but does not encrypt the value in state

That last point surprises many engineers. Marking an output as sensitive in your HCL is a UI hint, not a storage control. The value is still written to state in plaintext.

The Three Most Common Ways State Secrets Get Exposed

1. Local State Committed to Git

When you run terraform init and terraform apply without configuring a remote backend, Terraform writes terraform.tfstate to your working directory. If that directory is a Git repository — and there is no .gitignore entry for *.tfstate and *.tfstate.backup — those files get committed. Once committed, they live in history even after deletion (see: Git History Never Forgets).

A correct .gitignore entry for Terraform projects:

# Terraform state
*.tfstate
*.tfstate.*
.terraform/
crash.log
override.tf
override.tf.json
*_override.tf
*_override.tf.json

2. Insecure Remote Backends

Remote state backends (S3, GCS, Azure Blob) solve the local-commit problem, but they introduce their own risks:

  • Public S3 bucket ACLs — historically, misconfigured S3 buckets with public read access have been a major vector for state file exposure.
  • Missing encryption at rest — buckets without server-side encryption (SSE-S3 or SSE-KMS) expose state to anyone with storage-level access.
  • Overly permissive IAM policies — broad s3:GetObject grants can allow unintended roles or users to read state.
  • No state locking — unrelated to secrets, but a missing DynamoDB lock table signals an immature backend configuration overall.

3. Terraform Cloud / Enterprise Workspace Permissions

Terraform Cloud stores state for you, but workspace-level access controls matter. An organization where every developer has read access to every workspace means every developer can read every secret in every piece of infrastructure managed by those workspaces. Audit your workspace permissions as rigorously as you audit IAM policies.

A Practical Remediation Checklist

  1. Audit your repos right now. Search for tfstate files and check Git history. Tools like git log --all --full-history -- "*.tfstate" will surface any past commits. If found, treat every credential in that file as compromised and rotate immediately.
  2. Add *.tfstate* to your global .gitignore. Do this at the repository level and at the global Git config level (~/.gitignore_global) as a backstop.
  3. Migrate to a remote backend with encryption and access control. For AWS, this means an S3 bucket with:
    • Block all public access enabled
    • SSE-KMS encryption with a dedicated KMS key
    • Bucket versioning enabled (so you can recover state, and audit who accessed what)
    • A DynamoDB table for state locking
    • A bucket policy that explicitly denies access outside your Terraform execution role
  4. Never pass raw secrets as Terraform variables. Instead, reference secrets from a secrets manager at apply-time. For AWS, use data "aws_secretsmanager_secret_version" to fetch a value dynamically. This limits what ends up in state.
  5. Prefer resources that don't store secrets in state. For example, instead of creating an aws_iam_access_key in Terraform and storing it in state, provision a role-based identity (IAM role with instance profile or OIDC federation) that requires no long-lived key at all.
  6. Enable Terraform Cloud Sentinel or OPA policies to enforce backend configuration standards and flag resources that are known to write sensitive values to state.
  7. Scan your state backend, not just your code. Static analysis of HCL files misses values that are only present at runtime. You need tooling that inspects the actual state artifact.

The Deeper Problem: Secrets Scattered Across Artifacts

Terraform state is one of several places in a modern engineering workflow where secrets end up embedded in artifacts that were never designed to be secret stores. CI/CD logs, Docker image layers, compiled mobile binaries, and state files all share the same failure mode: a process that needs a secret captures it durably in a way that outlives its original purpose.

The consistent fix is to treat every artifact as potentially readable by an adversary, and to ensure secrets are never present in artifacts unless they are encrypted specifically for the identity that needs them.

How to Check If Your State File Is Already Leaking Secrets

Start with a targeted search of your state file (never commit this output anywhere):

# List all sensitive-looking keys in a local state file
cat terraform.tfstate | python3 -c "
import json, sys
state = json.load(sys.stdin)
for res in state.get('resources', []):
    for inst in res.get('instances', []):
        for k, v in inst.get('attributes', {}).items():
            if any(kw in k.lower() for kw in ['password','secret','key','token','private']):
                print(res['type'], res['name'], k)
"

This won't reveal values (intentionally) but will tell you which resource attributes in your state are likely to contain sensitive material. From there, cross-reference with your access logs to understand who has read that state file.

For a broader sweep across your repositories, .env files, and cloud configuration — run a free GhostCred scan to surface exposed credentials before an attacker finds them first.

Summary

Terraform state files are one of the most overlooked secret storage surfaces in cloud-native infrastructure. The combination of local state in Git history, misconfigured remote backends, and over-permissive workspace access creates a broad attack surface that most security audits miss. Closing it requires both technical controls (encrypted backends, least-privilege IAM, secrets-manager integration) and process controls (scanning artifacts, not just source code).

The good news: every item on the checklist above is achievable in a sprint. The risk of not acting is credentials quietly sitting in a state file, waiting to be discovered.

See what's exposed in your own code.

Run a free scan