kick the bucket

Kick the Bucket: S3 Bucket Squatting Through a Series of Half-Patched AWS Patterns

Dan Gansel November 02, 2026

Kick the Bucket: S3 Bucket Squatting Through a Series of Half-Patched AWS Patterns

Book A Demo
See how Upwind uses runtime context to turn cloud security fingdings into real-time protection.
Book a Demo

In March 2026, AWS announced account-namespaced S3 buckets – a platform-level change that scopes bucket names to individual accounts, eliminating the global namespace collision that makes bucket squatting possible. Articles followed declaring the vulnerability class dead. The community moved on.

We didn’t move on. We checked.

Over the following weeks, we found three exploitable S3 bucket squatting vulnerabilities across two AWS open-source projects: the SageMaker Python SDK and aws-sdk-pandas (awswrangler). The impact ranged from remote code execution on live ML endpoints to silent exfiltration of Athena query results to EMR log capture – all against services where fixes were already in place. All three vulnerabilities have since been patched.

This post is the fourth in a series covering this research. Here we share the methodology behind the discovery, the systemic patterns that allowed these vulnerabilities to survive remediation, and what defenders should do about it. The three vulnerability-specific posts provide the full technical detail for each finding.


The Three Primitives

Every S3 bucket squatting vulnerability we’ve seen – ours and prior work – requires three conditions to converge:

  1. Shared namespace. S3 bucket names are globally unique within a partition. A name claimed by one account cannot be used by another.
  2. Predictable name. AWS services and SDKs construct default bucket names from deterministic components – typically {service}-{account_id}-{region} or a close variant. An attacker who knows or can guess these components knows the bucket name.
  3. No ownership check. When the service or SDK encounters an existing bucket matching the expected name, it uses it without verifying that the bucket belongs to the caller’s account.

A fix only needs to break one of these three primitives to eliminate the vulnerability. Account-namespaced buckets break primitive 1. ExpectedBucketOwner breaks primitive 3. Adding entropy to bucket names breaks primitive 2.

Every fix we found broke zero.

Prior Art

This research builds on prior work that established and extended bucket squatting as a vulnerability class:

  • Aqua Nautilus, “Bucket Monopoly” (Black Hat USA / DEF CON 32, August 2024) – the foundational research that established bucket squatting as a vulnerability class. Demonstrated the attack across six AWS services, leading to service-specific patches from AWS. Their closing observation – “this attack vector can still apply on other AWS services and open-source projects” – is basically the hypothesis we tested in hindsight.
  • Spike Reply, “Amazon S3 Bucket Name Squatting” (2024) – documented predictable naming patterns across additional AWS services including Athena. Relevant here because they confirmed the Athena service-level fix worked – which makes our finding that the SDK-level path was never patched a direct continuation of their work.
  • Unit 42, “Pickle in the Middle”, Ori Hadad (June 2026) – published after our findings, this independently demonstrated the same vulnerability class in Google Cloud’s Vertex AI SDK, leading to cross-tenant RCE via model poisoning. It confirms the pattern is not AWS-specific and that researchers are still finding it – the same three primitives (shared namespace, predictable name, no ownership check) apply wherever cloud SDKs construct default resource names.

Why We Looked

By early 2026, the consensus was clear: bucket squatting was a solved problem. AWS had patched the services prior research flagged, account-namespaced buckets eliminated the shared namespace entirely, and the community moved on. Blog posts declared the vulnerability class dead.

At the same time, a separate hype cycle was building around AI-powered vulnerability discovery – the idea that LLMs could replace manual code auditing at scale, finding entire classes of bugs across codebases automatically. A well-defined vulnerability pattern across a large corpus of open-source code seemed like a good case study.

So we combined both: we built an AI-assisted scanner to sweep AWS open-source repositories for surviving bucket squatting patterns, and used the results to guide the manual work that followed.

How We Found Them

Automated Scanning

We built an AI-assisted scanner targeting 100+ AWS open-source repositories on GitHub. The pipeline worked in two stages:

Prescan: A regex filter searched for string formatting patterns that combine service prefixes with account IDs and regions – the signature of a predictable bucket name. This produced dozens of candidates.

