Infrastructure as code (IaC) security scanners like Checkov, Trivy, KICS, and Invicti IaC Security automate the detection of infrastructure misconfigurations. But knowing what they check for, why each control matters, and which issues should block deployment is what makes IaC security reviews useful rather than mechanical.
This checklist organizes the most important infrastructure as code security controls by domain. Use it for pull request reviews, continuous integration and continuous delivery (CI/CD) policy configuration, security audits, and onboarding new engineering teams to secure IaC practices.

The OWASP Infrastructure as Code Security Cheat Sheet recommends integrating IaC security into the development lifecycle through integrated development environment (IDE) plugins, secrets management, least privilege, static analysis, CI/CD checks, artifact signing, logging, monitoring, and runtime detection. This article turns those principles into a practical, copy-ready checklist for DevSecOps teams.
An IaC security checklist is a domain-organized reference of infrastructure security controls that should be validated in Terraform, CloudFormation, Kubernetes, Helm, Bicep, Azure Resource Manager (ARM) templates, Ansible, Pulumi, Dockerfiles, and other infrastructure-as-code configuration files.
Teams use IaC security checklists during code review, automated scanner configuration, and security audits to ensure infrastructure configuration meets baseline security requirements before deployment.
In pull request review, use the checklist as a reference when reviewing infrastructure changes, especially complex identity and access management (IAM) policies, security group rules, Kubernetes pod security contexts, and Terraform modules.
In CI/CD scanner configuration, use it as a baseline for Checkov policies, Trivy checks, KICS queries, custom Open Policy Agent (OPA) Rego rules, and deployment gates.
In security audits, use it to document which controls are implemented, which are enforced automatically, which require manual review, and which exceptions have been formally accepted.
The severity levels in this checklist are recommended starting points for deployment policy, not universal vulnerability severity ratings. Adjust them for asset criticality, exposure, compensating controls, and your organization’s risk tolerance:
Static IaC scanning reads infrastructure definitions. It can check what a Terraform file, CloudFormation template, or Kubernetes manifest says, but it can’t see repository settings, the state of a live cloud account, or behavior at runtime.
That split matters when you assign owners. Most items listed below are resource-level settings that a scanner can check in code. A few need another control: branch protection and pull request rules live in repository settings, root account keys and multi-factor authentication (MFA) live in account configuration, and drift detection compares deployed resources with code. Items in this checklist that an IaC scanner can’t verify are marked with a note on where to enforce them.
IAM misconfigurations create privilege escalation paths. An overly permissive role attached to a compromised Lambda function, EC2 instance, CI/CD workflow, or Kubernetes service account can give attackers broad access across the cloud account. Least privilege should be encoded in IaC, not left to manual review after deployment.
The following Terraform policy statement shows the difference between unrestricted permissions and a role scoped to specific S3 actions and resources:
# Vulnerable
statement {
effect = "Allow"
actions = ["*"]
resources = ["*"]
}
# Better
statement {
effect = "Allow"
actions = ["s3:GetObject", "s3:PutObject"]
resources = ["arn:aws:s3:::my-specific-bucket/*"]
}EC2 metadata configuration is another important IAM-adjacent control. Requiring IMDSv2 ensures that applications must use the token-based metadata service rather than allowing direct IMDSv1 requests:
# Vulnerable: metadata_options omitted, so IMDSv1 may still be allowed
resource "aws_instance" "example" {
# ...
}
# Better
resource "aws_instance" "example" {
# ...
metadata_options {
http_endpoint = "enabled"
http_tokens = "required"
}
}IMDSv2 matters for application security as much as for infrastructure. With IMDSv1, some server-side request forgery (SSRF) flaws can retrieve instance role credentials with a simple metadata request. IMDSv2 requires a session-token exchange that blocks many straightforward SSRF techniques that can retrieve credentials from IMDSv1.
A note on password policy: NIST SP 800-63B discourages mandatory periodic rotation and composition rules, and favors length and screening against known-compromised passwords. Don’t build a policy gate around forced rotation.
The Checkov policy index covers IAM, cloud, Kubernetes, Dockerfile, secrets, CI/CD, and other IaC checks, which can be used to map these controls into scanner enforcement.
Network misconfiguration is one of the most visible cloud attack surfaces. Public security groups, exposed databases, permissive firewall rules, and publicly accessible storage can turn an internal system into an internet-facing target.
For network access, avoid opening administrative ports to the entire internet. This example restricts SSH access to a specific trusted address instead:
# Vulnerable
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# Better
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["203.0.113.10/32"] # bastion or VPN egress address
}Better still, remove inbound SSH altogether and use AWS Systems Manager Session Manager for shell access:
resource "aws_s3_bucket_ownership_controls" "example" {
bucket = aws_s3_bucket.example.id
rule {
object_ownership = "BucketOwnerEnforced"
}
}
resource "aws_s3_bucket_public_access_block" "example" {
bucket = aws_s3_bucket.example.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}VPC Flow Logs are covered in Domain 6, where network activity monitoring belongs alongside other logging and audit controls.
Encryption controls reduce the impact of unauthorized access and support compliance expectations across frameworks such as Payment Card Industry Data Security Standard (PCI DSS), Health Insurance Portability and Accountability Act (HIPAA), SOC 2, ISO 27001, and General Data Protection Regulation (GDPR).
RDS encryption should be explicit in the resource definition. Where key-level control is required, specify the KMS key used to encrypt the database:
resource "aws_db_instance" "example" {
storage_encrypted = true
kms_key_id = aws_kms_key.rds.arn
}S3 follows the same principle, but the configuration is defined separately from the bucket resource. The following example uses a customer-managed KMS key and enables S3 Bucket Keys:
resource "aws_s3_bucket_server_side_encryption_configuration" "example" {
bucket = aws_s3_bucket.example.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.mykey.arn
}
bucket_key_enabled = true
}
}Hardcoded credentials in IaC files are high-risk because they are committed to version control history. Removing the current value from the latest commit does not remove it from prior commits, forks, logs, or CI/CD artifacts.
The safest pattern is to avoid passing the database password through Terraform at all when the service can manage it directly. This example replaces a hardcoded password with an RDS-managed secret:
# Vulnerable
resource "aws_db_instance" "example" {
password = "SuperSecretPassword123!"
}
# Better: RDS generates and manages the password in Secrets Manager
resource "aws_db_instance" "example" {
manage_master_user_password = true
master_user_secret_kms_key_id = aws_kms_key.rds.arn
}Avoid reading a secret with the aws_secretsmanager_secret_version data source and passing it to a resource argument. Terraform writes the resolved value to state in plaintext. Recent Terraform and AWS provider versions support ephemeral resources and write-only arguments that can keep supported values out of state and plan files, so check the provider documentation for the resources you use.
Terraform state protection is covered in Domain 7.
Kubernetes pod security misconfigurations can create privilege escalation and container escape paths. Official Kubernetes Pod Security Standards define privileged, baseline, and restricted profiles for pod-level controls.
Two of those profiles map directly onto this checklist. The baseline profile covers privileged containers, host namespaces, and hostPath volumes. The restricted profile adds running as non-root, dropping all capabilities, blocking privilege escalation, and the RuntimeDefault seccomp profile. Setting readOnlyRootFilesystem is good practice, but it is not a Pod Security Standards requirement.
Kubernetes security contexts make many of these controls explicit in the workload manifest. Start by disabling privileged execution and privilege escalation:
# Vulnerable
securityContext:
privileged: true
# Better
securityContext:
privileged: false
allowPrivilegeEscalation: falseRunning as non-root adds another layer of isolation. Set runAsNonRoot and, where appropriate, specify a non-zero user ID:
securityContext:
runAsNonRoot: true
runAsUser: 1000Rather than relying only on workload-by-workload settings, Pod Security Admission can enforce a security profile across an entire namespace. This example applies the restricted profile to the payments namespace:
apiVersion: v1
kind: Namespace
metadata:
name: payments
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latestAt the container level, drop Linux capabilities by default and add back only the specific capabilities the application requires:
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICEFinally, apply the runtime’s default seccomp profile to restrict the system calls available to the container:
securityContext:
seccompProfile:
type: RuntimeDefaultTogether, these settings implement several of the workload-level controls required or recommended by the restricted Pod Security Standard.
Security controls are much less useful if activity cannot be reviewed. Logs and audit trails are essential for detection, investigation, compliance evidence, and incident response.
A baseline CloudTrail configuration should cover all regions, validate log integrity, and encrypt the resulting audit data. The following Terraform resource enables all three controls:
resource "aws_cloudtrail" "main" {
name = "org-trail"
s3_bucket_name = aws_s3_bucket.trail.id
is_multi_region_trail = true
enable_log_file_validation = true
kms_key_id = aws_kms_key.trail.arn
}CI/CD pipelines and Terraform state are part of the infrastructure attack surface. A compromised workflow can deploy malicious infrastructure. A leaked state file can expose secrets, resource identifiers, and sensitive configuration. For deeper pipeline guidance, see the OWASP CI/CD Security and GitHub Actions Security cheat sheets.
A production Terraform backend should combine remote state, encryption, and state locking. This S3 backend example also uses a customer-managed KMS key for key-level control:
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
encrypt = true
kms_key_id = "arn:aws:kms:us-east-1:111122223333:key/EXAMPLE"
use_lockfile = true
}
}Native S3 locking with use_lockfile requires Terraform 1.10 or later. HashiCorp has deprecated the older dynamodb_table locking argument, so teams on earlier versions should plan an upgrade. Note that encrypt = true alone applies S3-managed encryption. Add kms_key_id if you need key-level control.
GitHub Actions references need similar supply chain discipline. A branch or release tag can be moved, while a full commit SHA identifies one immutable revision of the action:
# Risky
uses: actions/checkout@main
# Also risky: tags can be moved to point at different code
uses: actions/checkout@v7
# Better: pin to the full commit SHA and note the release in a comment
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1Tags are mutable. In March 2025, attackers compromised the tj-actions/changed-files action and repointed its tags to malicious code that exposed secrets from the workflows using it. A full commit SHA is the only reference that can’t be silently changed.
Module and provider pinning are covered in Domain 8.
IaC depends on external modules, providers, actions, Helm charts, and container images. Those dependencies extend the software supply chain beyond the infrastructure code stored in your own repository. Pinning, reviewing, and verifying external artifacts reduces the risk of an upstream change silently altering what you deploy.
For registry-hosted Terraform modules, specify an exact module version rather than allowing an unconstrained upgrade:
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.1.2"
}Container images need the same treatment. Tags such as node:20-alpine can resolve to different image contents over time, while a digest pins the build to a specific image:
# Risky
FROM node:20-alpine
# Better (replace the placeholder with the digest of the image you've vetted)
FROM node:20-alpine@sha256:<image-digest>The controls above apply regardless of IaC tool or cloud platform. The syntax and services change, but the underlying security requirements remain largely the same.
The same secrets, encryption, and exposure rules apply to AWS CloudFormation templates. The examples below show the “better” form of two common controls:
# Secrets: let RDS manage the master password instead of hardcoding it
Database:
Type: AWS::RDS::DBInstance
Properties:
StorageEncrypted: true
PubliclyAccessible: false
ManageMasterUserPassword: true
# ...
# S3: KMS encryption, public access blocked, ACLs disabled
ExampleBucket:
Type: AWS::S3::Bucket
Properties:
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: aws:kms
KMSMasterKeyID: !GetAtt BucketKey.Arn
BucketKeyEnabled: true
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
OwnershipControls:
Rules:
- ObjectOwnership: BucketOwnerEnforcedCloudFormation also adds a few template-specific checks:
The same control families exist on other clouds. This table maps the most common AWS items:
Invicti IaC Security scans infrastructure definitions before deployment and can also orchestrate and ingest findings from external IaC security scanners. It brings these results into the Invicti Platform with orchestration, normalization, deduplication, and cross-engine correlation, so overlapping detections can be managed as part of the same AppSec workflow. Scans can run as part of development and CI/CD workflows, with policies that can block builds or deployments when defined thresholds are exceeded.
The misconfiguration categories line up with this checklist: public storage, disabled encryption, missing audit logging, wildcard and over-provisioned IAM permissions, open security groups, insecure defaults, and secrets embedded in infrastructure definitions.
All IaC scanning has a structural limit. It checks definitions before deployment, but it isn’t continuous monitoring of cloud accounts, it doesn’t verify repository settings such as branch protection, and it can’t tell you whether a deployed application is exploitable. It also doesn’t detect application-layer vulnerabilities such as SSRF, SQL injection, cross-site scripting (XSS), broken authentication, or broken object level authorization.
IaC scanning is one part of a broader cloud application security strategy that spans infrastructure definitions, containers, API security, application code, and running applications. Connecting findings across those layers provides the context needed to understand how an infrastructure weakness relates to application risk.
Invicti’s application security posture management (ASPM) consolidates IaC findings with dynamic application security testing (DAST), static application security testing (SAST), software composition analysis (SCA), secrets, and container findings, then uses signals such as runtime evidence, exploitability, business context, and threat intelligence to improve prioritization where those signals are available. Invicti DAST tests running applications and can automatically confirm exploitability for many supported vulnerability types using proof-based scanning.
The layers matter together. Consider a DAST scan that confirms an SSRF vulnerability in an application. Whether that flaw can reach cloud credentials depends on the infrastructure around it: the permissions on the role the workload runs as, and whether the instance metadata service requires IMDSv2. Those are IaC checklist items. A team looking at the confirmed application flaw and an overpermissive role finding side by side can make a better call on what to fix first than either finding supports alone.
This checklist provides the policy baseline: the controls your IaC scanning tools should enforce, your code reviews should validate, and your CI/CD gates should evaluate.
Invicti IaC Security checks infrastructure definitions before deployment. Invicti ASPM brings those findings together with the rest of your application security data, while Invicti DAST adds runtime evidence about vulnerabilities in the applications that run on that infrastructure.
Used together, these layers help teams move from isolated infrastructure checks to a more complete view of application risk – before deployment and in running applications.
To see how Invicti can help automate IaC security checks, connect them with broader AppSec findings, and bring runtime context into prioritization, request a demo of the Invicti Platform.
Critical IaC misconfigurations include wildcard IAM permissions, SSH or RDP open to the internet, public storage, publicly accessible databases, hardcoded credentials, privileged Kubernetes containers, and unencrypted Terraform state. Instances that allow IMDSv1 can also increase the impact of some application SSRF vulnerabilities by making instance-role credentials accessible through straightforward metadata requests.
Use external secrets managers or service-managed credentials, such as RDS-managed master passwords. Keep resolved secret values out of state where the provider supports ephemeral values or write-only arguments, store state in an encrypted remote backend, exclude .tfvars and state files from Git, scan commits for secrets, and never set credential defaults in reusable modules. Marking a variable sensitive redacts it in CLI output but does not by itself remove it from state.
Scanners map their checks to rule IDs. Checkov, for example, publishes a policy index of checks with CKV identifiers for IAM wildcards, encryption, open ports, privileged containers, and more. Invicti IaC Security includes native IaC scanning and can also ingest findings from external scanners, bringing those results into a common policy and vulnerability management workflow. Native check sets include CIS benchmarks for AWS, Azure, Google Cloud, and Kubernetes.
For your own policy documentation, map each checklist item to the rule ID or benchmark recommendation your scanner uses, then use those IDs to configure hard failures, warnings, and exceptions in CI/CD.
Many checklist items support compliance requirements around access control, encryption, logging, vulnerability management, monitoring, and change control. IaC scanning can provide evidence that defined infrastructure controls are checked before deployment.
It does not establish compliance by itself. Account configuration, deployed infrastructure, runtime behavior, operational processes, and other controls may require separate validation depending on the framework and environment.
