JWT Detector
The jwt detector identifies JWT and JWE tokens in scanned content.
| Type | Finding ID | Format |
|---|---|---|
| JWT | jwt-jwt-token | header.payload.signature (3 parts) |
| JWE | jwt-jwe-token | header.enc_key.iv.ciphertext.tag (5 parts) |
All findings have Critical severity.
ey[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+
JWTs have three base64url-encoded parts:
- Header - Algorithm and token type
- Payload - Claims (user data, expiration, etc.)
- Signature - Cryptographic signature
The header always starts with ey because it’s a JSON object ({" encodes to eyJ).
ey[A-Za-z0-9-_]+(?:\.[A-Za-z0-9-_]+){4}
JWEs have five parts:
- Header - Encryption algorithm info
- Encrypted Key - Content encryption key
- Initialization Vector - Random IV
- Ciphertext - Encrypted payload
- Authentication Tag - Integrity check
{
"id": "jwt-jwt-token",
"probe": "shell_history",
"severity": "critical",
"title": "JWT Token Detected (JWT Token)",
"message": "A JWT Token was detected in file:/Users/dev/.zsh_history. JWT tokens in plain text may be exposed in logs...",
"path": "file:/Users/dev/.zsh_history",
"metadata": {
"detector_name": "jwt",
"token_type": "jwt-token",
"fingerprint": "sha256:..."
}
}
JWTs are commonly used as session tokens. A stolen JWT allows an attacker to:
- Impersonate the user
- Access protected resources
- Perform actions as the user until expiration
Many JWTs have long expiration times (hours, days, or longer), extending the attack window.
JWTs contain claims that servers trust without database lookup, making revocation difficult.
You can decode a JWT to understand what it contains (payload is not encrypted in standard JWTs):
# Using command line
echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U" | \
cut -d. -f2 | base64 -d 2>/dev/null
# Output: {"sub":"1234567890"}
Or use jwt.io (but never paste production tokens!).
Check the token’s expiration:
# Decode and check 'exp' claim
echo "$JWT" | cut -d. -f2 | base64 -d 2>/dev/null | jq .exp
# Convert timestamp: date -d @1234567890
If expired, the immediate risk is lower (but token reuse attacks may still apply).
For valid tokens, invalidate server-side:
- Log out the session
- Add to a token blacklist
- Rotate signing keys (invalidates all tokens)
Remove the token from:
- Shell history
- Environment variables
- Log files
- Configuration files
After cleanup, re-authenticate to get a fresh token.
Short expiration times:
exp: now + 15 minutes (access tokens) exp: now + 7 days (refresh tokens)Use refresh tokens:
- Short-lived access tokens
- Longer refresh tokens stored securely
- Refresh endpoint rotates both
Don’t log JWTs:
# BAD logger.info(f"Request with token: {request.headers['Authorization']}") # GOOD logger.info("Authenticated request received")Store in memory, not localStorage:
// BAD - accessible to XSS localStorage.setItem('token', jwt) // GOOD - httpOnly cookie or in-memoryUse token binding:
- Bind tokens to client fingerprints
- Validate binding on each request
Implement token revocation:
- Maintain blacklist for logged-out tokens
- Or use short expiration with refresh
| Feature | JWT | JWE |
|---|---|---|
| Payload | Readable (base64) | Encrypted |
| Use case | Authentication, claims | Sensitive data transfer |
| Parts | 3 | 5 |
JWE provides confidentiality but is less common. Both should be treated as sensitive.
- HTTP Auth Detector - Detects Bearer tokens
- Shell History Probe - Common JWT exposure vector