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.
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:destroy-fargate-dev.ymlcudly-devcleanup-staging.ymlcudly-stagingcleanup-staging.ymlcudly-stagingWhat
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')matchescudly-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:158is 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
|| truemeans 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 destroyhit 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.