Case Study 2: Anatomy of a Public-Bucket Data Exposure
"The breach was discovered by a stranger with a search tool. That sentence should terrify every cloud engineer." — A post-incident review, paraphrased
Executive Summary
This case study dissects the single most common kind of cloud breach — a publicly exposed storage bucket — not at Meridian, but at a fictional health-and-wellness technology company, Lumadyn Health, whose business is a consumer app that stores users' fitness, sleep, and basic health data. The point of studying it here, in a different sector, is that the mechanism is sector-independent: the same misconfiguration that would expose a bank's loan documents exposes a health app's user records, and the same chain of small failures produces the same headline. Where Case Study 1 was a design and remediation exercise (Meridian proactively hardening before anything bad happened), this one is a detection and analysis exercise — a forensic reconstruction of an exposure that ran for months and was found not by the company but by an outside researcher, with an SSRF-driven escalation thrown in to show how a "harmless" public bucket interacts with the rest of a cloud account. The company, incident, and all data are constructed for teaching (Tier 3), but the pattern is drawn from the general shape of many widely reported public-bucket incidents.
Skills applied: reconstructing a cloud exposure from logs after the fact; reading bucket ACLs and policies to determine exactly who could access what; tracing an SSRF-to-metadata escalation; distinguishing "no evidence of harm" from "no harm"; mapping the failure to the controls (guardrails, CSPM, least-privilege roles, IMDSv2) that would have prevented or detected it; deriving breach-notification implications.
Background
Lumadyn Health is a 90-person startup with a popular consumer app. It is "born in the cloud" — entirely on AWS, with a small engineering team that prizes shipping speed and has, until this incident, treated security as something to address "after product-market fit." Its architecture is unremarkable: a mobile app talks to an API running on cloud VMs, which read and write user data in a set of S3 buckets and a managed database. The company holds sensitive personal data — health-adjacent information that, while not always covered by medical-privacy law depending on jurisdiction, is exactly the kind of data whose exposure produces user harm, regulatory attention, and reputational damage.
Crucially for this study, Lumadyn had none of the controls Meridian built in Case Study 1: no account-wide Block Public Access, no CSPM, CloudTrail enabled but never monitored, IAM roles written with wildcards "to move fast," and no guardrails of any kind. This is not negligence by villains; it is the default state of a fast-moving team that has not yet done the work this chapter describes. The exposure that follows is what that default state produces.
🔗 Connection: Lumadyn is the counterfactual to Meridian. Meridian (Case Study 1) did the posture review and built the guardrails; Lumadyn did not. Reading the two together is the argument for the chapter: the same cloud, the same misconfigurations, two outcomes — one a proactive remediation, the other a public breach — separated almost entirely by whether the team had done the preventive work in advance.
The Incident
How it started: one setting, one developer, eight months
The exposure began the way they always do — small, reasonable, and forgotten. Eight months before
discovery, a developer needed to let a third-party analytics contractor pull a sample of anonymized usage
data from a bucket named lumadyn-user-exports. Rather than set up scoped, temporary access (which would have
taken an afternoon to do right), the developer made the bucket public — READ to AllUsers — sent the
contractor the URL, and moved on to the next ticket. The contractor got their sample. The bucket stayed
public. And over the following eight months, automated processes wrote more than the intended sample into
it: nightly export jobs, configured to drop files in lumadyn-user-exports, accumulated full user-data
exports — not the anonymized sample the developer had pictured, but real records with user identifiers,
email addresses, and health metrics. The public setting and the export pipeline, each innocuous alone,
combined into a growing pool of sensitive data readable by anyone on the internet.
Bucket: lumadyn-user-exports (the exposure)
Grants:
- Grantee: { ID: "ownerid..." } FULL_CONTROL
- Grantee: { URI: ".../groups/global/AllUsers" } READ <-- PUBLIC for 8 months
Contents (accreted over time):
/samples/anon-2025-q4.csv (the intended, harmless file)
/nightly/user-export-2026-01-14.json.gz (real user records)
/nightly/user-export-2026-01-15.json.gz (real user records)
... 240 more nightly exports ...
No alarm fired, because from AWS's perspective nothing was wrong: a customer had configured a bucket exactly
as the customer was permitted to. The shared-responsibility line did its job — and that is precisely the
point. AWS will not save you from your own configuration; the public setting was Lumadyn's decision to make,
and Lumadyn had no detective control (CSPM, or even an alert on PutBucketAcl) watching its own side of the
line.
Discovery: a stranger with a search tool
The breach was discovered by someone who did not work at Lumadyn and was not targeting it. A security
researcher running a routine scan for public buckets — the kind of indiscriminate enumeration the chapter
described, which lists public buckets across the cloud all day — found lumadyn-user-exports, sampled its
contents, recognized real user data, and did the responsible thing: emailed Lumadyn's security contact with a
disclosure. The first Lumadyn's leadership heard of the exposure was that email. There had been no break-in,
no malware, no alert — just a door built without a lock that a passerby noticed.
It is worth being precise about how such buckets are found, because the mechanism is what makes "we were
not targeted" no comfort at all. Object-storage bucket names share a global namespace, and a bucket's
contents, once public, are reachable at a predictable URL. Researchers and attackers alike run tools that
generate likely bucket names (company name, product name, common suffixes like -exports, -backups,
-prod, -dev), test each for public accessibility, and list the contents of any that respond. Some of
these tools are open-source and run continuously; some feed commercial "exposed data" monitoring services;
some feed criminal marketplaces. A bucket named lumadyn-user-exports is almost designed to be guessed —
it follows the obvious naming pattern for a company called Lumadyn. The exposure was not bad luck; it was the
predictable result of a guessable name plus a public setting plus enough time, and "enough time" was eight
months.
⚠️ Common Pitfall: Lumadyn's leadership initially wanted to dismiss the disclosure as "just a researcher, no real attacker found it." This is exactly backwards. The fact that a benign researcher found the bucket trivially means that any number of malicious scanners — which run the same enumeration continuously — could have found it just as easily over eight months, and would have left far less courteous evidence. "A researcher found it" is not reassurance; it is proof that the exposure was discoverable by anyone, which is the definition of a breach for notification purposes in many jurisdictions.
The escalation: from a public bucket to the account
The story would be bad enough as a data exposure. But Lumadyn's incident responders, now urgently auditing the whole account, found that the public bucket was not the only problem — and that an attacker who had found it could have gone much further. Reconstructing from CloudTrail (which had been recording, even though no one watched it — the log was there when they finally needed it), they traced a second, more dangerous weakness:
Lumadyn's API ran on cloud VMs whose attached IAM role used "Action": "*" — full account access, "to move
fast." And the API had an SSRF vulnerability in an image-proxy feature (the kind of web flaw studied in
Chapter 13). The chain is the one from §15.4:
The escalation chain (what an attacker COULD have done)
───────────────────────────────────────────────────────────────
1. SSRF bug in the API -> attacker makes the server fetch a URL it chooses
2. Target URL: http://169.254.169.254/.../ (the instance metadata service)
3. Metadata returns the VM role's TEMPORARY IAM credentials
4. Role policy = "Action":"*" -> those credentials = FULL account control
5. Attacker enumerates and downloads EVERY bucket + the database -> total breach
───────────────────────────────────────────────────────────────
Any ONE of these controls breaks the chain:
- IMDSv2 enforced (blocks the simple SSRF -> metadata read)
- Least-privilege VM role (stolen credential bounded to what the API needs)
- SSRF fixed in the app (no metadata fetch in the first place)
The responders could not prove this chain had been exploited — but they could prove it was possible, and that the metadata service was the un-hardened version (IMDSv1) that makes the SSRF path trivial. The public bucket was the exposure that got discovered; the SSRF-plus-wildcard-role was the latent total-compromise sitting one web bug away. This is the chapter's defense-in-depth argument made flesh: a single over-broad role turned a minor, common web vulnerability into an account-ending one, and three independent controls — any one of which Lumadyn lacked — would each have contained it.
The discovery of the latent escalation changed the character of the incident inside Lumadyn. The public bucket was embarrassing but bounded: a known set of data, exposed, that they could enumerate and assess. The SSRF-plus-wildcard-role finding was something worse — a path by which an attacker who never touched the public bucket at all could have taken the entire account: every other bucket, the production database, the ability to create persistent backdoor identities and then delete the logs that recorded them. The responders had to hold two facts at once: there was no evidence this had happened, and they could not have detected it if it had, because no one watched the logs and the metadata service left no trace of a credential read on the instance itself. "We found the unlocked window," the incident lead wrote, "while investigating the unlocked door. We have no evidence anyone climbed through the window. We also had no alarm on it. Both of those sentences are true, and the second is the one that should change how we operate." This is the uncomfortable honesty that mature incident response demands: distinguishing what you know happened from what you cannot rule out, and treating the gap in your own visibility as a finding in its own right.
🛡️ Defender's Lens: Note how the same root cause — over-broad permissions — appears twice in this incident, in two forms. The public bucket is over-broad data access (the whole internet can read it); the wildcard VM role is over-broad identity access (the VM can do anything). Both are least-privilege failures. A reviewer who internalizes "find the over-broad grant" — whether it is an ACL granting
AllUsersor a policy granting*— catches both with the same reflex, which is exactly whycloudpost.py's two functions (s3_publicandiam_overbroad) are the chapter's toolkit core.
The response and the reckoning
Lumadyn's actual response, once the disclosure landed, was a compressed and painful version of Meridian's proactive review:
- Contain immediately: revert
lumadyn-user-exportsto private; the bucket stopped being readable within minutes of the team understanding the email. - Assess exposure from logs: search S3 access logs for
GetObjectrequests from outside Lumadyn's known ranges over the eight-month window. Here the lesson is sobering — Lumadyn had not enabled S3 access logging on this bucket, so it could not fully determine who had downloaded what. They had CloudTrail (control-plane events) but not data-plane access logs. The honest finding was therefore: we cannot prove the extent of access, which for breach-notification purposes is often treated as the worst case. - Harden the latent escalation: enforce IMDSv2, replace the
*VM role with a least-privilege policy, and prioritize the SSRF fix in the application. - Build the controls that were missing: enable account-wide Block Public Access, deploy a CSPM tool,
wire
PutBucketAcl/PutBucketPolicypublic-access events andStopLoggingto alerts, and set guardrails — the entire Case Study 1 baseline, now built reactively under regulatory and press scrutiny instead of calmly in advance. - Notify: because Lumadyn could not rule out malicious access to identifiable user data, it faced breach-notification obligations to users and regulators — the expensive, reputation-damaging outcome that the afternoon of scoped-access work, eight months earlier, would have entirely avoided.
The notification decision deserves a closer look, because it is where the missing data-plane logging translated directly into worse outcomes. Breach-notification laws generally turn on whether sensitive personal data was, or may reasonably have been, accessed or acquired by an unauthorized party. An organization that can demonstrate, from complete access logs, that no external party downloaded the data may be able to limit or avoid notification. Lumadyn could not demonstrate that, because it had never enabled the logs that would record it. The legal and risk advice that followed was the predictable consequence: in the absence of evidence that the data was not accessed, and given that it was openly accessible for eight months at a guessable URL, the prudent and often legally required course was to assume it may have been and notify accordingly. The absence of a control (access logging) thus did not merely create a blind spot — it shifted the entire incident from "we can prove this was contained" to "we must assume the worst," with all the cost that assumption carries. This is the most expensive way to learn that logging is not optional.
📟 War Story: A constructed but representative footnote. In the rushed week after disclosure, a Lumadyn engineer — trying to help — quietly deleted the exposed bucket entirely, reasoning that destroying the data ended the exposure. It did end the exposure, but it also destroyed evidence the investigation and any regulator would need, and it did nothing about the other buckets the latent SSRF chain could reach. The incident lead had to stop this and reset the team's understanding: in an active incident, you contain (make private) and preserve, you do not destroy, because the questions "what was exposed?" and "was it accessed?" can only be answered from evidence you keep. Panic deletes evidence; discipline preserves it. (Forensic soundness and evidence preservation are Chapter 25's subject; the instinct to protect the evidence is worth forming now.)
🔄 Check Your Understanding: Lumadyn had CloudTrail (control-plane logs) but not S3 access logs (data-plane logs) on the exposed bucket, so it could see that the bucket was made public but not who downloaded its contents. Why does this distinction matter enormously for breach notification, and what should Lumadyn enable to avoid being blind to data-plane access in the future? (Hint: "we cannot prove who accessed it" usually forces you to assume the worst.)
Root cause: it was never one mistake
When Lumadyn ran its blameless post-incident review (the kind of analysis Chapter 24 formalizes), the most important finding was that there was no single root cause — and that looking for one would have led to the wrong fix. A naive analysis stops at "a developer made a bucket public," concludes "the developer was careless," and "fixes" the problem by telling people to be more careful. That fix changes nothing, because the next exposure will come from a different developer making a different reasonable-seeming choice. The honest analysis traces the chain of contributing causes, each of which is a systemic gap rather than an individual failing:
Why was customer data readable by the internet for 8 months?
──────────────────────────────────────────────────────────────
Because a bucket was public ............... (the proximate cause)
↳ Why did making it public succeed? ...... no account-wide Block Public Access (no GUARDRAIL)
↳ Why did real data end up in it? ........ an export job wrote there; nobody reviewed data flow
↳ Why did it stay public for 8 months? ... no CSPM and no alert on PutBucketAcl (no DETECTION)
↳ Why was the blast radius total-able? ... wildcard VM role + IMDSv1 + SSRF (no LEAST PRIVILEGE,
no metadata hardening, no appsec gate)
↳ Why couldn't they scope the harm? ...... no S3 access logging (no EVIDENCE)
──────────────────────────────────────────────────────────────
Every "why" is a MISSING SYSTEMIC CONTROL, not a careless person.
Read down that chain and the lesson of the chapter arrives with force: the exposure was not one mistake but the absence of an entire layer of controls — guardrails, detection, least privilege, metadata hardening, and evidence — any one of which would have shortened the eight months, bounded the blast radius, or made the exposure impossible in the first place. This is precisely why the remediation could not be "remind everyone to check bucket permissions." It had to be the systemic baseline from Case Study 1, installed reactively. The difference between Lumadyn and Meridian was never the quality of their engineers; it was whether the systemic controls existed before a reasonable individual choice turned into a breach.
🚪 Threshold Concept: When you analyze a cloud breach, resist the pull toward "someone was careless." Careless individuals are a constant; you cannot engineer them away, and a security program that depends on everyone always being careful is not a program. The transferable question is structural: what control was missing such that one ordinary mistake became a breach? That question turns every incident — yours or the industry's — into a list of guardrails to build, which is the only kind of lesson that prevents the next one. Internalize this and post-incident reviews stop being about blame and start being about architecture.
The two outcomes, side by side
The clearest way to extract the lesson is to put Lumadyn next to Meridian (Case Study 1) directly. They ran the same cloud, exposed to the same possible misconfigurations, with engineers of comparable skill. The difference in outcome was entirely a function of which controls existed in advance:
| Dimension | Lumadyn (no controls) | Meridian (baseline built) |
|---|---|---|
| Public bucket possible? | Yes — became public, stayed 8 months | No — Block Public Access guardrail makes it impossible |
| How an exposure is found | By an outside researcher (or attacker) | By the team's own CSPM alert, in minutes |
PutBucketAcl to public |
Silent; no alert | Alerts the SOC at the moment it happens |
| VM role scope | "Action":"*" (total blast radius) |
Least-privilege, MFA-gated for admin |
| Metadata service | IMDSv1 (SSRF path trivial) | IMDSv2 enforced |
| Logging | CloudTrail on but unwatched; no data-plane logs | All-region, delete-proof, monitored; access logs on sensitive buckets |
| Could they scope the harm? | No — forced to assume the worst | Yes — evidence preserved and reviewed |
| Outcome | Public breach, notification, reputational damage | A finding closed quietly, before any harm |
The table is the chapter's argument in one frame: the controls are the difference. Lumadyn is not a story about bad engineers; it is a story about what the default state of a cloud account produces when the preventive and detective layers this chapter describes have not yet been built. The work of cloud security is moving every row of that table from the left column to the right — ideally before an incident forces it, but always, eventually, because the left column is not a stable place to live.
⚖️ Authorization & Ethics: A closing note on the researcher who found and disclosed the bucket. Responsible disclosure — finding an exposure and quietly notifying the owner rather than exploiting or publicizing it — is a norm that benefits everyone, and Lumadyn was fortunate the finder honored it. But "I found it on a public scan" is not a blanket license: enumerating and downloading another organization's data, even from a misconfigured-public bucket, sits in legally contested territory, and accessing systems or data you are not authorized to access can be unlawful regardless of how easy it was. The defender's takeaway is twofold: assume your exposures will be found, and if you are ever the finder, stop at confirmation, minimize what you access, and disclose — never exploit (Chapter 39 develops the ethics and the law).
Discussion Questions
- The exposure resulted from two reasonable decisions combining: making a bucket public for a one-time share, and pointing a nightly export job at that same bucket. Neither developer did anything obviously reckless. What does this say about relying on individual engineers to "be careful," and how do guardrails change the equation?
- Lumadyn's leadership wanted to treat "a researcher found it, not an attacker" as mitigating. Construct the strongest argument for why this is the wrong frame, referencing how public buckets are discovered.
- The breach was a data exposure, but the latent SSRF-plus-wildcard-role chain was arguably the more serious finding. Should an incident report foreground the exposure that happened or the total compromise that could have? How do you communicate "we got lucky on something worse" to a board without either minimizing or fear-mongering?
- Lumadyn could not determine the extent of data access because it lacked S3 access logging. Discuss the tradeoff: data-plane access logging has cost and volume. When is it worth enabling, and for which buckets would you always enable it?
- Compare Lumadyn and Meridian. Both ran the same cloud with the same possible misconfigurations. Identify the single most consequential difference in what they had done in advance, and defend your choice.
Your Turn
Reconstruct and prevent this incident as an analyst:
- Reconstruct. Given only the bucket ACL and the CloudTrail
PutBucketAclevent (with timestamp and the IAM user who made it), write the two-sentence timeline of how the exposure began and how long it ran. - Scope the harm. List the specific logs you would search to answer "who accessed this data?" and state what you would conclude if the necessary access logs did not exist (as at Lumadyn).
- Trace the escalation. Given the wildcard VM role and the SSRF bug, write the five-step chain to full compromise and the three independent controls that each break it.
- Prevent the recurrence. Write the four preventive/detective controls that, had they existed, would have stopped this incident from happening or running undetected for eight months. Label each as guardrail (preventive) or CSPM/alert (detective).
Keep it to two pages. End with one sentence: the single change you would prioritize if Lumadyn could only do one thing — and why.
Key Takeaways
- The most common cloud breach is mechanism-independent of sector: a public storage bucket exposes a health app's user data exactly as it would a bank's loan documents. The pattern, not the industry, is the lesson.
- Exposures arise from innocuous decisions combining — a one-time public share plus an automated job that writes into the same bucket — which is precisely why "be careful" fails and guardrails (Block Public Access) that make the dangerous state impossible succeed.
- Public buckets are found by indiscriminate automated enumeration, so "a researcher found it" proves the exposure was trivially discoverable by anyone — it is evidence for a breach, not against one.
- An over-broad VM role plus an SSRF bug plus an un-hardened metadata service turns a minor web vulnerability into total account compromise; IMDSv2, least-privilege roles, and fixing the SSRF are three independent controls, any one of which breaks the chain (defense in depth).
- The distinction between "no evidence of harm" and "no harm" is decisive: without data-plane access logs, Lumadyn could not prove who downloaded the data, which for notification purposes usually forces the worst-case assumption. Enable access logging on sensitive buckets before you need it.
- Lumadyn is Meridian's counterfactual: same cloud, same possible mistakes, opposite outcomes — separated almost entirely by whether the preventive work (Case Study 1's baseline) was done in advance rather than under the scrutiny of a public breach.