Skip to content

sec(ci): RDS deletion protection is stripped by identifier prefix at 3 sites, hardening none #1821

Description

@cristim

Deferred from #1815. Surfaced by the adversarial review of that PR, in the same job it hardened.

Enumerated by predicate across .github/workflows/*.yml (rg -n 'starts_with\(DBInstanceIdentifier'), not sampled. 3 sites, 0 hardened:

file line prefix
destroy-fargate-dev.yml 158 cudly-dev
cleanup-staging.yml 164 cudly-staging
cleanup-staging.yml 249 cudly-staging

What

Each site queries RDS with DBInstances[?starts_with(DBInstanceIdentifier,'<prefix>')] and strips deletion protection from every match, with failures swallowed (2>/dev/null || true).

Why it matters

This is the same over-matching class as #1592, on a different resource, and it weakens a delete-guard rather than merely selecting a delete target. starts_with(…,'cudly-dev') matches cudly-dev-prod-mirror, cudly-dev-1a2b3c4d-replica, and any operator-named instance sharing the prefix. Deletion protection is the last line of defence on a database; removing it from an instance the workflow does not own leaves that instance exposed to the next destroy that does match it.

destroy-fargate-dev.yml:158 is the notable one: it sits immediately after the ECR step #1815 hardened, in the same job, so the file now selects ECR repositories by exact equality and RDS instances by prefix.

Swallowing the failure with || true means a partial or failed strip also reports success.

Fix direction

Resolve the owned instance identifier from the state being torn down (terraform output), the way #1815 does for ECR, and compare by exact equality. If more than one instance is legitimately owned, enumerate them from state rather than pattern-matching the account.

Do not swallow the failure: if deletion protection cannot be removed from an instance the state owns, that should fail loudly rather than letting terraform destroy hit a confusing downstream error.

Verification bar

Both directions: a prefix near-miss is refused, AND the legitimately owned instance is still selected. A selector that returns nothing passes a refusal-only test while leaving the destroy broken.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions