RDS PubliclyAccessible: The Flag That Bypasses Your VPC
An RDS instance stays reachable from the internet the moment PubliclyAccessible is true, even while no security group rule permits it. The exposure lies dormant until an unrelated security group change opens the database port to 0.0.0.0/0, instantly exposing production data to internet scanners.
At a glance
- Unsafe setting
- PubliclyAccessible=true combined with a security group later allowing 0.0.0.0/0 on the database port
- Failure trigger
- An unrelated security group edit adds an internet-wide ingress rule while the public endpoint already exists
- Blast radius
- Internet-wide scanners find the open port within hours, enabling brute force, RCE exploitation, and credential reuse across environments.
- Recommended control
- Disable PubliclyAccessible, enforce via AWS Config rds-instance-public-access-check with automated remediation, and block via SCP.
Fix commands and configuration
aws rds modify-db-instance --db-instance-identifier <id> --no-publicly-accessible --apply-immediatelyrds-instance-public-access-checkrds:CreateDBInstanceThe Trap
The PubliclyAccessible attribute on an Amazon RDS instance, left set to true and paired with a security group that later gains an inbound rule for 0.0.0.0/0 on the database port.
The Default State
The RDS console quick-create wizard defaults “Public access” to No, but this is routinely overridden. Terraform modules copied from public examples ship with publicly_accessible = true as the sample value, and CloudFormation stacks generated by migration tooling frequently set it explicitly for “testing convenience” during a cutover, then never revert it. Separately, engineers create a dedicated security group during a migration and add an ingress rule for 0.0.0.0/0 on port 5432 or 3306 so an external ETL box or a Lambda outside the VPC can reach the instance during the initial load. Neither setting alone is fatal. Together, they are lethal, because AWS provisions a second, internet-routable DNS endpoint the moment PubliclyAccessible is true, regardless of whether any rule currently permits reaching it.
The Blast Radius
The public endpoint exists and resolves from day one, sitting inert until someone modifies the security group for an unrelated reason: a new integration, a firewall exception ticket, a copy-pasted Terraform block. The instant a 0.0.0.0/0 rule lands on the database port, the previously dormant public endpoint becomes reachable from the entire internet, not just the intended new client. Mass scanners on Shodan and masscan detect the open port within hours. Brute-force attempts against the master username, exploitation of unpatched engine CVEs (MySQL/PostgreSQL RCE chains), and credential stuffing against IAM database authentication follow immediately. Because RDS snapshots inherit the same exposure posture and credentials are frequently reused in Parameter Store or Secrets Manager across environments, one exposed instance often yields lateral access into staging and production simultaneously.
The Lead Mechanic Fix
Disable the flag directly: aws rds modify-db-instance --db-instance-identifier <id> --no-publicly-accessible --apply-immediately. Enforce this at the control-plane level with an AWS Config managed rule, rds-instance-public-access-check, wired to an SSM Automation remediation that runs the same modify command on drift. Block the condition entirely with an SCP denying rds:CreateDBInstance and rds:ModifyDBInstance when the request context includes rds:PubliclyAccessible: true, excepting a tagged sandbox OU. Security groups must reference source security group IDs for application tiers, never CIDR blocks, and any external-integration requirement should route through a VPC endpoint or a bastion with Session Manager port forwarding instead of a public listener.