SkillAgentSearch skills...

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-jwt

Installs into whichever agent you are using.

About this skill
📄

SKILL.md

Installable skill definition

Quality Score

89/100

Category

Security

Supported Platforms

Universal

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.

Substance
30/30
Structure
20/20
Description
15/15
Adoption
13/20
Freshness
11/15

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.

SkillScoreStarsUpdatedFormat
sast-jwt (this skill)by utkusen891.3k6mo agoSKILL.md
algorithmic-artby anthropics100177.9k8d agoSKILL.md
pptxby anthropics100177.9k8d agoSKILL.md
designby nextlevelbuilder100130.2k9d agoSKILL.md
ui-ux-pro-maxby nextlevelbuilder100130.2k9d agoSKILL.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.

name: 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 of jwt.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

  • exp not checked → expired tokens remain valid forever
  • iss (issuer) not checked → tokens issued by other services are accepted
  • aud (audience) not checked → tokens intended for other services are accepted
  • nbf (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_id claim 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: jwt gem (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

View on GitHub
GitHub Stars1.3k
CategorySecurity
Updated5mo ago
Forks65

Trust signals

98/100

From repository metadata: license, adoption, age and documentation. Not a code audit — see the Safety scan above for what the skill file itself contains.

1 info