Infrastructure as code turns one bad line into the same misconfiguration in every environment it deploys: a wildcard IAM policy, a public bucket or an open SSH port copied from module to module and applied hundreds of times. This list ranks the ten infrastructure as code (IaC) misconfigurations that most often reach production, with what each enables, the scanner checks that catch it, how to fix it in the template and the real incidents that show the cost. Examples use Terraform and AWS because they are the most common pairing, with Checkov policy IDs as a concrete reference; the same mistakes apply to CloudFormation, Bicep, Pulumi and other clouds. It is written for platform engineers, DevOps and cloud security teams who own templates and pipelines. Identity-specific policy checks are covered in depth in IaC identity policy checks; this page is the wider list.
The Top 10 at a glance
| # | Misconfiguration | What it enables | Example scanner checks |
|---|---|---|---|
| 1 | Secrets hard-coded in templates and variables | Credentials in Git history, state and plan files | CKV_AWS_41, CKV_AWS_46, secret scanning |
| 2 | State files unprotected | Every secret and resource detail in one readable file | Backend encrypt, versioning, locking, access policy |
| 3 | Wildcard IAM policies | Full administrator rights from one compromised role | CKV_AWS_62, CKV_AWS_355, CKV_AWS_40 |
| 4 | Public or world-writable storage | Data exposure and tampering with hosted files | CKV_AWS_20, CKV_AWS_57, CKV2_AWS_6 |
| 5 | Network rules open to 0.0.0.0/0 | Brute force and exploitation of admin ports | CKV_AWS_24, CKV_AWS_25, CKV_AWS_277 |
| 6 | Instance metadata v1 allowed | SSRF that returns cloud role credentials | CKV_AWS_79, CKV_AWS_341 |
| 7 | Databases public or unencrypted | Direct internet access to data stores | CKV_AWS_17, CKV_AWS_16 |
| 8 | Logging and recovery settings left out | No evidence after an incident, no way back | CKV2_AWS_11, CKV_AWS_18, CKV_AWS_21, CKV_AWS_139 |
| 9 | Unpinned modules and providers | Supply chain changes applied without review | .terraform.lock.hcl, version constraints |
| 10 | Over-privileged pipelines and manual drift | Pipeline credentials that own the cloud; fixes undone by console edits | Pipeline role policy, drift detection |
The ranking is our editorial judgement of how directly each misconfiguration leads to compromise. Items 1 to 3 are about credentials and privilege; items 4 to 7 are exposure; items 8 to 10 decide whether you can see, recover and keep the fix in place. Check IDs are Checkov's, from its published policy index; other scanners have equivalent rules under different names.
1. Secrets hard-coded in templates and variables
The misconfiguration: access keys in the provider block, database passwords in .tfvars files, and tokens in EC2 user data scripts committed to the repository.
Why it matters: the secret is now in Git history forever and in every clone, fork and CI cache (MITRE ATT&CK T1552.001 Credentials In Files). HashiCorp's documentation is explicit that if you add secret values directly to your configuration, Terraform stores them in its state and plan files too.
How to detect: Checkov CKV_AWS_41 flags hard-coded AWS keys in the provider and CKV_AWS_46 flags secrets in EC2 user data; run a secret scanner on every commit and on full history. See the Git Secret Scanning Guide.
How to fix: authenticate providers with workload identity (OIDC federation from the pipeline, instance roles), never static keys. Fetch secrets at apply time from a secrets manager. Mark sensitive inputs with sensitive = true, but know that it only redacts CLI and UI output. To keep a value out of state and plan entirely, use ephemeral variables and the ephemeral block (Terraform 1.10 or later) and write-only arguments on resources that support them (Terraform 1.11 or later). Rotate anything that was ever committed. See the Terraform and IaC Pipeline Credential Security Guide.
2. State files unprotected
The misconfiguration: terraform.tfstate committed to Git, kept on laptops, or stored in a bucket without encryption, versioning, locking or tight access control.
Why it matters: HashiCorp warns that local state is a plaintext file that includes any secret values in your configuration, and that state and plan files hold resource attributes such as initial database passwords and API tokens. Read access to state is often read access to production secrets; write access lets someone change what Terraform believes exists.
How to detect: search repositories for *.tfstate and *.tfplan files, and review backend configuration in every root module.
How to fix: follow HashiCorp's four recommendations: store state remotely, encrypt it at rest, limit who can access it and use audit logs to track access. On the S3 backend, set encrypt = true (with kms_key_id for a customer managed key), enable bucket versioning and set use_lockfile = true for S3-native locking; DynamoDB-based locking is deprecated. The GCS backend supports customer-supplied or customer-managed keys, and HCP Terraform encrypts state at rest. Add *.tfstate* and plan files to .gitignore. See the Terraform Cloud and HCP Security Guide.
3. Wildcard IAM policies
The misconfiguration: policies with "Action": "*" and "Resource": "*", inline policies attached to users, and roles whose trust policy any service can assume.
Why it matters: a template that grants administrator rights to an application role means any flaw in that application is an account compromise. In the Capital One breach, a web application firewall's role could list and read many S3 buckets of customer data, far more than a firewall needs, which turned one misconfiguration into exposure of about 106 million people's records.
How to detect: Checkov CKV_AWS_62 flags full "*-*" administrative policies, CKV_AWS_355 flags "*" as the resource for restrictable actions, CKV_AWS_40 flags policies attached directly to users and CKV_AWS_61 flags assume role permission across all services.
How to fix: write policies per workload with named actions and resource ARNs, attach them to roles, and add policy-as-code rules that block wildcards in pull requests. See IaC identity policy checks.
4. Public or world-writable storage
The misconfiguration: bucket ACLs or policies granting public read or write, no bucket-level public access block, and temporary permissions added during troubleshooting that never come out.
Why it matters: public read leaks data (ATT&CK T1530 Data from Cloud Storage); public write lets an attacker replace hosted files. In the Twilio TaskRouter SDK compromise, a bucket policy allowing any AWS principal to read and write, added while troubleshooting a build system in 2015, stayed in place until 2020, when an attacker replaced a JavaScript SDK file served to customers.
How to detect: Checkov CKV_AWS_20 and CKV_AWS_57 flag public READ and WRITE ACLs, and CKV2_AWS_6 flags buckets without a public access block. Equivalent checks exist for Google Cloud Storage and Azure Storage.
How to fix: declare a public access block resource for every bucket in the module itself, enforce account-level blocking outside Terraform, and keep genuinely public content in dedicated buckets behind a CDN. See the Cloud Security Posture Management (CSPM) Guide for catching what templates miss.
5. Network rules open to 0.0.0.0/0
The misconfiguration: security group ingress from 0.0.0.0/0 to SSH (22), RDP (3389) or all ports, often added "for testing" in a shared module.
Why it matters: internet-exposed management ports are scanned constantly and attacked with stolen credentials and exploits (ATT&CK T1133 External Remote Services, T1021.004 SSH).
How to detect: Checkov CKV_AWS_24 (port 22), CKV_AWS_25 (port 3389) and CKV_AWS_277 (all ports) flag ingress from 0.0.0.0/0; CKV_AWS_23 flags rules with no description, which usually means nobody knows why they exist.
How to fix: remove admin ports from internet-facing rules entirely and reach hosts through a session manager, bastion service or identity-aware proxy. Make the CIDR a required module variable with no default rather than defaulting to 0.0.0.0/0.
6. Instance metadata v1 allowed
The misconfiguration: instances and launch templates created without requiring IMDSv2 session tokens, often because the module predates the option.
Why it matters: a server-side request forgery flaw can ask the metadata service for the instance role's temporary credentials (ATT&CK T1552.005 Cloud Instance Metadata API). The Capital One breach page notes that IMDSv2's session token requirement blocks many forged requests of this kind.
How to detect: Checkov CKV_AWS_79 flags instances where IMDSv1 is enabled and CKV_AWS_341 flags launch templates with a metadata response hop limit greater than 1.
How to fix: set metadata_options with http_tokens = "required" in every instance and launch template module, and a hop limit of 1 unless containers on the host need 2. See the EC2 Instance Metadata Service Hardening Guide.
7. Databases public or unencrypted
The misconfiguration: managed databases with publicly_accessible = true, no storage encryption, and default master credentials passed in as plain variables.
Why it matters: a public database endpoint is one leaked password away from a data breach (ATT&CK T1190 Exploit Public-Facing Application), and unencrypted snapshots can be shared or copied.
How to detect: Checkov CKV_AWS_17 flags RDS instances that are publicly accessible and CKV_AWS_16 flags RDS storage that is not encrypted at rest.
How to fix: place databases in private subnets, set encryption on by default in the module, and pass the master password through a write-only argument or let the cloud service manage it in its secrets store so it never lands in state.
8. Logging and recovery settings left out
The misconfiguration: modules that create VPCs, buckets and databases without flow logs, access logs, versioning or deletion protection, because those are optional arguments.
Why it matters: when something goes wrong there is no record of who accessed what, and a mistaken or malicious terraform destroy removes data permanently.
How to detect: Checkov CKV2_AWS_11 flags VPCs without flow logs, CKV_AWS_18 flags buckets without access logging, CKV_AWS_21 flags buckets without versioning and CKV_AWS_139 flags RDS clusters without deletion protection.
How to fix: make these settings on by default in shared modules so teams must opt out explicitly, and use prevent_destroy lifecycle rules on stateful resources.
9. Unpinned modules and providers
The misconfiguration: modules sourced from a Git branch or the registry without a version, and the dependency lock file left out of version control.
Why it matters: the next plan silently pulls whatever the upstream author, or whoever compromised them, published (ATT&CK T1195.001 Compromise Software Dependencies and Development Tools; OWASP CICD-SEC-3 Dependency Chain Abuse).
How to detect: search for module sources without a version constraint or a ref, and repositories without .terraform.lock.hcl.
How to fix: pin registry modules with version constraints and Git modules to a tag or commit SHA. HashiCorp recommends including .terraform.lock.hcl in version control: it records provider versions and checksums, and Terraform errors if a downloaded provider does not match a recorded checksum. Review lock file changes in pull requests like code. See the OWASP CI/CD Top 10 CICD-SEC-3 Dependency Chain Abuse Guide.
10. Over-privileged pipelines and manual drift
The misconfiguration: one pipeline identity with administrator rights on every account, plan and apply run on any branch, findings suppressed in bulk, and console changes made during incidents that never return to code.
Why it matters: the pipeline credential is the most powerful non-human identity in the estate (OWASP CICD-SEC-2 Inadequate Identity and Access Management, CICD-SEC-6 Insufficient Credential Hygiene). Drift means the template says one thing and the cloud another: the Twilio permission above was exactly such a temporary change. Scans of code then report clean while production is open.
How to detect: review the pipeline role's attached policies and trust conditions; run scheduled terraform plan with -detailed-exitcode against each workspace to find drift; report suppressions by age and owner.
How to fix: use short-lived federated credentials per pipeline and per environment, read-only for plan and a separate apply role limited to protected branches with approval. Require a reason and expiry on every suppression. Re-import or revert console changes within an agreed window. See the Configuration Drift and Baseline Enforcement Guide.
Detection and measurement
| Control point | What it catches | Items |
|---|---|---|
| Pre-commit hook and pull request scan (IaC scanner plus secret scanner) | New misconfigurations before merge | 1, 3 to 9 |
| Plan-time policy check (policy as code against the plan JSON) | Values resolved from variables and modules that static scans miss | 3 to 8 |
| Repository and history search for state, plan and key files | Leaked state and credentials | 1, 2 |
| Scheduled drift plan and cloud posture scan | Changes made outside IaC | 4, 5, 10 |
| Cloud audit logs on the state bucket and pipeline role | Unexpected state reads and role use | 2, 10 |
Track five measures: high-severity IaC findings open on the main branch (target zero); percentage of root modules with encrypted, versioned, locked remote state; suppressions older than 90 days; workspaces with drift detected in the last scheduled run; and pipelines still using static cloud keys.
Common failure patterns
- Scanning only changed files: the shared module with the open security group was merged before scanning began and is never rescanned.
- Static scan, dynamic values: a CIDR passed in as a variable passes the scanner and resolves to 0.0.0.0/0 at plan time. Check the plan, not only the source.
- Marking secrets sensitive and assuming they are safe: sensitive hides output; the value is still in state.
- Blanket suppressions: a skip comment added to get a release out disables the check for the whole resource forever.
- Fixing the module, not its consumers: teams pinned to an old module version keep deploying the old misconfiguration.
- Removing secrets from the latest commit only: history, forks and CI caches still hold them. Rotate first, then clean.
Mapping to OWASP, ATT&CK and NIST
| Item | OWASP CI/CD Top 10 | MITRE ATT&CK | NIST SP 800-53 Rev. 5 |
|---|---|---|---|
| 1. Hard-coded secrets | CICD-SEC-6 | T1552.001 | IA-5 |
| 2. State files | CICD-SEC-6 | T1530 | SC-28, AC-3 |
| 3. Wildcard IAM | CICD-SEC-2 | T1078.004, T1098.003 | AC-6 |
| 4. Public storage | CICD-SEC-7 | T1530 | AC-3, AC-21 |
| 5. Open network rules | CICD-SEC-7 | T1133, T1021.004 | SC-7 |
| 6. IMDSv1 | CICD-SEC-7 | T1552.005 | CM-6 |
| 7. Public databases | CICD-SEC-7 | T1190 | SC-7, SC-28 |
| 8. Logging and recovery | CICD-SEC-10 | T1685 | AU-12, CM-2 |
| 9. Unpinned dependencies | CICD-SEC-3 | T1195.001 | SR-11, SI-7 |
| 10. Pipelines and drift | CICD-SEC-2, CICD-SEC-1 | T1078.004 | CM-3, SA-10 |
The OWASP column maps to the closest CI/CD risk; IaC misconfigurations are not a separate OWASP list. In NIST CSF 2.0 terms the list sits mainly under PR.PS-01 (configuration management) and PR.AA-05 (access permissions).
Where to start
- Week one, read only: run an IaC scanner and a secret scanner across every repository and its full history, list backend configurations, and record the baseline for the five measures.
- Week one: rotate any committed credential and move state files out of Git into an encrypted, versioned, locked backend (items 1 and 2).
- Weeks two to four: add the scanner to pull requests in report-only mode, then block on high severity for new code; fix the shared modules first because every consumer inherits them (items 3 to 8).
- Month two: pin modules and providers, commit lock files, and split pipeline roles into plan and apply with federation (items 9 and 10).
- Month three: add plan-time policy checks and scheduled drift detection, and require expiry on suppressions.
- Test and roll back: roll new blocking rules out per repository with a short report-only period; if a rule blocks legitimate work, add a dated, owned exception rather than turning the rule off, and revert template changes through version control.
A small team with one cloud account can do this in a month with a free scanner and a single remote backend. Large organisations should publish approved modules from a central platform team, enforce them in the pipeline and back them with cloud-side guardrails such as organisation policies.
Practitioner checklist
- No static cloud keys or secrets in templates, variables files, user data or Git history.
- Secrets passed through ephemeral values or write-only arguments where the provider supports them.
- State remote, encrypted, versioned, locked and readable only by the pipeline and named administrators.
- No wildcard actions or resources in IAM policies; policies attached to roles only.
- Every bucket declares a public access block; public content isolated.
- No ingress from 0.0.0.0/0 to 22, 3389 or all ports; CIDR variables have no permissive default.
- IMDSv2 required in every instance and launch template module.
- Databases private and encrypted; logging, versioning and deletion protection on by default in modules.
- Modules and providers pinned; .terraform.lock.hcl committed and reviewed.
- Pipeline uses federated, per-environment plan and apply roles; drift checked on a schedule; suppressions expire.
Standards and references
- HashiCorp: Manage sensitive data in your configuration
- HashiCorp: Terraform S3 backend
- HashiCorp: Dependency lock file
- HashiCorp: Use temporary write-only arguments
- OWASP: Top 10 CI/CD Security Risks
- Checkov: Terraform resource scans policy index
- NIST: SP 800-53 Rev. 5 Security and Privacy Controls
Related NHI Mgmt Group resources: IaC identity policy checks · Terraform and IaC Pipeline Credential Security Guide · AWS CloudFormation and CDK Security Guide · Top 10 CI/CD Security Risks · Policy-as-Code for Authorisation Guide