Fullscan: An LLM-based analysis evaluated each candidate against a four-condition smell test:

  1. Does the resource live in a shared namespace? (S3 global buckets)
  2. Is the name predictable? (Derived from account ID, region, or framework defaults)
  3. Is the payload valuable? (Executable code, credentials, configuration, or user data)
  4. Is there an ownership check? (Or does the code trust the name alone?)

This produced a smaller set of confirmed findings.

Where It Broke Down

Frontier models were too expensive to run against 100+ repositories. Weaker models hallucinated – marking test utility functions as confirmed vulnerabilities, or misinterpreting commented-out code as live paths. Server-side defenses were invisible to the scanner, since it only analyzed client code. A service might have a predictable name in the SDK but validate ownership on the API side – the scanner couldn’t see that.

The Pivot

The real findings required manual code review. We needed to pick targets.

AI/ML services were the obvious choice – not because of any particular scanner signal, but because everybody was researching AI, and these SDKs had the right properties: open source, heavy S3 usage, and high-value payloads (executable code, model artifacts, training data, query results). We used an LLM to enumerate open-source AWS AI/ML libraries that natively accept s3:// URIs, which pointed us to the SageMaker Python SDK.

image-18
ChatGPT response

When we started reading the SageMaker Python SDK, we found something unexpected: it already had an explicit defense against bucket squatting – an ExpectedBucketOwner check added in 2023, clearly intended to prevent this exact attack. The defense was there, but something about the implementation looked off. It was – the defense had a logic bug that left the attack path fully open.

Once we confirmed the SageMaker vulnerability, we knew there would be more. If a high-profile SDK with an intentional fix was still vulnerable, less scrutinized libraries almost certainly were too.

The next step came from trying to write detection rules. As we built CSPM-style checks for predictable bucket patterns, we realized the same data we needed for detection – S3 bucket inventories, CloudTrail logs, naming patterns in use – could also guide us to more vulnerable targets:

  • Customer environment analysis: We checked whether the predictable default bucket patterns were actually in use across production environments.
  • CloudTrail origin tracing: We mapped suspicious buckets back to the specific SDK or service that created them.
  • GitHub search: We tracked how widely the default patterns had been copied from official AWS documentation and tutorials into production code.

This led us to aws-sdk-pandas, where the Athena and EMR findings followed. The automated scanner was good for kicking off the research, the detection work pointed us to real targets, and the findings themselves came from reading code.

The Three Findings

ServicePredictable Bucket NameImpactFix Status
SageMakersagemaker-{region}-{account_id}RCE via code injection, persistent endpoint exfiltration, IAM credential theftFixed in v3.10.0 (May 2026)
CVE-2026-112408
Athenaaws-athena-query-results-{account_id}-{region}Query result exfiltration, data poisoning via CTAS manifest hijackFixed in v3.16.1 (May 2026)
CVE-2026-118029
EMRaws-logs-{account_id}-{region}Log exfiltration (Spark secrets, PII, step source code)Fixed v3.18.0 (October 2026)
AWS Reviewing possible CVE

SageMaker was the most severe. The default bucket carries executable code – processing scripts, model artifacts, serving code – that runs in containers with the victim’s IAM role. We demonstrated two independent RCE vectors: processing job code injection and persistent endpoint serving code injection. A single squatted bucket gives the attacker a persistent tap on a live ML pipeline: every inference request exfiltrated, live IAM credentials harvested, and the victim’s code runs normally with no errors or warnings.

Athena was a direct continuation of prior work. AWS identified Athena as vulnerable a while back. They patched the service. But the aws-sdk-pandas library – published by AWS – constructs the same predictable bucket name independently and was never patched. The library’s error handler treats BucketAlreadyExists (someone else owns it) and BucketAlreadyOwnedByYou (you own it) identically. Every query result flows to whoever created the bucket first.

EMR was the simplest mechanically and the most concerning operationally. The library constructs the LogUri as a string and passes it to the RunJobFlow API. It never calls CreateBucket, never calls HeadBucket, never touches S3 at all. The API accepts any LogUri without validation. We found this pattern actively exploitable in customer environments – clusters across multiple organizations using the default logging path, with the target buckets unclaimed.

