sast-jwt
Detect insecure JWT (JSON Web Token) implementations in a codebase using a two-phase approach: first map all JWT issuance and verification sites to understand the token lifecycle and signing configuration, then check each verification site for exploitable weaknesses such as algorithm confusion, miss…
Install / Use
npx skills add utkusen/sast-skills --skill sast-jwtInstalls into whichever agent you are using.
SKILL.md
Installable skill definition
Quality Score
Category
SecuritySupported Platforms
Tags
Our assessment of sast-jwt
sast-jwt scores 89/100 on our quality scale, 491st of 971 Security skills we index.
Its SKILL.md is 23 KB long, well organised into 30 sections with 12 code examples: a thorough specification that gives an agent plenty to work with.
With 1,321 GitHub stars, it is one of the more widely adopted skills in the catalogue.
Maintenance, license and trust
- The repository was last updated about 6 months ago. That is recent enough to be usable, but agent tooling moves fast, so check the instructions against your agent's current version.
- It is released under the MIT license, a permissive license that allows use, modification and commercial use with attribution.
- Its trust signals score 98/100, with no cautions. These come from repository metadata, not a code audit — read the skill file before letting an agent act on it.
sast-jwt compared with similar skills
All 4 of these similar skills score higher than sast-jwt; compare them before choosing.
| Skill | Score | Stars | Updated | Format |
|---|---|---|---|---|
| sast-jwt (this skill)by utkusen | 89 | 1.3k | 6mo ago | SKILL.md |
| algorithmic-artby anthropics | 100 | 177.9k | 8d ago | SKILL.md |
| pptxby anthropics | 100 | 177.9k | 8d ago | SKILL.md |
| designby nextlevelbuilder | 100 | 130.2k | 9d ago | SKILL.md |
| ui-ux-pro-maxby nextlevelbuilder | 100 | 130.2k | 9d ago | SKILL.md |
Frequently asked questions
- How do I install sast-jwt?
- Run
npx skills add utkusen/sast-skills --skill sast-jwt. The install tabs above show the steps for each supported agent. - Which AI agents does sast-jwt work with?
- It is written for Universal, as a SKILL.md file. Other agents that read the same format can often use it too.
- Is sast-jwt safe to use?
- It is MIT-licensed and scores 98/100 on trust signals. Skills are instructions an agent will follow, so read the file before installing it and do not approve commands you do not understand.
- Is sast-jwt still maintained?
- The repository was last updated about 6 months ago. That is recent enough to be usable, but agent tooling moves fast, so check the instructions against your agent's current version.
Skill content
View source on GitHubname: sast-jwt description: >- Detect insecure JWT (JSON Web Token) implementations in a codebase using a two-phase approach: first map all JWT issuance and verification sites to understand the token lifecycle and signing configuration, then check each verification site for exploitable weaknesses such as algorithm confusion, missing signature verification, weak secrets, header injection, and missing claim validation. Requires sast/architecture.md (run sast-analysis first). Outputs findings to sast/jwt-results.md. If no JWT usage is found in Phase 1, Phase 2 is skipped. Use when asked to find JWT, token forgery, or authentication bypass bugs.
JWT Vulnerability Detection
You are performing a focused security assessment to find insecure JSON Web Token (JWT) implementations. This skill uses a two-phase approach with subagents: recon (map the full JWT lifecycle — issuance, verification, and configuration) then analysis (identify every exploitable weakness in those verification sites).
Prerequisites: sast/architecture.md must exist. Run the analysis skill first if it doesn't.
What is an Insecure JWT Implementation
JWTs consist of three Base64URL-encoded parts: header.payload.signature. The header declares the signing algorithm (alg), the payload carries claims (e.g., sub, role, exp), and the signature is a cryptographic proof of integrity. Vulnerabilities arise when the server trusts the token's own claims about how it was signed, fails to verify the signature at all, uses a guessable secret, or trusts attacker-controlled key material embedded in the token itself.
The core pattern: the server does not fully verify the JWT's authenticity and integrity before trusting its claims.
What JWT Vulnerabilities ARE
1. Algorithm confusion — alg: none
The server accepts a JWT whose header declares "alg": "none", bypassing signature verification entirely. An attacker crafts an arbitrary payload, sets alg to none, and omits the signature. If the library processes it, the forged token is accepted.
2. Algorithm confusion — RS256 → HS256 A server configured for RS256 (asymmetric: sign with private key, verify with public key) can be tricked into HS256 mode if the library allows the algorithm to be specified by the token. Since the public key is often retrievable, the attacker signs a forged token with HS256 using the server's public key as the HMAC secret. The server verifies the HMAC using the same public key and accepts the token.
3. Missing or disabled signature verification The server decodes the JWT payload without actually verifying the signature. Common patterns:
- Python (PyJWT):
jwt.decode(token, options={"verify_signature": False}) - Node.js (jsonwebtoken):
jwt.decode(token)instead ofjwt.verify(token, secret) - Manual base64 decode of the payload with no signature check
algorithms=["none"]accepted in the decode call
4. Weak or hardcoded HMAC secret
The server signs tokens with a short, guessable, or hardcoded secret (e.g., "secret", "password", "changeme", "jwt-secret-key"). An attacker who captures a valid token can brute-force the secret offline with tools like hashcat or jwt_tool, then forge arbitrary tokens.
5. Embedded JWK (jwk header injection)
The token header contains an embedded JSON Web Key (jwk parameter). If the verification code trusts the embedded key to verify the token's own signature, an attacker generates their own key pair, signs a forged token with their private key, and embeds their public key in the header. The server verifies the signature using the attacker's embedded public key and accepts the token.
6. JKU / X5U header injection
The jku (JWK Set URL) or x5u (X.509 certificate URL) header value is used to fetch the verification key from a URL. If the server does not validate the URL against an allowlist, the attacker can point it to their own server hosting a crafted key set.
7. Key ID (kid) header injection
The kid header is used to look up the signing key, often from a database or the filesystem. If the kid value is interpolated into a SQL query without sanitization, it becomes an SQL injection vector. If it is concatenated into a file path, it becomes a path traversal vector.
8. Missing claim validation
expnot checked → expired tokens remain valid foreveriss(issuer) not checked → tokens issued by other services are acceptedaud(audience) not checked → tokens intended for other services are acceptednbf(not-before) not checked → tokens used before their valid window
9. No token revocation There is no token blacklist or revocation mechanism. Stolen or logged-out tokens remain valid until they expire. This matters most when token lifetimes are long.
What JWT Vulnerabilities are NOT
Do not flag these as JWT vulnerabilities:
- IDOR: Changing a
user_idclaim to access another user's data is an authorization flaw, not a JWT forgery — only flag if the token itself can be forged - XSS via JWT payload: Injecting
<script>into a claim that is later rendered unescaped — that's XSS, not a JWT bug - CSRF: JWT in cookies without
SameSite— that's a CSRF concern, not a JWT integrity issue - Properly restricted verification:
jwt.verify(token, secret, { algorithms: ['HS256'] })with a strong secret — not vulnerable
Patterns That Prevent JWT Vulnerabilities
1. Algorithm allowlist in verification call
# Python — PyJWT: explicitly specify allowed algorithms
payload = jwt.decode(token, secret, algorithms=["HS256"])
# Node.js — jsonwebtoken: restrict algorithms
jwt.verify(token, secret, { algorithms: ['HS256'] })
# Java — jjwt: specify expected algorithm
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token)
# (jjwt does not use the header's alg; it uses the key type)
2. Strong, randomly generated secret
# Strong secret: at least 256 bits of entropy, not hardcoded
import secrets
SECRET_KEY = secr…[redacted](32) # load from env in production
3. Full claim validation
payload = jwt.decode(
token, secret, algorithms=["HS256"],
options={"require": ["exp", "iss", "aud"]},
issuer="https://myapp.example.com",
audience="myapp-api"
)
4. Asymmetric keys with no algorithm ambiguity
// Use RS256 with public key for verification; never accept HS256 on the same endpoint
jwt.verify(token, publicKey, { algorithms: ['RS256'] })
5. JWK/JKU URL allowlist
# Only fetch keys from a known, trusted JWKS endpoint
ALLOWED_JWKS_URLS = {"https://accounts.google.com/.well-known/jwks.json"}
if jku not in ALLOWED_JWKS_URLS:
raise ValueError("Untrusted JWK URL")
Vulnerable vs. Secure Examples
Python — PyJWT
# VULNERABLE: signature verification disabled
def get_current_user(token: str):
payload = jwt.decode(token, options={"verify_signature": False})
return payload["user_id"]
# VULNERABLE: accepts alg:none because no algorithm restriction
def get_current_user(token: str):
payload = jwt.decode(token, SECRET_KEY) # PyJWT < 2.x default: accepts any alg
return payload["user_id"]
# VULNERABLE: weak hardcoded secret
SECRET_KEY = "secret"
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
# SECURE: algorithm restricted, strong secret from env
SECRET_KEY = os.environ["JWT_SECRET"] # strong, random, from environment
def get_current_user(token: str):
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
return payload["user_id"]
Node.js — jsonwebtoken
// VULNERABLE: jwt.decode() — no signature verification
function getUser(token) {
const payload = jwt.decode(token); // decode only, never verify
return payload.userId;
}
// VULNERABLE: algorithms not restricted — susceptible to alg:none or RS256→HS256
function getUser(token) {
const payload = jwt.verify(token, SECRET); // no algorithms option
return payload.userId;
}
// VULNERABLE: weak hardcoded secret
const SECRET = "password123";
jwt.verify(token, SECRET, { algorithms: ['HS256'] });
// SECURE: algorithm restricted, strong secret from env
const SECRET = process.env.JWT_SECRET;
function getUser(token) {
const payload = jwt.verify(token, SECRET, { algorithms: ['HS256'] });
return payload.userId;
}
Java — jjwt
// VULNERABLE: deprecated parser (accepts alg from header)
Jwts.parser().setSigningKey(key).parseClaimsJws(token);
// VULNERABLE: no expiry check — the library default may not enforce exp
Claims claims = Jwts.parserBuilder()
.setSigningKey(key).build()
.parseClaimsJws(token).getBody();
// claims.getExpiration() never checked
// SECURE: parserBuilder (does not trust header alg; uses key type)
Claims claims = Jwts.parserBuilder()
.requireIssuer("myapp")
.requireAudience("myapp-api")
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
Go — golang-jwt / dgrijalva/jwt-go
// VULNERABLE: accepts any algorithm including "none"
token, _ := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return []byte(secret), nil // no algorithm check
})
// VULNERABLE: weak secret
var jwtKey = []byte("secret")
// SECURE: validate signing method before returning key
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return jwtKey, nil
})
kid header SQL injection
# VULNERABLE: kid used in SQL query without sanitization
def get_signing_key(kid):
result = db.execute(f"SELECT key FROM jwt_keys WHERE id = '{kid}'")
return result.fetchone()[0]
token_header = jwt.…[redacted](token)
key = get_signing_key(token_header["kid"]) # attacker controls kid
jwt.decode(token, key, algorithms=["HS256"])
# SECURE: kid validated against allowlist or parameterized lookup
def get_signing_key(kid):
result = db.execute("SELECT key FROM jwt_keys WHERE id = %s", (kid,))
row = result.fetchone()
if not row:
raise ValueError("Unknown key id")
return row[0]
Embedded JWK injection
// VULNERABLE: trusts the jwk embedded in the token header
const { publicKey } = getPublicKeyFromHeader(decoded.header); // attacker-supplied
jwt.verify(token, publicKey);
// SECURE: only use keys from a pre-configured, trusted source
const trustedKey = loadKeyFromConfig();
jwt.verify(token, trustedKey, { algorithms: ['RS256'] });
Execution
This skill runs in two phases using subagents. Pass the contents of sast/architecture.md to both subagents as context.
Phase 1: Map the JWT Lifecycle
Launch a subagent with the following instructions:
Goal: Map how the application creates, transmits, and verifies JWTs. Identify every JWT issuance and verification site, the library used, the signing algorithm and key/secret configuration, and the claims that are used for authorization. Write results to
sast/jwt-recon.md.Context: You will be given the project's architecture summary. Use it to understand the tech stack, authentication layer, and middleware patterns.
What to search for:
1. JWT library imports — identify which JWT library is in use:
- Python:
import jwt,from jose import,from authlib import,import python_jose- Node.js:
require('jsonwebtoken'),import jwt from 'jsonwebtoken',jose,@nestjs/jwt- Java:
io.jsonwebtoken,com.auth0.jwt,nimbus-jose-jwt- Go:
github.com/golang-jwt/jwt,github.com/dgrijalva/jwt-go,github.com/lestrrat-go/jwx- Ruby:
jwtgem (require 'jwt')- PHP:
firebase/php-jwt,lcobucci/jwt- C#:
System.IdentityModel.Tokens.Jwt,Microsoft.AspNetCore.Authentication.JwtBearer**2
Truncated for display — read the full file on GitHub.
Related Skills
algorithmic-art
177.9kCreating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems.
pptx
177.9kUse this skill any time a .pptx or .potx file is involved in any way — as input, output, or both. This includes: creating slide decks, pitch decks, or presentations; reading, parsing, or extracting text from any .pptx or .potx file (even if the extracted content will be used elsewhere, like in an em…
design
130.2kComprehensive design skill: brand identity, design tokens, UI styling, logo generation (55 styles, Gemini, Atlas Cloud, or MuAPI AI), corporate identity program (50 deliverables, CIP mockups), HTML presentations (Chart.js), banner design (22 styles, social/ads/web/print), icon design (15 styles, SVG…
ui-ux-pro-max
130.2kUI/UX design intelligence for web, mobile, and desktop. This skill should be used when designing, building, reviewing, or fixing interfaces, including pages, components, design systems, accessibility, interaction, responsive layout, typography, color, charts, and stack-specific UI implementation.
Trust signals
From repository metadata: license, adoption, age and documentation. Not a code audit — see the Safety scan above for what the skill file itself contains.
