skills/jwt-misuse-anti-pattern/SKILL.md
Security anti-pattern for JWT misuse vulnerabilities (CWE-287). Use when generating or reviewing code that creates, validates, or uses JSON Web Tokens. Detects 'none' algorithm attacks, weak secrets, sensitive data in payloads, and missing expiration.
npx skillsauth add igbuend/grimbard jwt-misuse-anti-patternInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Severity: High
JSON Web Tokens (JWTs) are frequently misused in AI-generated code, creating critical vulnerabilities. Common flaws include accepting the "none" algorithm, weak secrets, sensitive data in payloads, and missing expiration. These enable authentication bypass, token forgery, and sensitive data exposure.
Critical vulnerability where library accepts any algorithm in token header. Attacker changes algorithm to "none" and removes signature, bypassing all cryptographic validation.
# VULNERABLE: Accepts whatever algorithm is in the header
import jwt
def verify_jwt_vulnerable(token, secret_key):
# If the token's header is {"alg": "none"}, the library may bypass signature verification entirely.
try:
decoded = jwt.decode(token, secret_key, algorithms=None) # algorithms=None or not specified
return decoded
except jwt.PyJWTError as e:
print(f"JWT verification failed: {e}")
return None
# SECURE: Explicitly specify allowed algorithms
import jwt
def verify_jwt_secure(token, secret_key):
# CRITICAL: Always specify exact algorithm(s) expected
# Library rejects tokens not using specified algorithms
try:
decoded = jwt.decode(token, secret_key, algorithms=["HS256", "RS256"])
return decoded
except jwt.PyJWTError as e:
print(f"JWT verification failed: {e}")
return None
Weak, predictable, or hardcoded secrets for symmetric signing algorithms (HS256) enable attackers to brute-force secrets and forge valid tokens.
# VULNERABLE: Weak or short secret key
import jwt
JWT_SECRET = "secret123" # Easily brute-forced!
def create_jwt(user_id):
payload = {"user_id": user_id}
return jwt.encode(payload, JWT_SECRET, algorithm="HS256")
# SECURE: Strong, centrally managed secret
import jwt
import os
# Load strong, randomly generated secret from environment or secret manager
JWT_SECRET = os.environ.get("JWT_SECRET")
def initialize():
if not JWT_SECRET or len(JWT_SECRET) < 32:
raise ValueError("JWT_SECRET must be at least 256 bits (32 chars) for HS256")
# For production, use asymmetric keys (RS256): private key kept secret,
# public key safely distributed for verification
def create_jwt_asymmetric(user_id, private_key):
payload = {"sub": user_id}
return jwt.encode(payload, private_key, algorithm="RS256")
JWT payload is Base64Url-encoded, not encrypted. Anyone intercepting the token can decode and read it. Storing PII, passwords, or internal data in payload creates major security risk.
# VULNERABLE: Sensitive data in JWT payload
import jwt
def create_jwt_with_pii(user, secret_key):
payload = {
"user_id": user.id,
"email": user.email,
"ssn": user.social_security_number, # PII EXPOSED!
"password_hash": user.password_hash # CRITICAL RISK!
}
return jwt.encode(payload, secret_key, algorithm="HS256")
# SECURE: Minimal, non-sensitive claims
import jwt
import time
def create_jwt_secure(user, secret_key):
payload = {
"sub": user.id, # Subject (user ID) - standard, non-sensitive
"iat": int(time.time()), # Issued at - standard
"exp": int(time.time()) + 3600, # Expiration (1 hour) - standard
"role": user.role # Non-sensitive custom claim
}
# Never include passwords, PII, payment info, or internal data
# Server fetches data from secure database using user ID from token
return jwt.encode(payload, secret_key, algorithm="HS256")
JavaScript/Node.js:
// VULNERABLE: Multiple JWT misuse patterns
const jwt = require('jsonwebtoken');
// Weak secret
const SECRET = 'mysecret';
// No algorithm specified - accepts "none"!
function verifyToken(token) {
return jwt.verify(token, SECRET); // CRITICAL FLAW
}
// Sensitive data in payload, no expiration
function createToken(user) {
return jwt.sign({
id: user.id,
email: user.email,
password: user.passwordHash, // EXPOSED!
ssn: user.ssn // EXPOSED!
// No exp claim!
}, SECRET);
}
// SECURE: Proper JWT implementation
const jwt = require('jsonwebtoken');
const SECRET = process.env.JWT_SECRET; // Strong, env-based secret
if (!SECRET || SECRET.length < 32) {
throw new Error('JWT_SECRET must be at least 256 bits');
}
function verifyToken(token) {
// CRITICAL: Explicitly specify allowed algorithms
return jwt.verify(token, SECRET, {
algorithms: ['HS256'],
maxAge: '1h' // Also enforce expiration
});
}
function createToken(user) {
const now = Math.floor(Date.now() / 1000);
return jwt.sign({
sub: user.id, // Standard claim
iat: now, // Issued at
exp: now + 3600, // Expires in 1 hour
role: user.role // Non-sensitive only
}, SECRET, {
algorithm: 'HS256'
});
}
Java:
// VULNERABLE: Weak secret and no algorithm enforcement
import io.jsonwebtoken.*;
public class JwtService {
private static final String SECRET = "secret123"; // Weak!
public Claims verifyToken(String token) {
// No algorithm specified - vulnerable to none attack
return Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody();
}
public String createToken(User user) {
// No expiration, sensitive data included
return Jwts.builder()
.setSubject(user.getId())
.claim("email", user.getEmail())
.claim("ssn", user.getSsn()) // EXPOSED!
.signWith(SignatureAlgorithm.HS256, SECRET)
.compact();
}
}
// SECURE: Proper JWT implementation
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
import java.util.Date;
public class SecureJwtService {
// Load from environment or secret manager
private static final String SECRET_KEY = System.getenv("JWT_SECRET");
private static final Key KEY = Keys.hmacShaKeyFor(SECRET_KEY.getBytes());
public Claims verifyToken(String token) {
// Explicitly require HS256 algorithm
return Jwts.parserBuilder()
.setSigningKey(KEY)
.requireAlgorithm("HS256")
.build()
.parseClaimsJws(token)
.getBody();
}
public String createToken(User user) {
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
Date expiry = new Date(nowMillis + 3600000); // 1 hour
return Jwts.builder()
.setSubject(user.getId())
.setIssuedAt(now)
.setExpiration(expiry) // REQUIRED
.claim("role", user.getRole()) // Non-sensitive only
.signWith(KEY, SignatureAlgorithm.HS256)
.compact();
}
}
rg 'jwt\.decode.*algorithms\s*=\s*(None|null|\[\])'rg 'jwt\.decode' | rg -v 'algorithms=' (missing explicit algorithm)rg 'verify.*false|verify:\s*false' --type js (disabled verification)rg 'JWT_SECRET.*=.*["\'][^"\']{1,16}["\']' (short secrets < 32 chars)rg 'secret.*password|password.*jwt' -irg 'jwt\.encode|jwt\.sign' -A 5ssn, password, credit_card, email, phonerg 'jwt\.encode' -A 5 | rg -v 'exp|expir'exp claim: Reasonably short lifetime for access tokensjti)development
Security anti-pattern for Cross-Site Scripting vulnerabilities (CWE-79). Use when generating or reviewing code that renders HTML, handles user input in web pages, uses innerHTML/document.write, or builds dynamic web content. Covers Reflected, Stored, and DOM-based XSS. AI code has 86% XSS failure rate.
development
Security anti-pattern for XPath injection vulnerabilities (CWE-643). Use when generating or reviewing code that queries XML documents, constructs XPath expressions, or handles user input in XML operations. Detects unescaped quotes and special characters in XPath queries.
development
Security anti-pattern for weak password hashing (CWE-327, CWE-759). Use when generating or reviewing code that stores or verifies user passwords. Detects use of MD5, SHA1, SHA256 without salt, or missing password hashing entirely. Recommends bcrypt, Argon2, or scrypt.
development
Security anti-pattern for weak encryption (CWE-326, CWE-327). Use when generating or reviewing code that encrypts data, handles encryption keys, or uses cryptographic modes. Detects DES, ECB mode, static IVs, and custom crypto implementations.