MediumThe 404 That Leaked a Live JWT and Sentry Keys | by Advait Rane | Medium
Sitemap
Advait Rane

Advait Rane

hack everything!

The 404 That Leaked a Live JWT and Sentry Keys

3 min readApr 27, 2026

--

Press enter or click to view image in full size

I requested a URL that didn’t exist. The server handed me an access token, a refresh token, and internal monitoring credentials. Bug accepted.

Bug Bounty · JWT · Keycloak · Sentry DSN · Information Disclosure

Severity Type Outcome High Token + Secret Exposure in Error Response Accepted — Duplicate

What Happened

While testing an authorized target, I did something simple — I requested a path I knew didn’t exist:

GET /api/v2/non-existent-security-test HTTP/1.1
Host: [redacted]

Expected: a boring 404 Not Found.

What I actually got:

HTTP/1.1 404 Not Found
Set-Cookie: keycloak_token={"AccessToken":"eyJhbGci...","RefreshToken":"eyJhbGci..."}; Path=/; HttpOnly
Content-Security-Policy: default-src 'self'; report-uri https://sentry.io/api/[PROJECT_ID]/security/?sentry_key=[EXPOSED_KEY]
Reporting-Endpoints: default="https://[KEY]@sentry.io/[PROJECT_ID]"

Two critical issues in one response. No authentication required.

Finding 1 — Live JWT in a 404 Cookie

The server’s error-handling middleware was minting and issuing a full Keycloak session on every 404 — access token and refresh token — to completely unauthenticated requests.

The access token decoded to real claims: valid sub, realm roles, live expiry. The refresh token made it persistent — an attacker could silently rotate tokens indefinitely without the user ever knowing.

Why this is serious beyond the obvious:

Headers don’t stay between client and server. They travel through CDN logs (Cloudflare logs response headers by default), corporate proxies doing SSL inspection, and browser extensions with webRequest permission. A token in a Set-Cookie header is a token that's already been seen by every intermediary in the chain.

Get Advait Rane’s stories in your inbox

Join Medium for free to get updates from this writer.

The attack is three steps:

1. GET /api/v2/anything-fake
← Set-Cookie: keycloak_token={AccessToken, RefreshToken}
2. POST /auth/realms/[realm]/protocol/openid-connect/token
grant_type=refresh_token&refresh_token=eyJhbGci...
← fresh access_token, rotated refresh_token
3. GET /api/v1/profile
Authorization: Bearer <new_access_token>
← full authenticated access

This is a textbook Principle of Least Privilege violation — the error handler had no business touching authentication state at all.

Finding 2 — Sentry DSN Key in Response Headers

The Content-Security-Policy and Reporting-Endpoints headers both contained a live Sentry DSN key. I confirmed it in thirty seconds:

curl -X POST "https://sentry.io/api/[PROJECT_ID]/store/" \
-H "X-Sentry-Auth: Sentry sentry_version=7, sentry_key=[KEY]" \
-H "Content-Type: application/json" \
-d '{"message": "test", "level": "info"}'
→ {"id": "a1b2c3d4..."}

Event accepted. The key was live.

With a valid DSN an attacker can flood the project with fake errors to drown out real alerts, exhaust the event quota to create a monitoring blackout, or probe the internal error taxonomy for reconnaissance. A Sentry DSN is a credential — it should never appear in a public response header.

Root Cause

The session initialization middleware was registered globally, so it ran on every request — including the 404 catch-all. It saw no existing token, minted one, set the cookie, and passed control forward. The 404 handler fired after, with the credential already in the response.

// The problem — middleware runs before the 404 handler
app.use(keycloakSessionMiddleware); // ← sets cookie on every request
app.use((req, res) => res.status(404).json({ error: 'Not Found' }));

Middleware ordering is a security property. Scoping it only to protected routes fixes the issue entirely.

The Fix

// Scope auth middleware to protected routes only
const apiRouter = express.Router();
apiRouter.use(keycloakSessionMiddleware);
apiRouter.get('/users', handler);
app.use('/api/v2', apiRouter);
// 404 handler — no auth middleware ever reaches here
app.use((req, res) => res.status(404).json({ error: 'Not Found' }));

For the Sentry DSN — proxy CSP reports through your own backend, keep the DSN server-side only, and rotate any key that has been exposed in a public header.

The Duplicate

Triage confirmed it valid. Someone had already reported it.

That stung for a moment. Then I reframed it: two independent researchers found the same high-severity bug using the same methodology. My instincts were right — I was just second.

This was my first confirmed valid bug. Since then, three more informative findings. The habit that got me here — actually reading error responses instead of skimming past them — is the same habit driving everything after it.

Error responses are attack surface. Read them like it.

Conducted under an authorized bug bounty program. All sensitive values redacted.

Tags: bug-bounty · jwt · keycloak · sentry · appsec · web-security · ethical-hacking

--

--