DevSecOps Guides

DevSecOps Guides

Your Terraform State File Is a Credential Vault

How infrastructure pipelines leak secrets, and the controls that stop them.

Developer Advocate's avatar
Developer Advocate
Aug 21, 2026
∙ Paid

TL;DR

Infrastructure as Code (IaC) turns your cloud resources into manifest files. The security of your entire infrastructure depends on who can modify those files and how well the deployment pipeline validates them. Terraform state files contain plaintext credentials for database instances, access keys, private certificates, and client secrets. Standard S3 remote state configurations are insufficient. They require strict Customer Managed Keys (CMK), scoped IAM Policies, and OPA policy-as-code guardrails. Observability systems (logs, metrics, traces) present a parallel threat: they have the most permissive access because everyone needs to see them, but they also accumulate sensitive data by accident. This article covers how IaC pipelines break, how to lock down and encrypt Terraform state, what observability data leaks, and how security chaos engineering finds the gaps before attackers do.


Part 1. The Core Vulnerability: Terraform State as a High-Value Target

The Core Vulnerability: Terraform State as a High-Value Target

What the state file holds

Terraform keeps a record of every cloud resource that it makes. This record is the state file. The file holds passwords, access keys, and private certificates as plain text. Terraform does not encrypt these values. A person who can read the file can read every secret.

In this part - See which resource attributes hold plain text secrets. - Follow an attacker from one leaked file to full cloud access.

Infrastructure as Code (IaC) changes the security posture of modern applications. Instead of configuring cloud environments manually, organizations write declarative manifests in HashiCorp Configuration Language (HCL). These files are committed to a Git repository, and a CI/CD pipeline applies the configuration. While this approach provides auditability, version control, and speed, it introduces a severe security challenge: the Terraform state file (terraform.tfstate).

The state file serves as Terraform's source of truth. It maps real-world cloud resources to the resource declarations in your configuration. To perform this mapping, Terraform must store the metadata and attributes of every deployed resource. Because cloud APIs often return sensitive values (such as initial database passwords, IAM secret keys, private certificates, API gateway tokens, and client secrets), Terraform writes these values directly into the state file.

By design, Terraform does not encrypt individual fields within the state file. It stores them in plaintext JSON. This means anyone with read access to the state file can extract every secret managed by your infrastructure code.

The Plaintext Exposure in Resource Attributes

Common HCL resources leak sensitive data into the state file. Consider a database deployment:

resource "aws_db_instance" "production_db" {
  identifier           = "production-db"
  allocated_storage    = 50
  engine               = "postgres"
  engine_version       = "15.4"
  instance_class       = "db.r6g.xlarge"
  username             = "db_admin"
  password             = var.db_password # Sensitive variable passed at runtime
  db_subnet_group_name = aws_db_subnet_group.db_subnet.name
}

When Terraform runs terraform apply, it creates the RDS instance and retrieves its attributes. The password value, even if marked as sensitive = true in the variables file, is written in plaintext inside the terraform.tfstate JSON:

{
  "mode": "managed",
  "type": "aws_db_instance",
  "name": "production_db",
  "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
  "instances": [
    {
      "schema_version": 1,
      "attributes": {
        "address": "production-db.c123456789.us-east-1.rds.amazonaws.com",
        "username": "db_admin",
        "password": "SuperSecretPassword123!",
        "port": 5432,
        "identifier": "production-db"
      }
    }
  ]
}

Another example is generating a TLS private key inside Terraform using the tls_private_key resource. While convenient for bootstrapping, this resource writes the private key directly into the state file. If an attacker gains access to the S3 bucket storing this state file, they can extract the private key and immediately compromise TLS decryption or SSH endpoints:

resource "tls_private_key" "ingress_key" {
  algorithm = "RSA"
  rsa_bits  = 4096
}
# The private_key_pem attribute is stored in plaintext inside the state JSON.

Storing secrets in HashiCorp Vault is a standard defense, but even the vault_generic_secret resource leaks data:

resource "vault_generic_secret" "api_key" {
  path = "secret/api_keys"
}
# The data_json attribute containing all vault secrets is written in plaintext to the state.

Scenario: An Attacker's Pivot from State Leak to Full Cloud Compromise

Consider a production system where developers configure an S3 bucket to host the remote state file. By default, they do not enable default bucket encryption or block public access, or they grant broad read permissions to a corporate IAM role that includes non-production developers.

An attacker exploits an SSRF (Server-Side Request Forgery) vulnerability in a public-facing application or obtains a developer's compromised AWS access key with read-only permissions to the S3 bucket.

  1. Discovery: The attacker scans S3 buckets and discovers company-terraform-state-prod.

  2. Exfiltration: The attacker downloads terraform.tfstate.

  3. Parsing: The attacker parses the JSON and finds: , Plaintext credentials for the primary PostgreSQL database (db_admin / SuperSecretPassword123!). , The private SSH key for the bastion host. , An AWS IAM access key generated via aws_iam_access_key for a legacy integration service.

  4. Pivoting: Using the SSH private key, the attacker logs into the bastion host. From there, they connect to the database within the private VPC subnet using the extracted credentials.

  5. Privilege Escalation: The attacker uses the IAM access key, which was granted administrative permissions to manage ECS tasks, to launch a malicious container, escalating their access to full Administrator access over the AWS account.

PCI DSS v4.0 Compliance Mapping

This risk directly impacts PCI DSS v4.0 compliance: * Requirement 3.2.1: Requires protecting stored account data. Plaintext primary account numbers (PAN) or authentication credentials must never be readable. Storing RDS passwords or access keys in plaintext state files violates this requirement. * Requirement 3.5.1: Requires restricting access to cryptographic keys. If Terraform generates or manages TLS certificates and private keys, storing them in an unencrypted state file violates this control. * Requirement 8.2.1: Requires securing authentication credentials. Credentials must be encrypted at rest. * Requirement 8.3.1: Requires strict access control for authentication credentials.


User's avatar

Continue reading this post for free, courtesy of Developer Advocate.

Or purchase a paid subscription.
© 2026 Reza · Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for great culture