Skip to main content
emnode
Compliance Medium severity

AWS Security Hub · CloudFormation

CloudFormation.3: Stacks can be deleted without termination protection

Written and reviewed by Emnode · Last reviewed

What does AWS Security Hub CloudFormation.3 check?

CloudFormation.3 fails when a stack does not have termination protection enabled. The control reads the stack's EnableTerminationProtection flag, which when off allows a single DeleteStack call to tear down every resource the stack owns.

Why does CloudFormation.3 matter?

DeleteStack against the wrong stack (wrong profile, wrong terminal) can destroy a production VPC, database cluster, and message brokers in one command, and not everything restores from backups. A well-known fintech post-mortem put one such mistake at 11 hours of downtime. Termination protection forces an explicit two-step delete, turning an accidental keystroke into a deliberate decision.

How do I fix CloudFormation.3?

  1. Run an audit query across stacks to find those without termination protection.
  2. Enable termination protection on each one with update-termination-protection.
  3. Add DeletionPolicy: Retain to stateful resources for defence in depth.
  4. Use an SCP to restrict who can disable termination protection or delete protected stacks.

Remediation script · bash

# Enable deletion protection on every unprotected standalone RDS instance in a region.
for id in $(aws rds describe-db-instances \
  --query 'DBInstances[?DeletionProtection==`false`].DBInstanceIdentifier' --output text); do
  aws rds modify-db-instance --db-instance-identifier "$id" \
    --deletion-protection --apply-immediately
  echo "Protected RDS instance: $id"
done

# Termination-protect every production-tagged CloudFormation stack (eyeball the list first).
aws cloudformation describe-stacks \
  --query "Stacks[?Tags[?Key=='Environment' && Value=='production']].StackName" \
  --output text | tr '\t' '\n' | while read -r stack; do
  aws cloudformation update-termination-protection \
    --stack-name "$stack" --enable-termination-protection
  echo "Protected stack: $stack"
done

# Deletion-protect a production load balancer.
aws elbv2 modify-load-balancer-attributes --load-balancer-arn "$LB_ARN" \
  --attributes Key=deletion_protection.enabled,Value=true

Full walkthrough (console steps, edge cases and verification) in the lesson Enable deletion and termination protection.

Is CloudFormation.3 a false positive?

Ephemeral stacks are meant to be deleted, and termination protection actively gets in their way. CI pipelines, per-pull-request preview environments, and integration-test fixtures spin up a stack, exercise it, and tear it down automatically; turning on termination protection would force a manual two-step delete and break that automation, so leaving it off is the correct, deliberate state. Note also that you can't set termination protection directly on a nested stack (the flag is inherited from the root stack), so a nested stack flagged in isolation is expected behaviour. Scope this control to long-lived, stateful stacks (production VPCs, databases, the stacks you'd never want a stray DeleteStack to reach) and suppress the finding on disposable stacks, ideally tagging them (for example lifecycle=ephemeral) so the exception is auditable rather than a blanket mute.

Part of the learning path Lock down access