Infrastructure-as-code does not declare data stores with public network exposure
Why it matters
A database reachable from the open internet is the single most catastrophic misconfiguration class in cloud history: the Moltbook exposure Wiz Research documented in 2026 and the recurring MongoDB and Elasticsearch ransom sweeps (tens of thousands of open instances wiped and held for bitcoin) all started with exactly this, a data store bound to 0.0.0.0/0 instead of a private subnet. This check evidences SOC 2 CC6.6, protection against threats from outside system boundaries: an auditor will ask how you restrict network access to systems holding customer data, and your committed IaC is the artifact that answers. AI coding tools fail here reliably because generated Terraform and docker-compose examples default to publicly_accessible = true and 0.0.0.0:5432:5432 so the demo "just works", and nobody revisits the setting before production. The check reads only what is declared in the repo; it makes no claim about live cloud state.
Severity rationale
Critical because a publicly routable database port turns a single leaked or default credential into full exfiltration of the entire data store, with no application-layer control in the path.
Remediation
In Terraform, set publicly_accessible = false on every aws_db_instance / aws_rds_cluster and replace any security-group ingress rule granting 0.0.0.0/0 (or ::/0) on database ports with the application security group id or a VPC CIDR:
ingress {
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.app.id]
}
For GCP Cloud SQL, remove any authorized_networks entry of 0.0.0.0/0 and use private IP or the Cloud SQL Auth Proxy. In production docker-compose files, drop the host port mapping entirely or bind to loopback (127.0.0.1:5432:5432); containers on the same compose network reach the database by service name without any published port.
Detection
- ID:
iac-database-not-publicly-accessible - Severity:
critical - What to look for: First determine whether the repo contains infrastructure-as-code: Terraform (
**/*.tf), Pulumi (Pulumi.yamlplus a program importing@pulumi/*in TS/JS,pulumi_*in Python, orgithub.com/pulumi/pulumi-*in Go), CloudFormation (YAML/JSON containingAWSTemplateFormatVersionor a top-levelResources:withAWS::types), CDK (aws-cdk-liborconstructsinpackage.json/requirements.txtwith acdk.json), or docker-compose files (docker-compose*.yml,compose*.yaml). If IaC exists, enumerate declared data-store resources:aws_db_instance,aws_rds_cluster,aws_elasticache_*,aws_opensearch_domain/aws_elasticsearch_domain,google_sql_database_instance,aws_docdb_cluster, CDK/CloudFormationAWS::RDS::*/AWS::ElastiCache::*/AWS::OpenSearchService::Domain, Pulumi equivalents (aws.rds.Instance,gcp.sql.DatabaseInstance), and compose services runningpostgres,mysql,mariadb,mongo,redis,mssql/sqlserver, orelasticsearch/opensearchimages. Then inspect their network exposure:publicly_accessiblearguments, security-group ingress blocks whosecidr_blocks/ipv6_cidr_blocksinclude0.0.0.0/0or::/0on ports 5432, 3306, 27017, 6379, 1433, or 9200, GCPauthorized_networksvalues, and composeports:mappings for those ports. Do NOT treatsupabase/config.tomlas an exposure signal: it configures the local dev stack and expresses no network exposure; hosted Supabase manages its own boundary. - Pass criteria: IaC is present, at least one data-store resource is declared, and none of them carries public exposure: every
publicly_accessibleisfalseor absent (AWS defaults to false), no security-group ingress rule anywhere in the IaC allows0.0.0.0/0or::/0on the database ports above, no GCPauthorized_networksentry is0.0.0.0/0, and production compose files either publish no host port for database services or bind them to127.0.0.1. - Fail criteria: Any of:
publicly_accessible = trueon anaws_db_instanceoraws_rds_cluster(including via a variable whose declared default istrue); a security-group ingress rule (inline block,aws_security_group_rule, oraws_vpc_security_group_ingress_rule) allowing0.0.0.0/0or::/0on ports 5432, 3306, 27017, 6379, 1433, or 9200 — an open DB-port rule declared in the repo fails regardless of whether you can prove attachment to a specific data store; agoogle_sql_database_instancewith anauthorized_networksvalue of0.0.0.0/0; or a production-facing compose file (docker-compose.yml,docker-compose.prod.yml,compose.yaml, or any compose file referenced by deploy scripts, but notdocker-compose.dev.yml/docker-compose.test.yml/ files under adev/orlocal/directory) mapping a database port as"5432:5432"or"0.0.0.0:5432:5432"(unqualified host binding means all interfaces). A commented-out restrictive rule next to a live open rule is still a fail; a# TODO: lock downcomment does not change the declared state. - Skip (N/A) when: The repo contains no IaC files at all, OR IaC exists but declares no data-store resources (for example, Terraform that only manages DNS or a compose file with only app containers). Quote which case applies:
"No IaC in repo (no *.tf, Pulumi, CloudFormation/CDK, or docker-compose files)"or"IaC present (<files>) but declares no data-store resources". - Before evaluating, quote: The file path and the exact declaration lines for each data-store resource found, plus the exposure-relevant lines judged (the
publicly_accessibleargument, the ingresscidr_blocks, theauthorized_networksentry, or the composeports:mapping). If passing because an argument is absent and defaults safe, say so explicitly. - Report even on pass:
"IaC data stores checked: <N> across <files>; none publicly exposed (publicly_accessible false/absent, no 0.0.0.0/0 ingress on DB ports, no host-published compose DB ports)". - Detail on fail:
"infra/rds.tf declares aws_db_instance.main with publicly_accessible = true and aws_security_group.db allows ingress 0.0.0.0/0 on 5432"or"docker-compose.prod.yml maps postgres service ports 0.0.0.0:5432:5432, exposing the database on all host interfaces". - Remediation: Set
publicly_accessible = falseon RDS resources and scope ingress to the app security group (security_groups = [aws_security_group.app.id]) or a VPC CIDR instead of0.0.0.0/0; on GCP, remove openauthorized_networksand use private IP or the Cloud SQL Auth Proxy; in production compose files, delete the hostports:mapping for database services or bind to127.0.0.1:5432:5432. For broader network-boundary coverage, see thesecurity-hardeningaudit.
External references
- soc2:2017 · CC6.6 — Protection against threats from outside system boundaries
- iso-27001:2022 · A.8.20 — Networks security
- nist:rev5 · SC-7 — Boundary Protection
Taxons
History
- 2026-07-03·v1.0.0·Initial soc2-readiness v1 authoring — IaC-declared data stores must not carry public network exposure, evidencing SOC 2 CC6.6 boundary protection.·by soc2-readiness-v1-build