A few hours after Windsurf finishes scaffolding an S3 uploader, your AWS access key and secret can be sitting inside the JavaScript bundle of an Android build that is on its way to internal testing. The autocomplete put the keys where they needed to be to make the test pass. The bundler kept them there. Now they ship.
Short answer
Windsurf will happily write AKIA... access keys, secret keys, and full credential objects directly into source files, config helpers, and .env files when you ask it to "make S3 work" or "connect to AWS". Those values are not stripped at build time. They land in your APK, AAB, or IPA bundle as plain strings. The real fix is to remove long-term AWS credentials from the client entirely. Use a backend endpoint or Amazon Cognito identity pools with temporary AWS STS credentials.
What you should know
- Windsurf treats keys as configuration, not as secrets. When Cascade reads code that already contains a key, it suggests new code in the same style.
- JavaScript bundles in React Native and Expo apps are easy to read. An attacker pulls
index.android.bundlefrom any installed APK and greps forAKIA. - Google Play flags leaked AWS credentials. The Play Console notice reads "Leaked AWS Credentials: Your app(s) expose Amazon Web Services credentials."
- OWASP tracks this as MASWE-0005. Hardcoded API keys in the app package are a documented mobile security weakness under MASVS-AUTH-1.
- Long-term IAM user keys should never reach a phone. AWS recommends temporary credentials issued by STS or a Cognito identity pool for any client-side use.
- Removing the key from a later release does not rotate it. The leaked key is valid until you rotate it in IAM, no matter how clean the next build looks.
Why does Windsurf put AWS keys directly in the code?
The short answer is that the AI assistant follows the path of least friction. When you tell Cascade to "wire up S3 uploads for profile pictures", it needs an access key and a secret key to make the SDK initialize. It either reads them from your local .env, asks you to paste them into chat, or writes a placeholder constant that you fill in. Once the keys exist in a file the bundler can see, they become part of your app.
In practice, three patterns show up the most:
- A
config.tsoraws-config.jsfile that exports the access key and secret as constants. - A
.envfile read byreact-native-config, Expo'sextrafield, orimport.meta.env, and then inlined into the JavaScript bundle at build time. - An inline
new AWS.S3({ accessKeyId: "AKIA...", secretAccessKey: "..." })call inside a service module, because the chat asked for "a working snippet".
The pattern that catches most builders by surprise is the second one. .env feels private because it lives on your machine and is .gitignored. Once the bundler runs, the values are no longer in .env. They are inside the compiled bundle, as plain strings.
What happens when an AWS key ships in your app bundle?
The honest answer is that the key is now public. Every user who downloads your build has it. Every researcher who pulls your APK off a third-party mirror has it. Every automated scanner that crawls public app distribution has it. The clock is short.
The most cited incident pattern in mobile security writing is a dating app whose React Native bundle exposed AWS credentials. Coverage of the case describes an attacker pulling user photos, database backups, and personal information for tens of thousands of users from the S3 bucket those keys had access to. The financial cost reported runs into the millions. The technical mechanism is the part worth remembering: the keys were in index.android.bundle, and a single strings pass found them.
This matters because the worst version of the leak gives the attacker every permission the IAM user attached to that key has. Uploading garbage to S3 is the lower bound, not the upper bound. If the key is scoped to a development IAM user with AdministratorAccess, the damage scales accordingly.
How do attackers find the keys inside a compiled APK or IPA?
The technique is older than most app stores. For an Android APK, an attacker unzips the file, locates assets/index.android.bundle for a React Native build (or the equivalent Hermes bytecode, convertible back to JavaScript with tools like hermes-dec), and runs a grep for AKIA, ASIA, aws_secret, or the constant name used by the code. For an iOS IPA, the same logic applies to the bundled JavaScript inside the .app directory, plus any plist files.
The HackTricks community guide on React Native applications documents this whole flow with the exact commands. A separate Payatu writeup on reverse engineering React Native goes further and walks through how a Hermes bundle is identified and decompiled. None of this requires specialist tooling.
The point worth internalizing is that minification does not solve the problem. A string constant inside JavaScript is still a string constant after minification, and a strings pass on a native binary still finds embedded base64-shaped secrets.
What does Google Play do when it detects leaked AWS credentials?
Google Play scans uploaded builds and flags leaked AWS credentials with a Play Console policy notice. Developers in a long-running react-native-config issue thread report receiving the exact message: "Leaked AWS Credentials: Your app(s) expose Amazon Web Services credentials." The notice links to Google's security guidance and gives a window to fix the issue. Repeated violations can lead to suspension.
Apple does not publish an equivalent automated message in App Store Connect, but the App Review Guidelines section on data security applies. If a reviewer finds the key, or a third party reports the leak through Apple's security channel, the build can be pulled. The OWASP MAS controls that map to this are MASVS-AUTH-1 (no authentication material embedded in the app), MASVS-CRYPTO (no hardcoded cryptographic keys), and MASWE-0005 specifically on API keys in the package.
The limit worth keeping in mind: neither store guarantees detection. Plenty of apps with hardcoded keys ship without a Console notice ever appearing. The store is not your secret scanner.
What should I tell Windsurf to do instead of hardcoding keys?
The prompt that works is specific. Instead of "wire up S3 uploads", ask Cascade to generate a setup that follows AWS best practice:
| Pattern | What to ask Windsurf to write | Where the credential lives |
|---|---|---|
| Backend proxy | A server endpoint (POST /upload-url) that returns a presigned S3 URL | On the server, never on the client |
| Cognito identity pool | A client that authenticates to Cognito and receives temporary STS credentials with scoped IAM permissions | Returned to the client, expires in minutes |
| Federated identity | A client that exchanges a sign-in token (Google, Apple, custom) for AWS temporary credentials via Cognito | Same as above |
| Local development only | A .env.local that is read at dev time and never committed or bundled | On your development machine |
The AWS IAM best practices documentation recommends temporary credentials over long-term access keys for any client-side use case. Cognito identity pools are the canonical way to do this for mobile, but for many apps a backend with a presigned URL endpoint is simpler and faster to ship.
When you write the prompt to Windsurf, name the pattern: "Use AWS Cognito identity pools to get temporary credentials. Do not embed access keys in the client." Cascade follows the style of nearby code, so the second prompt that matters is "remove every reference to AKIA access keys from this file, including imports and helper functions."
How can I check my build for leaked secrets before submitting?
A leak scan on the compiled artifact is the only reliable answer, because the build is what attackers download. The check has three parts:
- Decode the bundle. For React Native, unzip the APK, locate
assets/index.android.bundle, and either read it directly or run it throughhermes-decif the bundle is in Hermes bytecode. For iOS, extract the IPA, open the.appfolder, and inspect the JavaScript bundle and plist files. - Search for known patterns. AWS access keys start with
AKIA(long-term) orASIA(temporary). Secrets are 40 characters of base64-like text. Also search foraws_access_key_id,aws_secret_access_key, and any constant names from your code. - Verify the finding is dead. If a key is found, rotate it in IAM immediately. Do not wait until the next release. The leaked key stays valid until the rotation completes.
A scanner that runs the same passes automatically saves time when builders ship multiple updates per week. For builders who want an external automated read of an APK, AAB, or IPA before submission, PTKD.com (https://ptkd.com) is one of the platforms focused on pre-submission scanning aligned with OWASP MASVS for AI-coded and no-code apps, including detection of hardcoded credentials in the bundle.
What to watch out for
The most common mistake is fixing the symptom and skipping the cause. A developer sees the key, deletes it from the source file, pushes a new release, and considers the issue closed. The key is still valid in IAM. Anyone who downloaded the previous version still has it. Rotate first, then ship the cleaned build.
A related mistake is treating obfuscation or encryption inside the bundle as a fix. The decryption code has to run on the client, so the decryption key also has to be in the bundle. A reverse engineer with an afternoon will find both. The only real answer is to remove the secret from the client entirely.
The myth to actively reject: "my app is a small project, no one will reverse it." Automated scanners crawl public app stores looking for cloud credentials precisely because small projects are the easiest targets. A leaked AWS key is currency on a credential market, not a vanity issue.
Key takeaways
- Windsurf will write AWS access keys directly into your code unless you tell it not to. Treat the AI as a fast intern who needs an explicit policy: temporary credentials only, no embedded long-term keys.
- The bundle is public. Anything inside an APK, AAB, or IPA should be treated as known to attackers from the moment the build is published.
- Use Cognito identity pools or a backend proxy with presigned URLs. Long-term IAM user keys do not belong on a phone, no matter how convenient the snippet looks.
- If a key has shipped, rotate it in AWS IAM the same hour. Removing it from source does not invalidate the leaked copy.
- Some teams outsource the pre-submission check, and PTKD.com (https://ptkd.com) is one of the platforms that scan compiled APK, AAB, and IPA files for hardcoded credentials and other MASVS-aligned issues before the build reaches the store.



