NEXT_PUBLIC_ leaked your aws access key id into the App Router bundle
Identifies an IAM principal. Paired with its secret, it grants that principal’s permissions.
Why Next.js does this
A server component can read `process.env.STRIPE_SECRET_KEY` safely. Move that same line into a client component and the build fails to find it, so the quickest fix — rename it with `NEXT_PUBLIC_` — is also the one that inlines it into the JavaScript every visitor downloads.
Confirm it first
Before rotating anything, check whether the key is actually being served. Paste your deployed URL — KeyDrift downloads the same JavaScript a visitor gets and tells you what is in it.
Rotate the key
Do this before changing any code. The key has been served to browsers, cached by CDNs and very likely scraped already — removing it from the source does not un-publish it.
- 1Deactivate the access key in IAM, then delete it once nothing is broken.
- 2Read CloudTrail for the period the key was public. This is the one provider where you can find out exactly what was done.
- 3Replace long-lived keys with a role, and issue presigned URLs from a server route instead of shipping credentials.
Move the call to a server
The replacement key must not follow the old one into the bundle, which means the code that uses it cannot live in the browser. Any variable named NEXT_PUBLIC_… is inlined at build time by design — the prefix is the mechanism, not a mistake.
Before — shipped to the browser
// app/components/Chat.tsx ("use client")
// NEXT_PUBLIC_ inlines this into the browser bundle.
const key = process.env.NEXT_PUBLIC_API_KEY;
const result = await callTheApi(key, body);After — stays on a route handler or server action
// app/api/proxy/route.ts — runs on the server only
import 'server-only';
export async function POST(request: Request) {
const key = process.env.API_KEY; // no NEXT_PUBLIC_ prefix
const result = await callTheApi(key, body);
return Response.json(result);
}
// app/components/Chat.tsx ("use client")
const result = await fetch('/api/proxy', { method: 'POST' }).then((r) => r.json());How KeyDrift detects it
Matches the `AKIA` and `ASIA` prefixes. `ASIA` is a temporary STS credential and expires on its own, so it is reported one level lower. The documented example key from the AWS guides is excluded by name.
It will happen again
A server component can read `process. That has not changed because you fixed this one file — the next feature request produces the same shape of code. Continuous monitoring re-scans every deploy and tells you the moment a key comes back.