Three Fixes, Three Failure Modes

The individual vulnerabilities are interesting. The pattern is the real finding.

Each service we examined had a defense in place – and each defense failed in a different way. Together, they form a taxonomy of how vulnerability class remediation goes wrong when applied piecemeal.

Failure Mode 1: Defense Code Present But Not on the Attack Path

The SageMaker Python SDK added ExpectedBucketOwner validation in v2.190.0 (October 2023). The fix was intentional, specific, and addressed exactly this attack. It was also broken.

The ownership check only runs when bucket.creation_date is not None – which means the bucket appeared in the caller’s list_buckets() response, which means the caller already owns it. When an attacker pre-creates the bucket, creation_date is None, and the code takes the unprotected branch. The fix protects the case that doesn’t need protection and skips the case that does.

Beyond this logic bug, multiple S3 upload and download operations across the SDK never pass ExpectedBucketOwner at all. Even a corrected session-level check wouldn’t reach them.

The vulnerability remained fully exploitable for over two years after the fix was introduced.

Failure Mode 2: Service-Level Fix That Missed SDK-Level Code

AWS remediated the Athena service by requiring users to specify an S3 bucket for query results rather than auto-creating one with the predictable name.

The aws-sdk-pandas library was not part of that remediation. It independently constructs aws-athena-query-results-{account_id}-{region} in its own create_athena_bucket() function and silently catches the exception that would indicate the bucket belongs to someone else. The service-level fix did not propagate to the SDK layer.

This is the gap where “fix the service, not the class” breaks down. The Athena service was patched – it now requires users to specify a bucket name. But the AWS-published SDK that wraps Athena still picks the name for you, using the same predictable pattern the service-level fix was supposed to eliminate.

Failure Mode 3: Console Hardening Without API Hardening

AWS hardened the EMR console to validate bucket ownership before starting a cluster. If the default logging bucket is owned by another account, the console blocks the operation.

The EMR RunJobFlow API has no equivalent check. It accepts any LogUri value and starts the cluster. Any client calling the API directly – aws-sdk-pandas, the AWS CLI, boto3 scripts, CI/CD pipelines – bypasses the console’s protection entirely.

