Fortify Development
coollabsio/coolify
ACTIVATE when the user works on authentication in Laravel. An agent skill from coollabsio/coolify.
JWT and OAuth/OIDC security auditor. An agent skill from LeoYeAI/openclaw-master-skills.
$ npx skills add LeoYeAI/openclaw-master-skills --skill phy-jwt-auth-audit -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install LeoYeAI/openclaw-master-skills phy-jwt-auth-audit --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/LeoYeAI/openclaw-master-skills.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/phy-jwt-auth-audit .claude/skills/phy-jwt-auth-audit && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "phy-jwt-auth-audit" agent skill from https://github.com/LeoYeAI/openclaw-master-skills/tree/main/skills/phy-jwt-auth-audit into .claude/skills/phy-jwt-auth-audit/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "phy-jwt-auth-audit", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/LeoYeAI/openclaw-master-skills/tree/main/skills/phy-jwt-auth-auditType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add LeoYeAI/openclaw-master-skills --skill phy-jwt-auth-audit -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install LeoYeAI/openclaw-master-skills phy-jwt-auth-audit --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/LeoYeAI/openclaw-master-skills.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/phy-jwt-auth-audit .agents/skills/phy-jwt-auth-audit && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "phy-jwt-auth-audit" agent skill from https://github.com/LeoYeAI/openclaw-master-skills/tree/main/skills/phy-jwt-auth-audit into .agents/skills/phy-jwt-auth-audit/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "phy-jwt-auth-audit", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add LeoYeAI/openclaw-master-skills --skill phy-jwt-auth-audit -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install LeoYeAI/openclaw-master-skills phy-jwt-auth-audit --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/LeoYeAI/openclaw-master-skills.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/phy-jwt-auth-audit .cursor/skills/phy-jwt-auth-audit && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "phy-jwt-auth-audit" agent skill from https://github.com/LeoYeAI/openclaw-master-skills/tree/main/skills/phy-jwt-auth-audit into .cursor/skills/phy-jwt-auth-audit/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "phy-jwt-auth-audit", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/LeoYeAI/openclaw-master-skills.git --path skills/phy-jwt-auth-audit--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add LeoYeAI/openclaw-master-skills --skill phy-jwt-auth-audit -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install LeoYeAI/openclaw-master-skills phy-jwt-auth-audit --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/LeoYeAI/openclaw-master-skills.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/phy-jwt-auth-audit .gemini/skills/phy-jwt-auth-audit && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "phy-jwt-auth-audit" agent skill from https://github.com/LeoYeAI/openclaw-master-skills/tree/main/skills/phy-jwt-auth-audit into .gemini/skills/phy-jwt-auth-audit/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "phy-jwt-auth-audit", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install LeoYeAI/openclaw-master-skills phy-jwt-auth-auditInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add LeoYeAI/openclaw-master-skills --skill phy-jwt-auth-audit -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/LeoYeAI/openclaw-master-skills.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/phy-jwt-auth-audit .github/skills/phy-jwt-auth-audit && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "phy-jwt-auth-audit" agent skill from https://github.com/LeoYeAI/openclaw-master-skills/tree/main/skills/phy-jwt-auth-audit into .github/skills/phy-jwt-auth-audit/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "phy-jwt-auth-audit", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add LeoYeAI/openclaw-master-skills --skill phy-jwt-auth-audit -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install LeoYeAI/openclaw-master-skills phy-jwt-auth-audit --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/LeoYeAI/openclaw-master-skills.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/phy-jwt-auth-audit .opencode/skills/phy-jwt-auth-audit && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "phy-jwt-auth-audit" agent skill from https://github.com/LeoYeAI/openclaw-master-skills/tree/main/skills/phy-jwt-auth-audit into .opencode/skills/phy-jwt-auth-audit/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "phy-jwt-auth-audit", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
phy-jwt-auth-auditJWT and OAuth/OIDC security auditor. An agent skill from LeoYeAI/openclaw-master-skills.
Phy JWT Auth Audit is an agent skill from LeoYeAI/openclaw-master-skills. JWT and OAuth/OIDC security auditor. Decodes any JWT token (without verification) to inspect alg/exp/iss/aud/scope claims, detects the "alg:none" bypass vulnerability, expired or no-expiry tokens, overly broad OAuth scopes, JWT stored in localStorage (XSS theft risk), JWT in URL parameters (log leakage), missing issuer/audience validation in source code, hardcoded tokens in .env files, and weak HMAC secrets. Also scans source files for insecure token handling patterns: Bearer token logged, token compared with ==…
Its SKILL.md is about 5.9k tokens, which your agent loads only when the skill is triggered. The skill folder holds 1 other file (for example `_meta.json`).
It sits in Backend & APIs, covering Authentication and OAuth and OpenID Connect. The repository describes itself as: 🧠 Curated collection of 1209+ best OpenClaw skills — weekly updated by MyClaw.ai. The licence is Apache-2.0.
4 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit e5199b5. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
nodeFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names these keys or tokens, usually read from environment variables:
JWT_SECRETLOCALSTORAGE_TOKENHARDCODED_JWT_SECRETFrom names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Phy JWT Auth Audit loads about 5.9k tokens when it runs. Until then it costs about 206 tokens; SKILL.md has 431 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check noted patterns worth knowing about, such as sudo or a known installer.
tion in source code, hardcoded tokens in .env files, and weak HMAC secrets. Also scans source files for insecure token h# Option 3: Audit .env files for hardcoded tokens## Step 3: Scan .env Files for Hardcoded Tokens"""Scan .env files for hardcoded tokens and weak secrets."""for pattern in ['.env', '.env.local', '.env.production', '.env.*']:# JWT token value in .env'issue': 'JWT token hardcoded in .env file',lyzed: eyJhbGci... | Source scan: src/ | .env scan: .### .env Findings`.env:3` — `JWT_SECRET=mysecret` — **CRITICAL: Weak secret**Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from LeoYeAI/openclaw-master-skills at commit e5199b5, republished under its Apache-2.0 licence (© LeoYeAI). 431 words, ~5,856 tokens.
.claude/skills/phy-jwt-auth-audit/SKILL.md (or your agent's skills folder). This skill also uses 1 other file; get the full folder from GitHub.A developer pastes a JWT into a debug log. The logger ships it to Datadog. An attacker finds it in the logs 6 months later. The token never expires.
This skill decodes JWTs without verifying them (which is the point — you need to inspect them even when you don't have the secret), checks their claims against security best practices, scans your codebase for insecure token handling, and finds the OAuth scopes that give more access than necessary.
Zero external API — all analysis runs locally. Works with any JWT/OAuth provider.
# Option 1: Decode and audit a specific JWT token
/jwt-audit eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
# Option 2: Audit source code for insecure token handling
/jwt-audit --scan src/
# Option 3: Audit .env files for hardcoded tokens
/jwt-audit --env-scan
# Option 4: Audit a specific auth file
/jwt-audit src/middleware/auth.ts
# Option 5: Full audit (token decode + code scan + env scan)
/jwt-audit eyJhbG... --scan . --env-scan
# Option 6: Check OAuth scopes in API calls
/jwt-audit --check-scopes
# Option 7: CI mode (exit 1 on critical findings)
/jwt-audit --scan src/ --ci --max-critical 0import base64
import json
import time
from dataclasses import dataclass, field
from typing import Any, Optional
@dataclass
class JwtFinding:
severity: str # CRITICAL / HIGH / MEDIUM / LOW / INFO
claim: str # which claim is affected
issue: str
detail: str
fix: str
def decode_jwt_unsafe(token: str) -> tuple[dict, dict, str]:
"""
Decode a JWT token WITHOUT verifying the signature.
Returns (header, payload, signature_b64).
Safe for inspection purposes — never use for auth decisions.
"""
parts = token.strip().split('.')
if len(parts) != 3:
raise ValueError(f"Invalid JWT format: expected 3 parts, got {len(parts)}")
def b64_decode(s: str) -> dict:
# JWT uses URL-safe base64 without padding
padding = 4 - len(s) % 4
if padding != 4:
s += '=' * padding
raw = base64.urlsafe_b64decode(s)
return json.loads(raw)
header = b64_decode(parts[0])
payload = b64_decode(parts[1])
signature = parts[2]
return header, payload, signature
def analyze_jwt_claims(token: str) -> list[JwtFinding]:
"""Full security analysis of a JWT token's claims and header."""
findings = []
try:
header, payload, sig = decode_jwt_unsafe(token)
except Exception as e:
return [JwtFinding(
severity='CRITICAL', claim='format',
issue='Invalid JWT format', detail=str(e),
fix='Ensure token is a valid JWT (3 base64url parts separated by dots)'
)]
now = int(time.time())
# ── Algorithm checks ─────────────────────────────────────────────────────
alg = header.get('alg', '')
if alg.lower() == 'none':
findings.append(JwtFinding(
severity='CRITICAL', claim='alg',
issue='Algorithm "none" — signature verification disabled',
detail=(
'alg:none means the JWT has no signature. Any payload can be crafted '
'and will be accepted by a vulnerable server. This is CVE-2015-9235.'
),
fix=(
'Server must reject tokens with alg:none. '
'In jsonwebtoken: jwt.verify(token, secret, { algorithms: ["HS256"] })'
),
))
elif alg.startswith('HS') and len(sig) < 32:
findings.append(JwtFinding(
severity='HIGH', claim='alg',
issue=f'Algorithm {alg} with suspiciously short signature',
detail='Short signature may indicate a weak or guessable HMAC secret.',
fix='Use a minimum 256-bit (32-byte) random secret for HMAC-SHA256',
))
elif alg == 'RS256' or alg == 'ES256':
findings.append(JwtFinding(
severity='INFO', claim='alg',
issue=f'Algorithm: {alg} (asymmetric — good)',
detail='RSA/ECDSA signature — cannot be forged without the private key.',
fix='Ensure public key is loaded from a trusted source, not from the JWT header itself.',
))
# Algorithm confusion: HS256 when server expects RS256
if alg == 'HS256':
findings.append(JwtFinding(
severity='MEDIUM', claim='alg',
issue='HS256 — verify server rejects RS256→HS256 algorithm confusion',
detail=(
'If the server also supports RS256, an attacker may forge tokens using '
'the public key as the HMAC secret (CVE-2016-10555 pattern).'
),
fix=(
'Explicitly specify allowed algorithms on verify: '
'jwt.verify(token, secret, { algorithms: ["HS256"] })'
),
))
# ── Expiry checks ────────────────────────────────────────────────────────
exp = payload.get('exp')
iat = payload.get('iat')
nbf = payload.get('nbf')
if exp is None:
findings.append(JwtFinding(
severity='HIGH', claim='exp',
issue='No expiry (exp) claim — token is valid forever',
detail=(
'Without exp, a stolen token can be used indefinitely. '
'OWASP API Security Top 10 A2: Broken Authentication.'
),
fix='Add exp claim: { exp: Math.floor(Date.now()/1000) + (60*60) } // 1 hour',
))
else:
ttl = exp - now
if ttl < 0:
findings.append(JwtFinding(
severity='HIGH', claim='exp',
issue=f'Token EXPIRED {abs(ttl)//3600}h {(abs(ttl)%3600)//60}m ago',
detail=f'exp={exp}, current time={now}. Using an expired token is a security risk.',
fix='Generate a fresh token. Check if your token refresh logic is working.',
))
elif ttl > 86400 * 30: # > 30 days
findings.append(JwtFinding(
severity='MEDIUM', claim='exp',
issue=f'Token expires in {ttl//86400} days — very long-lived',
detail='Long-lived tokens increase the window of exposure after theft.',
fix=(
'Use short-lived access tokens (≤1 hour) + refresh tokens. '
'For JWTs: exp should be 15min–1hr for sensitive endpoints.'
),
))
else:
findings.append(JwtFinding(
severity='INFO', claim='exp',
issue=f'Token expires in {ttl//3600}h {(ttl%3600)//60}m',
detail=f'exp={exp}',
fix='',
))
# ── Issuer / Audience ────────────────────────────────────────────────────
iss = payload.get('iss')
aud = payload.get('aud')
if not iss:
findings.append(JwtFinding(
severity='MEDIUM', claim='iss',
issue='Missing issuer (iss) claim',
detail='Without iss, tokens from different issuers (auth providers) are indistinguishable.',
fix='Add iss claim and validate it on the server: verify(token, key, { issuer: "https://auth.myapp.com" })',
))
if not aud:
findings.append(JwtFinding(
severity='MEDIUM', claim='aud',
issue='Missing audience (aud) claim',
detail=(
'Without aud, a token issued for service A can be used against service B. '
'This enables cross-service replay attacks.'
),
fix='Add aud claim and validate: verify(token, key, { audience: "api.myapp.com" })',
))
# ── Sensitive data in payload ─────────────────────────────────────────────
SENSITIVE_KEYS = ['password', 'secret', 'credit_card', 'ssn', 'cvv', 'private_key']
for key in SENSITIVE_KEYS:
if key in payload:
findings.append(JwtFinding(
severity='CRITICAL', claim=key,
issue=f'Sensitive field "{key}" in JWT payload',
detail=(
'JWT payloads are base64-encoded, NOT encrypted. '
'Anyone with the token can decode and read this value.'
),
fix=f'Remove "{key}" from JWT payload. Use JWE (JSON Web Encryption) if the data must be in the token.',
))
# ── Scope analysis ────────────────────────────────────────────────────────
scope = payload.get('scope', payload.get('scp', ''))
if isinstance(scope, str):
scopes = scope.split()
elif isinstance(scope, list):
scopes = scope
else:
scopes = []
OVERLY_BROAD_SCOPES = {
'admin', 'root', 'superuser', '*', 'all', 'write:*', 'read:*',
'openid email profile address phone offline_access', # too many OIDC scopes
}
for s in scopes:
if s in OVERLY_BROAD_SCOPES or s.endswith(':*'):
findings.append(JwtFinding(
severity='HIGH', claim='scope',
issue=f'Overly broad scope: "{s}"',
detail='This scope grants more access than most operations need (principle of least privilege violation).',
fix='Issue tokens with minimal scopes needed for each operation.',
))
# ── Role escalation risk ─────────────────────────────────────────────────
role = payload.get('role', payload.get('roles', payload.get('groups', [])))
if isinstance(role, str):
role = [role]
if isinstance(role, list):
for r in role:
if str(r).lower() in ('admin', 'superadmin', 'root', 'super_admin'):
findings.append(JwtFinding(
severity='MEDIUM', claim='role',
issue=f'Admin role in JWT payload: role="{r}"',
detail=(
'If the server trusts the role claim from the JWT without '
'verifying against a database, any token can be forged to role:admin.'
),
fix='Validate roles from the database, not from the JWT payload claims.',
))
return findings, header, payloadimport re
import glob
from pathlib import Path
SKIP_DIRS = {'node_modules', '.git', 'dist', 'build', '__pycache__',
'.next', 'vendor', 'venv', '.venv'}
# Source code anti-patterns
CODE_PATTERNS = [
# JWT decoded but alg not validated
{
'name': 'JWT_NO_ALG_VALIDATION',
'pattern': re.compile(r'jwt\.(verify|decode)\s*\([^,)]+,\s*[^,)]+\)', re.I),
'check': lambda line, ctx: 'algorithm' not in ctx and 'algorithms' not in ctx,
'severity': 'HIGH',
'message': 'jwt.verify() without algorithms option — vulnerable to alg:none and algorithm confusion',
'fix': 'Add algorithms option: jwt.verify(token, secret, { algorithms: ["HS256"] })',
},
# Token stored in localStorage
{
'name': 'LOCALSTORAGE_TOKEN',
'pattern': re.compile(
r'localStorage\.(setItem|getItem)\s*\(["\'](?:token|auth|jwt|access_token|bearer)["\']',
re.I
),
'check': lambda line, ctx: True,
'severity': 'HIGH',
'message': 'Token stored in localStorage — vulnerable to XSS theft',
'fix': 'Store tokens in httpOnly cookies (inaccessible to JavaScript): Set-Cookie: token=X; HttpOnly; Secure; SameSite=Strict',
},
# Token in URL / query parameter
{
'name': 'TOKEN_IN_URL',
'pattern': re.compile(
r'[\?&](token|access_token|jwt|auth_token|bearer)\s*=',
re.I
),
'check': lambda line, ctx: True,
'severity': 'HIGH',
'message': 'Token in URL query parameter — appears in server logs, browser history, Referer headers',
'fix': 'Pass token in Authorization header: Authorization: Bearer <token>',
},
# Bearer token logged
{
'name': 'TOKEN_LOGGED',
'pattern': re.compile(
r'(console\.|logger\.|log\.|print\()[^;)]*(?:token|bearer|jwt|auth)',
re.I
),
'check': lambda line, ctx: True,
'severity': 'CRITICAL',
'message': 'Auth token may be logged — enables credential theft from log systems',
'fix': 'Never log tokens. Log: { has_token: !!token } instead.',
},
# Token compared with == (timing attack)
{
'name': 'TIMING_ATTACK',
'pattern': re.compile(
r'(?:token|secret|hmac)\s*[=!]=\s*|===\s*(?:token|secret|hmac)',
re.I
),
'check': lambda line, ctx: True,
'severity': 'MEDIUM',
'message': 'Token/secret compared with == — vulnerable to timing attacks',
'fix': 'Use constant-time comparison: crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b))',
},
# Missing Bearer prefix check
{
'name': 'MISSING_BEARER_CHECK',
'pattern': re.compile(
r'req\.headers\.authorization\s*\|\|\s*req\.headers\[.authorization.\]',
re.I
),
'check': lambda line, ctx: 'split' not in ctx and 'Bearer' not in ctx,
'severity': 'MEDIUM',
'message': 'Authorization header accessed without validating "Bearer " prefix',
'fix': (
"const [scheme, token] = req.headers.authorization?.split(' ') ?? [];\n"
"if (scheme !== 'Bearer') throw new Error('Invalid auth scheme');"
),
},
# Python: PyJWT decode without verification
{
'name': 'PYJWT_NO_VERIFY',
'pattern': re.compile(r'jwt\.decode\s*\([^)]*options\s*=.*verify_signature.*False', re.I),
'check': lambda line, ctx: True,
'severity': 'CRITICAL',
'message': 'PyJWT decode with verify_signature=False — signature is NOT checked',
'fix': 'Remove options={"verify_signature": False} unless you explicitly need unsigned inspection',
},
# Hardcoded JWT secret
{
'name': 'HARDCODED_JWT_SECRET',
'pattern': re.compile(
r'(jwt_secret|JWT_SECRET|jwtSecret|secret)\s*[=:]\s*["\'](?!process\.|os\.|getenv)[^"\']{4,}["\']',
re.I
),
'check': lambda line, ctx: True,
'severity': 'CRITICAL',
'message': 'Hardcoded JWT secret in source code — anyone with repo access can forge tokens',
'fix': 'Load from environment: process.env.JWT_SECRET or os.getenv("JWT_SECRET")',
},
]
def scan_auth_code(src_dir: str = '.') -> list[dict]:
findings = []
for ext in ['.js', '.ts', '.jsx', '.tsx', '.py', '.go', '.java', '.rb']:
for fpath in glob.glob(f'{src_dir}/**/*{ext}', recursive=True):
if any(skip in fpath for skip in SKIP_DIRS):
continue
try:
content = Path(fpath).read_text(errors='replace')
lines = content.splitlines()
except Exception:
continue
for i, line in enumerate(lines, 1):
for p in CODE_PATTERNS:
if p['pattern'].search(line):
ctx_start = max(0, i - 5)
ctx_end = min(len(lines), i + 10)
context = '\n'.join(lines[ctx_start:ctx_end])
if p['check'](line, context):
findings.append({
'file': fpath,
'line': i,
'code': line.strip()[:120],
'name': p['name'],
'severity': p['severity'],
'message': p['message'],
'fix': p['fix'],
})
return findingsimport os
import re
from pathlib import Path
# JWT-like patterns (3 base64 parts) and API key patterns
JWT_PATTERN = re.compile(
r'[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}'
)
SHORT_LIVED_ENV_KEYS = re.compile(
r'(jwt_secret|access_token|bearer_token|auth_token|api_key|private_key)',
re.I
)
WEAK_SECRETS = ['secret', 'password', '123456', 'your-secret', 'changeme',
'test', 'dev', 'development', 'supersecret', 'mysecret']
def scan_env_files(root: str = '.') -> list[dict]:
"""Scan .env files for hardcoded tokens and weak secrets."""
findings = []
env_files = []
for pattern in ['.env', '.env.local', '.env.production', '.env.*']:
env_files.extend(glob.glob(f'{root}/{pattern}'))
for env_path in env_files:
if not os.path.exists(env_path):
continue
# Skip .env.example files
if 'example' in env_path.lower() or 'sample' in env_path.lower():
continue
try:
lines = Path(env_path).read_text(errors='replace').splitlines()
except Exception:
continue
for i, line in enumerate(lines, 1):
if line.startswith('#') or '=' not in line:
continue
key, _, value = line.partition('=')
key = key.strip()
value = value.strip().strip('"\'')
# JWT token value in .env
if JWT_PATTERN.match(value):
findings.append({
'file': env_path,
'line': i,
'key': key,
'issue': 'JWT token hardcoded in .env file',
'severity': 'HIGH',
'fix': 'Generate dynamic tokens at runtime. Store only the signing secret, not a token.',
})
# Weak secret values
if SHORT_LIVED_ENV_KEYS.search(key) and value.lower() in WEAK_SECRETS:
findings.append({
'file': env_path,
'line': i,
'key': key,
'issue': f'Weak JWT secret: "{value}"',
'severity': 'CRITICAL',
'fix': (
'Generate a strong 256-bit secret:\n'
' node -e "console.log(require(\'crypto\').randomBytes(32).toString(\'hex\'))"\n'
' python3 -c "import secrets; print(secrets.token_hex(32))"'
),
})
return findings## JWT & Auth Security Audit
Token analyzed: eyJhbGci... | Source scan: src/ | .env scan: .
---
### Token Analysis
**Decoded Header:**
```json
{ "alg": "HS256", "typ": "JWT" }Decoded Payload:
{
"sub": "user_12345",
"email": "alice@example.com",
"role": "admin",
"scope": "read:* write:* admin",
"iat": 1710000000,
"exp": 1741536000
}| # | Severity | Claim | Issue |
|---|---|---|---|
| 1 | 🔴 HIGH | scope | Wildcard scopes read:* write:* admin — principle of least privilege violation |
| 2 | 🔴 HIGH | exp | Token expires in 365 days — too long-lived |
| 3 | 🟠 MEDIUM | role | role:admin in payload — if trusted without DB check, forgeable |
| 4 | 🟠 MEDIUM | aud | Missing audience claim |
| 5 | 🟡 MEDIUM | alg | HS256 — confirm server rejects RS256→HS256 confusion |
🔴 CRITICAL — Bearer token logged
src/middleware/auth.ts:23
logger.debug(`Auth header: ${req.headers.authorization}`)This logs the full Bearer token to your logging system. Anyone with log access can steal tokens.
Fix:
logger.debug('Auth header present', { has_token: !!req.headers.authorization })🔴 CRITICAL — Hardcoded JWT secret
src/config/jwt.ts:5
const JWT_SECRET = 'mysecret'This secret is in version control. Anyone with repo access can forge JWTs for any user.
Fix:
const JWT_SECRET = process.env.JWT_SECRET;
if (!JWT_SECRET) throw new Error('JWT_SECRET env var required');🔴 HIGH — jwt.verify() without algorithms
src/auth/verify.ts:14
const payload = jwt.verify(token, secret)Vulnerable to alg:none bypass and algorithm confusion attack.
Fix:
const payload = jwt.verify(token, secret, { algorithms: ['HS256'] })🟠 HIGH — Token in localStorage
src/auth/login.ts:45
localStorage.setItem('token', response.data.access_token)Token in localStorage is readable by any JavaScript, including injected XSS payloads.
Fix:
// Server should set: Set-Cookie: token=X; HttpOnly; Secure; SameSite=Strict
// Client reads from cookie automatically — no JavaScript access needed.env:3 — JWT_SECRET=mysecret — CRITICAL: Weak secret
Generate a strong replacement:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
# → a7f3e2b1c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1| Severity | Count | Must Fix Before Deploy |
|---|---|---|
| 🔴 CRITICAL | 2 | YES — hardcoded secret, token logged |
| 🔴 HIGH | 4 | YES — alg check, localStorage, long-lived token |
| 🟠 MEDIUM | 3 | Recommended |
Priority order:
---
## Quick Mode Output
JWT Audit: eyJhbGci... + src/ scan
Token: 🔴 No aud claim 🟠 role:admin in payload 🟠 expires in 365 days 🔴 Wildcard scopes: read:* write:* admin
Code (src/): 🔴 CRITICAL: JWT_SECRET hardcoded in jwt.ts:5 🔴 CRITICAL: Bearer token logged in auth.ts:23 🔴 HIGH: jwt.verify() without algorithms (alg:none bypass) in verify.ts:14 🟠 HIGH: Token in localStorage in login.ts:45
.env: 🔴 CRITICAL: JWT_SECRET=mysecret (weak secret)
Fix order: 1) Rotate secret 2) Add algorithms to verify() 3) Move to httpOnly cookie
© LeoYeAI, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 1 other file in skills/phy-jwt-auth-audit of LeoYeAI/openclaw-master-skills.
Open the folder on GitHubat commit e5199b5
Phy JWT Auth Audit next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Phy JWT Auth Audit this skillLeoYeAI/openclaw-master-skills | 2.2k | — | ~5.9k | Automated safety check: Notes | Apache-2.0 | |
| Fortify Developmentcoollabsio/coolify | 63k | 4 repos | ~1.9k | Automated safety check: Pass | MIT | |
| Cognitoitsmostafa/aws-agent-skills | 1.2k | 1 repos | ~2.3k | Automated safety check: Pass | MIT | |
| Security Reviewdoorkeeper-gem/doorkeeper | 5.5k | — | ~1.4k | Automated safety check: Pass | MIT | |
| OAuth Account Setupspinabot/brigade | 11k | — | ~878 | Automated safety check: Pass | MIT | |
| Auth Implementation Patternsynulihao/AgentSkillOS | 618 | 10 repos | ~4.4k | Automated safety check: Pass | None |
coollabsio/coolify
ACTIVATE when the user works on authentication in Laravel. An agent skill from coollabsio/coolify.
itsmostafa/aws-agent-skills
AWS Cognito user authentication and authorization service. An agent skill from itsmostafa/aws-agent-skills.
doorkeeper-gem/doorkeeper
Verify that code changes do not introduce OAuth security vulnerabilities.
spinabot/brigade
Connects an OAuth 2.0 account such as Gmail with the built-in oauth_authorize tool: an authorization link, automatic code capture and sealed token storage.
ynulihao/AgentSkillOS
Master authentication and authorization patterns including JWT, OAuth2, session management, and RBAC to build secure, scalable access control systems.
hexlet-volunteers/hexlet-sicp
Manages OAuth social authentication with Laravel Socialite. An agent skill from hexlet-volunteers/hexlet-sicp.
LeoYeAI/openclaw-master-skills
Manages pipelines on a DevOps quality and efficiency platform through its OpenAPI: list workspaces and templates, create, update, run and cancel pipelines, and read run records.
LeoYeAI/openclaw-master-skills
Patches OpenClaw's Feishu extension so an edited document triggers an isolated agent session that reads the doc and replies inline, turning it into a live chat space.
LeoYeAI/openclaw-master-skills
Multi-context memory management system for OpenClaw agents with group-isolated storage, global shared memory, workspace organization, and group-specific skills isolation.
LeoYeAI/openclaw-master-skills
Runs a brand's AI-search visibility work end to end: diagnosing how AI platforms represent it, repositioning it, producing AI-optimized content and monitoring ongoing mentions.
LeoYeAI/openclaw-master-skills
Installs and authenticates the gws CLI, then automates Gmail, Drive, Sheets, Calendar, Docs, Chat and Tasks with ready-made recipes, persona bundles and security audits.
LeoYeAI/openclaw-master-skills
Runs four advisor roles, a fitness coach, nutritionist, data analyst and TCM practitioner, to build a health profile and track workouts, diet and wellness over time.
Categories
JWT and OAuth/OIDC security auditor. An agent skill from LeoYeAI/openclaw-master-skills. Phy JWT Auth Audit is an agent skill from LeoYeAI/openclaw-master-skills. JWT and OAuth/OIDC security auditor.
Phy JWT Auth Audit fits situations like: tasks that involve Authentication; tasks that involve OAuth and OpenID Connect.
Run `npx skills add LeoYeAI/openclaw-master-skills --skill phy-jwt-auth-audit -a claude-code`. Or copy the skill folder (skills/phy-jwt-auth-audit in LeoYeAI/openclaw-master-skills) into .claude/skills/phy-jwt-auth-audit in your project. Claude Code loads it when a task matches its description.
Run `npx skills add LeoYeAI/openclaw-master-skills --skill phy-jwt-auth-audit -a codex`. Or copy the skill folder (skills/phy-jwt-auth-audit in LeoYeAI/openclaw-master-skills) into .agents/skills/phy-jwt-auth-audit in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add LeoYeAI/openclaw-master-skills --skill phy-jwt-auth-audit -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/phy-jwt-auth-audit, .gemini/skills/phy-jwt-auth-audit, .github/skills/phy-jwt-auth-audit and .opencode/skills/phy-jwt-auth-audit in your project.
Going by SKILL.md and its folder, Phy JWT Auth Audit needs the command-line tools its instructions call (node) and credentials named JWT_SECRET, LOCALSTORAGE_TOKEN and HARDCODED_JWT_SECRET. Our summary lists: Python 3.
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found notes only (mentions a .env file), nothing it rates as a warning. It is not a guarantee. Review the folder before installing.
Phy JWT Auth Audit is published under the Apache-2.0 licence (declared in SKILL.md). It allows redistribution, so the full SKILL.md is shown on this page.
About 5.9k tokens (SKILL.md is roughly 23k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Phy JWT Auth Audit: Fortify Development (coollabsio/coolify, 63k stars), Cognito (itsmostafa/aws-agent-skills, 1.2k stars), Security Review (doorkeeper-gem/doorkeeper, 5.5k stars) and OAuth Account Setup (spinabot/brigade, 11k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
LeoYeAI (a GitHub user) maintains it in LeoYeAI/openclaw-master-skills, which has 2,161 GitHub stars. The repository holds 1,235 skills in this directory. The repository was last updated on July 20, 2026.
Source: LeoYeAI/openclaw-master-skills on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.