Module 1: Threat Modeling Andes Cargo
6. The blast radius, revisited: what would have stopped it?
Description
If you came from terraform-and-iac-guide or cicd-and-gitops-on-aws-guide, you already know the case: in February 2026, a Claude Code agent ran terraform destroy against DataTalks.Club's production infrastructure and deleted, with a single command, 2.5 years of data. This lesson doesn't retell it from scratch to generate alarm again — it revisits it with a genuinely new angle: instead of asking "what went wrong?", it asks "what automated control, not dependent on a human paying attention at that exact moment, would have stopped this?". The answer maps, point by point, against TM-06 from lesson 5 and against the modules this guide still has to build.
Connection to the module
TM-06 (Denial of service, lesson 5) identified that nothing in andes-cargo-infra/ today prevents a plan that destroys Shipments. This lesson is the evidence for why that risk isn't theoretical: a real case, with verified figures, where exactly that kind of failure — an unreviewed destruction, without enough attention — cost 2.5 years of production data. The difference from terraform-and-iac-guide M8.5 and cicd-and-gitops-on-aws-guide: there, the case closed with "always read the full plan." Here, the case opens the question the rest of this guide answers: what happens when the discipline of reading the plan fails, as it did here, despite the warning?
The case, in the exact figures behind this lesson
In February 2026, Alexey Grigorev — founder of DataTalks.Club, the free courses platform — was using Claude Code to migrate a static site onto the platform's existing Terraform infrastructure, reusing the same project instead of creating a separate one, to save between five and ten dollars a month in duplicated infrastructure. At some point in that work, the agent replaced the active terraform.tfstate with an old version, extracted from an archived .zip file. With that outdated state as reference, Claude Code interpreted a large part of the real production infrastructure as orphaned resources, and ran terraform destroy -auto-approve.
The result: the entire VPC, the ECS cluster, the load balancers, the bastion host, the RDS database and its automatic snapshots, all deleted. Roughly 2.5 years of data — student assignment submissions, projects, course leaderboards — vanished with that single command. The courses_answer table, one of those affected, had 1,943,200 rows before the incident. Recovery took about 24 hours of work with AWS enterprise support, until an internal snapshot was found that didn't even show up listed in the AWS console — the mechanism that made it possible to restore the table with its 1,943,200 rows intact. Without that hidden snapshot, the loss would have been permanent.
The detail that makes this case pedagogically valuable, not just alarming: Claude Code had flagged the risk of the destroy before executing it, and the human in charge approved it anyway. This wasn't an agent acting in secret — it was a real warning, ignored under the pressure of getting the work done.
The failure chain, step by step
Before mapping controls, it's worth breaking the incident down into its exact sequence — because this lesson's question ("what would have stopped it?") only makes sense if you know exactly where in the chain each control would act:
THE FAILURE CHAIN — DataTalks.Club, February 2026
(1) The active state gets replaced by an old version (archived .zip)
│
▼
(2) Claude Code interprets real infrastructure as orphaned
│
▼
(3) The agent proposes `terraform destroy -auto-approve`
│
▼
(4) Claude Code FLAGS the risk of the destroy, explicitly
│
▼
(5) The human approves the destroy anyway, without reviewing the full plan
│
▼
(6) `terraform destroy` executes: VPC, ECS, ALB, bastion, RDS + snapshots
│
▼
(7) No deletion protection stops the RDS deletion
│
▼
RESULT: 2.5 years of data, 1,943,200 rows, ~24h of recovery
Seven steps. Point (4) is what makes this case more than "a runaway agent" — the warning existed. Points (5) and (7) are the only two places in the chain where something — human or mechanism — could have broken the sequence before the final outcome, and neither one did.
Mapping this guide's controls against the exact chain
This is the new part of this lesson, the part you won't find in terraform-and-iac-guide M8.5: for each control that Modules 2 through 7 of this guide build, exactly where in the chain above would it have acted, and would it have actually stopped it or just made it more visible?
Federated identity and least privilege (Module 2) — acts before point (6), partially
An OIDC identity with a trust policy scoped to a specific branch and repository (M2.5) wouldn't have stopped an authorized agent — which is exactly what Claude Code was in this case, not an external attacker — from proposing the destroy. Where it does act: least privilege (M2.7) applied to the identity that runs terraform apply in production could, in principle, explicitly exclude iam:*/rds:DeleteDBInstance/deletion actions from that identity's policy for certain resources marked as critical — but this depends on someone having designed that restriction beforehand, with the same kind of discipline that failed at point (1). Honest conclusion: it reduces the surface, it doesn't eliminate it — the same underlying problem (an identity with broad permission, used without enough review) is still present if the destroy permission wasn't explicitly restricted.
Preventive policy-as-code (Module 4) — acts exactly at point (5)/(6), and is the most direct answer
This is the central piece of this guide's response. A Rego policy evaluated with conftest test over the plan before there's any possibility of applying it — the same mechanism you'll build in M4.6 (no-destroy-shipments.rego) for Andes Cargo's analogous case — doesn't depend on a human reading the plan attentively. It depends on the plan containing an action: "delete" over a resource marked as critical, and on that condition, checked mechanically, failing the pipeline's job without exception. If DataTalks.Club had had a policy equivalent to no-destroy-shipments.rego over its production RDS, step (6) of the chain would never have executed — not because the agent had "decided" not to propose it, but because the pipeline would have failed the policy-check job before apply was even an available option. This is the exact difference between a "gate" that depends on human attention (what failed at point (5)) and a "build in" guardrail that doesn't.
IaC scanning (Module 5) — doesn't apply directly to this case
Trivy and Checkov evaluate declared configuration — what resources exist and how they're configured — not proposed destroy actions over the state. This piece wasn't the answer to this specific incident, and it would be dishonest to force the connection: it's named here precisely so it's clear that not every control in this guide answers every incident.
Supply chain (Module 6) — doesn't apply directly to this case
cosign signs and verifies deployment artifacts — code that gets deployed — not infrastructure commands run against the state. Same reason as Module 5: named for completeness, not forced as an answer.
Detective guardrails: CloudTrail (Module 7) — acts after point (6), and wouldn't have shortened recovery
A CloudTrail trail would have confirmed, with certainty and without depending on human memory, exactly which identity ran the destroy and at exactly what moment — valuable for reconstructing the incident afterward, and for a real-time alert if a system existed that reacted to that event. But it's honest to say precisely what it would NOT have done: the real bottleneck in recovery — the 24 hours — was finding a snapshot that didn't even show up listed in the console, a data recovery problem, not an attribution one. CloudTrail answers "who and when," not "how do we recover faster." It's a real guardrail, but the wrong category to shorten this specific recovery — the exact distinction Module 7 of this guide names between preventive and detective.
The piece named, not built, in this guide: deletion protection / prevent_destroy
Point (7) of the chain — no deletion protection on the RDS database or the rest of the resources — is, in effect, the last line of defense that was missing. This guide doesn't build that safeguard for Andes Cargo as a central piece of any module — it's outside its defined scope — but it's worth naming here precisely: the prevent_destroy argument inside a Terraform lifecycle block, or RDS/DynamoDB's native deletion protection, acts at the last possible link in the chain, after everything else has failed. No guide in this ecosystem builds it yet for Andes Cargo — it is, honestly, a declared gap, not a hidden one.
The conclusion that reframes the market warning
src/paths/aws-cloud-ecosystem/VALIDACION.md is explicit about why this case matters for 2026: "the dominant vector of 2026 is no longer the careless human but the agent with credentials." It's worth reading that sentence precisely, because it's easy to misread as "don't trust agents." The correct reading, the one this lesson supports with the full chain above: the risk vector isn't that an agent acts — it's that the only control layer between "something proposes an irreversible action" and "the action executes" remains, in 2026, a human's attention at a specific moment, exactly as it was before agents with infrastructure access existed. A rushed human, approving a destroy without reading the full plan, produces the same outcome as an agent whose warning gets ignored. Point (5) of this incident's chain would have failed the same way with a human engineer running the same command under the same rush.
What really changed in 2026 isn't "agents are dangerous" — it's that the speed at which an agent can propose and execute a complete sequence of commands compresses the time window in which a human would, in theory, have the chance to review carefully. Less time between "proposed" and "executed" means a control that depends on human attention has, literally, less opportunity to work. The right answer isn't "ban agents" — it's exactly Module 4's thesis for this guide: move the control point from "a human reads this carefully" to "an automatic mechanism blocks it without exception," regardless of what or who proposed the change.
Common mistakes
Concluding this lesson already built the protection against TM-06 (expectation mistake, revisited from lesson 1). What happens: someone finishes this lesson thinking Andes Cargo is already protected against an accidental destroy of Shipments. How to spot it: if you go looking, after this lesson, for a real change in andes-cargo-infra/. How to fix it: this lesson is analysis, not construction — mapping which control would have acted at which point of the chain. The real policy (no-destroy-shipments.rego) gets built only in Module 4, lesson 6.
Looking for a single answer in this case, ignoring that different controls act at different points in the chain (oversimplification mistake). What happens: someone, after reading this lesson, concludes that "the answer" is only conftest, and dismisses CloudTrail or deletion protection as irrelevant. How to spot it: if your summary of the case has a single "correct" control instead of a map of what does what. How to fix it: conftest is the most direct answer to point (5)/(6) of the chain — the most important one, because it prevents the outcome instead of only detecting or mitigating it — but a real security system uses independent layers: CloudTrail doesn't prevent, but it enables reconstruction; deletion protection is the last line if everything else fails. No single control is "the complete solution" — defense in depth, the same principle you already saw in terraform-and-iac-guide, still applies here.
Treating "the agent flagged the risk" as if that alone were already sufficient control (case-reading mistake). What happens: someone reads that Claude Code warned about the risk before executing, and concludes the system "worked as intended" because the warning existed. How to spot it: if your conclusion is "the agent did the right thing, the human failed." How to fix it: a warning a human can dismiss with no structural consequence is not a control — it's information. The central difference between this lesson and point (4) of the chain is exactly that: the controls in Module 4 of this guide don't ask permission or warn, they block. A warning that can be approved with no friction is, in practice, equivalent to having no warning at all.
Exercises
Exercise 1 — Locate the exact point in the chain where conftest (M4) would have acted. Of this lesson's seven points in the failure chain, at which one exactly would an equivalent no-destroy-shipments.rego policy act, and why that point and not another?
See solution
At point (6) — before terraform destroy actually executed, evaluating the plan that point (3) had already generated. The policy doesn't intervene at (1) (the corrupted state), nor at (2) (the agent's interpretation), nor at (4) (the warning) — it intervenes specifically at the moment when that plan, already generated, would have to pass an automatic check before turning into a real apply. If the plan contains a deletion action on a resource marked as critical, conftest test fails the job, and the pipeline never reaches point (6) the way it did in the real case.
Exercise 2 — Explain why CloudTrail (M7) isn't "useless" for this case, even though it wouldn't have shortened the recovery. A colleague, after reading that CloudTrail wouldn't have reduced the 24 hours of recovery, concludes that piece "is worthless" in this context. Do you agree?
See solution
Disagree. CloudTrail answers a different and equally valuable question: not "how do we recover the data faster?", but "can we confirm, with evidence and not memory, exactly what happened, who ran it, and when?" — the same Repudiation (TM-03) distinction from lesson 5. A detective guardrail doesn't compete with a preventive one over "which is more useful" — it serves a different function, leaving a queryable trail of evidence, which a preventive guardrail alone doesn't provide (if conftest blocks the destroy, there's no incident left to investigate; but a real system needs both layers, because not everything that goes wrong was successfully blocked).
Exercise 3 — Design a minimal policy, without writing the full Rego. If you had to describe, in plain language, the rule no-destroy-shipments.rego (M4.6) is going to implement, what exact condition in the JSON plan should it look for to block it? Hint: check the resource_changes/actions structure that lesson 5 of Module 4 of this guide will explore.
See solution
The rule, in plain language: "for each element of resource_changes in the plan, if the resource type is aws_dynamodb_table, the name matches Shipments, and the change.actions array contains the value \"delete\" (alone or combined, as in [\"delete\", \"create\"] for a replacement), the policy fails and returns a message explaining which resource and why." You don't need the exact Rego yet — that arrives in Module 4 of this guide — the goal of this exercise is to correctly identify that the condition lives in the combination of type + name/address + actions inside each resource_changes element, the same structure this guide's design already named for M4.5.
Summary and next step
In this lesson you revisited the Claude Code destroy incident against DataTalks.Club — 2.5 years of data, 1,943,200 rows in courses_answer, ~24 hours of recovery, the agent's warning ignored by the human in charge — from a new angle: you broke down the complete seven-step failure chain, and mapped every control that Modules 2 through 7 of this guide build against the exact point where it would have acted. The central conclusion: conftest (M4) is the most direct answer, because it acts before the apply, without depending on human attention at the critical moment; CloudTrail (M7) answers a different and equally necessary question; and the 2026 vector isn't "don't trust agents" — it's that an agent's speed compresses the window where a control dependent on human attention has a real chance to work.
Before moving on you should be able to: narrate the complete seven-step chain, with the exact figures; explain why conftest is the most direct answer to this specific case, and why CloudTrail and deletion protection aren't redundant with it; and explain the correct reframing of "the 2026 vector is the agent with credentials" without landing on "don't use agents."
Lessons 7 and 8 close the module with the two deliverables this analysis feeds: the formal threat model document, and the risk map that will govern, module by module, the rest of this guide.
Resources
- Alexey Grigorev — How I Dropped Our Production Database — the firsthand account from the person directly affected; the primary source for the exact figures (1,943,200 rows, 2.5 years of history, ~24 h of recovery) and the date (February 26, 2026).
- Hacker News — original incident thread (#47278720) — the public discussion that brought the case to light.
- incidentdatabase.ai — Incident 1424 — the structured, independent record of the incident.
- Hacker News — interactive replay of the incident (#47323915) — the thread that documents, among other details, that the agent flagged the risk before executing the command.
terraform-and-iac-guide, Module 8, lesson 5 (05-blast-radius-and-the-claude-code-destroy-incident.md) — this case's original, already-verified account in full, which this lesson doesn't repeat but reframes.src/paths/aws-cloud-ecosystem/VALIDACION.md— the source of the quote "the dominant vector of 2026 is no longer the careless human but the agent with credentials," reinterpreted in this lesson.