The library never touches S3 at all. It builds a string (s3://aws-logs-{account_id}-{region}/elasticmapreduce/) and passes it to the API. There is no CreateBucket call to trigger the BucketAlreadyExists check. There is no HeadBucket call to detect anything. The API is the last line of defense, and it performs no validation.

The Pattern

There is no unified defense strategy for bucket squatting across AWS. Each service team independently implements its own mitigation – ExpectedBucketOwner in one, console-side validation in another, server-side naming changes in a third – and each implementation has its own blind spots.

Account-namespaced buckets are the platform-level fix that should eliminate the entire class. But a platform primitive only works if everything above it migrates: services, SDKs, CLI tools, documentation, tutorials, and the customer code that is copied from them. None of the SDKs we examined had adopted account-namespaced buckets. Until that migration is complete – across every layer – the old predictable names remain the default, and the vulnerability class survives.

The Shared Responsibility Gap

Cloud shared responsibility models assume the customer controls their configuration. That assumption breaks when vendor-published SDKs make the configuration choices for the customer.

In each of our findings, the vulnerable behavior was the default. The SDK chose the bucket name. The documentation demonstrated usage without overriding it. Customers who followed the official getting-started path inherited the vulnerability without making a single security decision – because the SDK made it for them.

This creates a gap that neither side owns. The customer can’t fix the SDK. The vendor treats the SDK’s defaults as the customer’s configuration. The result is a vulnerability that persists because it falls between two responsibility boundaries, with each side reasonably pointing to the other.

The broader problem is what happens after a fix ships. Half-patched vulnerabilities are worse than unpatched ones. An unpatched vulnerability gets scrutinized – researchers look for it, defenders test for it, vendors prioritize it. A “fixed” vulnerability gets forgotten. When a fix ships and the community declares the class dead, defenders stop checking. Attackers don’t need new techniques. They need the old techniques to still work – and they do.

Beyond the SDKs

Our data shows that the problem extends beyond SDK users. Customer-built applications have adopted the same predictable naming conventions – inspired from official documentation and tutorials into production infrastructure. We see patterns like my-company-{account_id}-{region} used as default bucket templates in internal tooling, IaC modules, and deployment scripts. The vulnerable defaults have become a template that the ecosystem reproduces independently, well beyond the reach of any single SDK patch.

What Defenders Should Do Now

Immediate

  • Upgrade SDK versions: SageMaker Python SDK to v3.10.0+, aws-sdk-pandas to v3.18.0+.
  • Check bucket ownership for any bucket matching these patterns:
  • sagemaker-{region}-{account_id}
  • aws-athena-query-results-{account_id}-{region}
  • aws-logs-{account_id}-{region}

Run aws s3api head-bucket --bucket {name} --expected-bucket-owner <your-account-id> and verify the owner matches your account.

  • Pre-create default buckets yourself in your own account before first SDK use, if they don’t already exist.

Posture Hardening

  • Stop using SDK defaults. Pass explicit, uniquely-named bucket parameters to all SDK calls. default_bucket() and its equivalents are the vulnerability surface.
  • Apply SCPs that deny S3 operations to buckets outside your organization:
{

  "Effect": "Deny",

  "Action": ["s3:*"],

  "Resource": "arn:aws:s3:::*",

  "Condition": {

    "StringNotEquals": {

      "aws:ResourceOrgID": "${aws:PrincipalOrgID}"

    }

  }

}
  • Adopt account-namespaced buckets for all new bucket creation.

Detection

  • Audit resources that reference S3 buckets – not just the buckets themselves. EMR cluster configurations, SageMaker job definitions, Athena workgroup settings, and other service resources all contain S3 URIs. Cross-referencing these against your bucket inventory can reveal cases where your workloads are pointed at buckets you don’t own.
  • This is a detection gap. No existing AWS-native tooling flags this pattern, and traditional bucket auditing misses the problem entirely – the issue isn’t a misconfigured bucket in your account, it’s a correctly configured resource pointing to a bucket in someone else’s account.

For SDK and Service Developers

  • Adopt account-namespaced buckets for all default bucket creation. The platform-level fix exists – SDKs and services need to actually use it.
  • Never treat bucket existence as proof of ownership. HeadBucket succeeding does not mean you own the bucket.
  • Use ExpectedBucketOwner on all S3 operations, not just the creation path.
  • Do not suppress or swallow BucketAlreadyExists errors – they are security signals.
  • Add entropy to default bucket names. A UUID suffix eliminates predictability.

Conclusion

Bucket squatting was supposed to be done. Disclosed at Black Hat/DEF CON in 2024, patched by AWS. Then account-namespaced buckets in March 2026 – the platform-level fix. Multiple articles declared it dead.

We checked. The fixes were half-finished, inconsistent, and in one case logically broken. The same three primitives – shared namespace, predictable name, no ownership check – survived every layer of remediation. Not because fixes don’t exist, but because no one checked whether they actually work.

The vulnerability class is alive because everyone stopped looking.

This research is ongoing. The same patterns extend beyond these three services into other AWS services, open-source projects, and customer-built applications that mirror the {prefix}-{account_id}-{region} convention from official documentation. When a vendor says a vulnerability class is fixed, verify it.


In This Series

  1. Training on Enemy Ground: Cross-Tenant RCE in Amazon SageMaker 
  2. Athena’s Trojan Horse: Cross-Tenant Data Poisoning and Exfiltration via AWS Athena
  3. Spark in the Dark: Silent Cross-Tenant Log Exfiltration in Amazon EMR
  4. Kick the Bucket: S3 Bucket Squatting Through a Series of Half-Patched AWS Patterns (this post)

Demo the Upwind Platform

See how Upwind is reducing risk and strengthening cloud security for global enterprises.
Get a Demo
Contents
Add the Upwind RSS Feed to Slack
Connect the Upwind RSS Feed to your Slack.
Follow the how-to here.
Threat RSS
Add the Upwind RSS Feed to Slack
Connect the Upwind RSS Feed to your Slack.
Follow the how-to here.
Main RSS