notra·
2026-08-27·4 min read·The Notra team

Forging Admin: When a Leaked Secret Becomes a Root Login

A token is only as strong as its secret

A JWT is three base64 chunks: header, payload, signature. The signature is the whole security model. It proves the payload was signed by someone who holds the secret. So if that secret leaks, the model inverts. Now anyone can sign anything, and your server will believe it, because believing a valid signature is its entire job.

This is the step that turns a leak into a login. It is worth understanding exactly how short the path is.

Three ways the secret gets out

  1. In the bundle. A NEXT_PUBLIC_ or VITE_ prefix ships the variable straight to the browser. People do this to signing secrets more often than you would think. Grep your built JavaScript for it before an attacker does.
  2. Weak secret. HS256 with a secret like secret, changeme, or your company name is brute-forced offline in seconds. There is no network noise for you to notice.
  3. Algorithm confusion. Your tokens use RS256 (a public/private key pair), but a lax verifier also accepts HS256. The attacker signs a token with HS256 using your public key, which is public, as the HMAC secret. A verifier told to "just verify" accepts it.

Where do secrets like this leak from in the first place? Usually the file in The .env File That Ends Companies.

From secret to admin, in one decode

Take a normal session token and look at the payload:

{ "sub": "user_77", "role": "user", "iat": 1756300000, "exp": 1756386400 }

Change one field, re-sign with the leaked secret, and you are done:

import jwt  # PyJWT
 
payload = {"sub": "user_77", "role": "admin", "iat": 1756300000, "exp": 1756386400}
forged = jwt.encode(payload, "leaked-secret-here", algorithm="HS256")
print(forged)
Ten lines of Python between a leaked secret and an admin session.

The server checks the signature, finds it valid because it was signed with the real secret, and reads role: admin. You are now an administrator without ever knowing an admin password, resetting one, or triggering a login alert.

Warning

Every session token you have ever issued shares one secret. When that secret leaks, you are not exposed to one forged login, you are exposed to unlimited forged logins as any user, any role, until you rotate. Rotation is the only real remedy.

How to tell if you are exposed

You do not need to wait to be attacked to find out. Three quick checks cover most of the risk:

  • Grep your shipped bundle. From your built front-end, search for the secret and for tell-tale prefixes. If anything comes back, the secret is already public and rotation is not optional.
# Run against your production build output, not your source
grep -rIE 'JWT_SECRET|SUPABASE_JWT|NEXT_PUBLIC_.*SECRET|VITE_.*SECRET' dist/ .next/
  • Decode a real token. Paste one of your own session tokens into any offline base64 decoder and read the header. If alg is HS256 and your backend also issues RS256 elsewhere, you have the ingredients for algorithm confusion.
  • Test the weak-secret case. If the header says HS256, a wordlist run against the token finishes in seconds on a laptop. If a common word cracks it, so will an attacker's.

None of these touch another user's data. They tell you whether the front door is already unlocked.

Proving it without doing damage

You do not break anything to prove this. You forge a token with elevated claims and hit a read-only privileged endpoint:

GET /api/admin/me HTTP/1.1
Host: app.yourco.com
Authorization: Bearer eyJhbGci...forged-admin-token

A 200 that returns an admin context in the body is the evidence: request in, response out, nothing mutated. That saved pair is the difference between "theoretically exploitable" and "here is the proof." Once you can forge a session, the natural next target is data an ordinary user should never touch, which is exactly the Supabase RLS Gaps an attacker chains to next.

Shutting it down

FailureWhat the attacker doesFix
Secret in the bundleReads it from your JSNever prefix a secret as public
Weak HS256 secretCracks it offline32+ random bytes from a CSPRNG
Algorithm confusionSigns with your public keyPin an algorithms allowlist

Concretely:

  • Rotate the secret now if there is any chance it leaked. Rotation invalidates every forged token along with the real ones.
  • Use a long, random secret, never a word. Thirty-two bytes from a real random source, minimum.
  • Pin the algorithm. Tell your verifier algorithms=["RS256"] explicitly and reject everything else. This kills both algorithm confusion and the alg: none trick in one line.
  • Keep signing secrets server-side. If it can reach a browser, it is not a secret.

Notra forges a token against your own endpoints, non-destructively, and shows you the exact request and response if one gets in. No guessing, no "possible." Run a free scorecard, or read more of our field notes on the blog.

ShareX LinkedIn

Related

Want this run against your site?