Security basics
An AWS access key exposed in your frontend is not like an OpenAI key
An AWS access key exposed in your frontend reads and writes your storage. The fix is usually a presigned URL rather than a new key.

In short
- An AWS access key exposed in your frontend is a different problem from a key that can only spend money. What it reaches is whatever its permissions allow.
- Two strings sign an AWS request: an identifier starting `AKIA`, and a secret half. One without the other signs nothing.
- A new key pasted into the same page is the same finding with a different value. The fix is a presigned URL, so the browser holds no credential at all.
- We found one in 9 of the 31,056 scanned apps where that check completed.
Someone opens your app in a browser, presses F12, and finds a string beginning
AKIA sitting in your frontend code. Or GitHub emails you to say a commit you
pushed last March carries an AWS credential. The advice that arrives with
either one is always the same: rotate the key. For most keys that is nearly the
whole answer.
Here is the part that guide after guide gets wrong. An AWS access key exposed in your frontend is not a key that spends money. It is a key that opens things, and the two go wrong in different directions. A key that only spends is a company card: somebody runs up a bill, you cancel the card, and the damage is a number you can read on a statement. A key that opens things is the set of keys to the depot. What it reaches depends on what was written on it when it was cut, which is something you can read off one screen in your AWS console.
What does AKIA at the start of a key mean?
It marks a long-term access key, belonging either to a user in your account or
to the account's root login. AWS writes the prefix down in its own identifier
reference: AKIA for access keys, and ASIA for the temporary ones that its
token service hands out. So the first four characters tell you which kind you
are looking at, and AKIA is the kind with no expiry on it. It works until
somebody switches it off.
An AWS credential is two strings, and a request needs both to be signed. The
first is the access key ID, which is the part starting AKIA. The second is
the secret access key, a longer string with no prefix on the front of it. The
ID alone signs nothing.
That is worth checking before you decide how bad a morning this is, because it
changes the answer. If your bundle carries the AKIA string and nothing
resembling its secret half, a visitor reading your page got the name on the key
and not the key. In practice the two usually travel together, because whatever
put one in the browser put both: a pair of environment variables named
AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY, read by code that was supposed
to run on a server.
What can someone do with my AWS access key?
Whatever its permissions allow, and nothing beyond them. So there are three answers.
What can it read? Every S3 bucket the policy lists, and every object in them, including the ones you have never linked to from anywhere. A bucket that nothing in your app mentions is not hidden from a credential that can list it.
What can it write? The same places, which is the half people forget. A key that can write can also overwrite and delete, and a file replaced in place looks exactly like the file you uploaded until somebody opens it.
What can it start? This is the one with a meter on it. A policy wide enough to launch compute launches compute, and that bills by the hour for as long as it runs. Nothing about the browser's copy of the key limits how much of your account it reaches; the policy is the only limit there is.
The worst version of this is a key cut for the account's root login. AWS's own
guidance on the root user says it has unrestricted access to all AWS services,
resources and billing information, and tells you not to create access keys for
it at all. If the key in your page starts AKIA and belongs to the root user,
the thing exposed is the whole account and not one bucket.
So do not guess at any of this. Open IAM, find the user the key belongs to, and read the policies attached to it. That is one screen, and it answers all three questions above.
Do I just rotate it?
Deactivate the exposed key today, and do not put a replacement in the same place.
Deactivating is the right first move and it is gentler than it sounds. AWS lets a user hold up to two active access keys at once, which exists precisely so a replacement can be brought up before the old one goes down. Deactivating leaves the key in place and switched off, and it can be switched back on; deleting is irreversible, and the secret half of a deleted key cannot be recovered. So deactivate, watch what breaks, and delete afterwards.
What does not finish this is creating a new key and pasting it where the old one
was. The leak was never really about that particular value. It was about a
browser holding a credential with no expiry, and everything your page
downloads, every visitor downloads. A fresh AKIA string in the same bundle
comes back as the same finding on the next scan.
AWS puts this plainly in its security audit guide, writing about mobile applications: they should never contain embedded access keys, even if encrypted. A browser bundle is read more easily than an app binary, so the same answer holds here and costs a stranger less work to act on.
If you have already found the key in a public place, the order you work in matters, and what to do when an API key leaks has that order laid out for keys of every kind.
What a presigned URL does instead
It lets the browser do one thing to one file without holding a credential at all.
Your own server keeps the access key, where a visitor cannot read it. When your page needs to put a file into S3 or fetch one out, it asks your server, and your server signs a URL for that one object and that one action and hands it back. The browser uses the URL and gets nothing else with it. AWS describes a presigned URL as granting temporary access to S3 objects without requiring AWS credentials, and the same mechanism covers downloads and uploads.
Back to the depot. A presigned URL is meeting the courier at the door with the one box they came for, instead of handing over the keys and trusting them to find it. What the courier walks away with stops working when its clock runs out.
For the case where each signed-in person reaches their own files, AWS points
somewhere else again: its guidance on securing access keys says mobile
applications should use Amazon Cognito to manage identities rather than
embedding access keys. What Cognito hands the browser is a temporary
credential, the ASIA kind from the top of this article, which stops working
by itself after a while. So a copy of it scraped out of your page on Tuesday is
already dead by Wednesday, and that is the one property an AKIA key does not
have.
How do I check whether my app ships an AWS key?
Search what the browser downloaded, not what is in your project. Those are two different files, and the one that matters is the one your visitors receive.
In the browser. Open your published site, press F12, and search the loaded
JavaScript for AKIA. Chrome and Edge search every loaded file at once from
the Sources panel.
In your project. Search for AKIA, AWS_ACCESS_KEY_ID and
AWS_SECRET_ACCESS_KEY. Then search for the two prefixes that put a value into
a bundle on purpose, VITE_ and NEXT_PUBLIC_, and read every value behind
them.
Why those two prefixes publish whatever they name
is the mechanic behind most of these, and it is a rule about the build that
nobody typed by mistake.
From outside. Our free scan reads your published site the way a stranger would and reports which credentials it can see in what you shipped, this one included. It takes about 20 seconds and needs no account: scan your app free.
Across every app we have scanned, this is a rare finding: an AWS access key turned up in 9 of the 31,056 apps where that check completed. The keys that come up far more often are Google's, which are usually fine, and OpenAI's, which are usually not. What actually leaks out of apps built this way sets all of them side by side with the counts.
What the key did while it was out there
Open CloudTrail event history and filter it on the access key ID.
Event history is on in every AWS account with nothing to switch on, it holds the last 90 days, and it costs nothing extra. Every entry carries the access key ID that made the call, so you can read back what your key did. That answers the questions about configuration: a bucket created, a policy changed, a user added.
Then there is the part worth knowing before you go looking. Event history holds management events. Reading an object out of S3 is a data event, and AWS is explicit that trails do not log those by default and that data events do not appear in event history at all. Switching them on records from that moment forward.
So the honest reading is this. If the key was used to change your account, the log will show you. If it was used to download the contents of a bucket, the log will probably not, and you are deciding what to tell your customers on the basis that it may have been.
Keeping a key out of the next build
A credential leaves for the browser at build time, which means the next deploy is the one to watch. The key you take out today goes back in the afternoon somebody adds a feature that needs S3 and asks an agent to wire it up.
Reeve Monitor reads your published site again for you:
- all nine checks every hour, on up to three apps
- a message when a result changes, so a key that arrives on Tuesday does not wait until you next look
- whether the app is up, every 60 seconds
- a monthly report of what it saw
If your app keeps its data in a Supabase project, Reeve Care keeps a copy of that too:
- an encrypted copy of your Supabase database every night, kept where your project cannot reach it
- each copy verified before it counts, by counting the rows in every table
- a one-click restore when you need one
- your uploaded files as well, once you connect a Storage credential
- everything Monitor does
Every one of those nine checks describes what a stranger can reach. A copy of last night's database is what answers the other question, which is what somebody already altered while the key was out.
What to do this week
What to do
- Open your published site, press F12, and search the loaded JavaScript for
AKIA. That is the file your visitors have, and it is the only one that counts. - If you find one, deactivate it in IAM today. Deactivating is reversible and takes effect at once; deleting is not, and the secret half cannot be recovered.
- Read the policies attached to the key's user before you decide how serious this is. What it can read, what it can write and what it can start are three separate answers.
- Replace the pattern rather than the value. Your server holds the key and signs a presigned URL for one object, or Amazon Cognito hands the browser a temporary credential that expires on its own.
- Filter CloudTrail event history by the key for the last 90 days. It will show you what the key configured, and it will not show you which files it read unless data events were already switched on.
Start with the F12 search, because it settles in a minute whether you have a problem at all. If it turns something up, the 10-minute security checklist covers the rest of what a newly launched app tends to leave open, and moving a secret to an edge function is the pattern for every key that should never have been in the page.
FAQ
Is it ever safe to put AWS keys in frontend code?
No. AWS says so in its own security audit guide, about mobile apps: applications should never contain embedded access keys, even if encrypted. A browser bundle is easier to read than an app binary, so the same answer holds here and costs a stranger less work to act on. Where your page genuinely needs to reach S3, the supported routes are a presigned URL signed by your own server, or temporary credentials from Amazon Cognito, which expire by themselves.
What can someone do with my AWS access key?
Exactly what the permissions behind it allow, which is why there is no single answer. A key attached to a user with read access to one bucket reads that bucket. A key attached to an administrator does everything an administrator does, including starting machines that bill by the hour. A key belonging to the account root user is the worst case, because AWS states the root user has unrestricted access to all services, resources and billing information. Open IAM and read the policies attached to the key before deciding how worried to be.
What is a presigned URL?
A long URL that lets a browser do one thing to one file in S3, for a set number of seconds, without holding any AWS credential. Your own server holds the key and signs the URL on request. AWS describes presigned URLs as granting temporary access to S3 objects without requiring AWS credentials, and they work for downloads and uploads alike.
How do I check whether my app ships an AWS key?
Search what your browser downloaded rather than what is in your project. Open your published site, press F12, and search the loaded JavaScript for AKIA. Then search your project for AKIA, AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY, and for any variable name beginning VITE_ or NEXT_PUBLIC_ that holds one, because those prefixes are what put a value into the bundle in the first place.
My AWS key is on GitHub. What now?
Deactivate it in IAM first, which is reversible and takes effect immediately, then create a replacement for the server that needed it. Removing the key from your code does not close anything, because the old value stays in your commit history and in every clone anyone has made. After that, open CloudTrail event history and filter on the key to see what it did.