authentication-failures — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited authentication-failures (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Rank: #7 (stable) | CWEs covered: 36 | Occurrences: 1.1 million+ New 2025 threat: Hybrid password attacks / password spray — attackers increment leaked credentials (e.g., Password1! → Password2!).
Authentication Failures occur when an application's identity verification mechanisms are absent, weak, or bypassable. Key attack patterns:
Work through each section systematically. Mark ✓ pass, ✗ fail, N/A where not applicable.
# ✓ GOOD — short-lived tokens, claim validation
token = jwt.encode(
{"sub": user_id, "exp": datetime.utcnow() + timedelta(minutes=15),
"iss": "myapp", "aud": "myapp-client"},
settings.SECRET_KEY, algorithm="HS256"
)
jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"],
audience="myapp-client", issuer="myapp")
# ✗ BAD — no expiry, no claim validation, accepts any algorithm
jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256", "RS256", "none"])Checks:
aud (audience) and iss (issuer) claims are set AND verified on decode"none"os.environ, minimum 32 characters# FastAPI — slowapi
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
@app.post("/login")
@limiter.limit("5/minute") # ✓ per-IP rate limit
async def login(request: Request, credentials: LoginSchema):
...
# Flask — Flask-Limiter
from flask_limiter import Limiter
limiter = Limiter(app, key_func=get_remote_address, default_limits=["200/day"])
@app.route("/login", methods=["POST"])
@limiter.limit("5 per minute") # ✓
def login():
...Checks:
429 Too Many Requests, not 401# ✓ GOOD — check against breached databases
import hashlib, httpx
def is_pwned(password: str) -> bool:
sha1 = hashlib.sha1(password.encode()).hexdigest().upper()
prefix, suffix = sha1[:5], sha1[5:]
resp = httpx.get(f"https://api.pwnedpasswords.com/range/{prefix}")
return suffix in resp.text
# ✓ GOOD — enforce minimum complexity
def validate_password(password: str):
if len(password) < 12:
raise ValueError("Password must be at least 12 characters")
if is_pwned(password):
raise ValueError("Password found in known data breach")Checks:
# ✓ GOOD — Flask-Login with secure session config
app.config.update(
SESSION_COOKIE_SECURE=True, # HTTPS only
SESSION_COOKIE_HTTPONLY=True, # no JS access
SESSION_COOKIE_SAMESITE='Lax', # CSRF mitigation
PERMANENT_SESSION_LIFETIME=timedelta(hours=1),
)
@app.route("/logout")
@login_required
def logout():
logout_user() # ✓ invalidates session
session.clear() # ✓ belt-and-suspenders
return redirect("/login")Checks:
Flask-Login or Flask-Security-Too in use (no hand-rolled session management)SESSION_COOKIE_SECURE, SESSION_COOKIE_HTTPONLY, SESSION_COOKIE_SAMESITE all set# ✗ BAD — reveals which field was wrong
if not user:
return {"error": "Username not found"}
if not verify_password(password, user.hashed_password):
return {"error": "Incorrect password"}
# ✓ GOOD — generic message regardless of failure reason
if not user or not verify_password(password, user.hashed_password):
return JSONResponse({"error": "Invalid credentials"}, status_code=401)Checks:
"Invalid credentials" (or equivalent)404 for missing users vs 401 for bad passwordhmac.compare_digest and always hash even for unknown users)# ✗ BAD — Bandit will flag these
ADMIN_PASSWORD = "admin"
TEST_USER = {"username": "test", "password": "test123"}
JWT_SECRET = "supersecretkey"
# ✓ GOOD
JWT_SECRET = os.environ["JWT_SECRET"] # loaded from environment / secrets managerChecks:
bandit -r . -t B105,B106,B107 (hardcoded password checks)admin/admin, test/test, root/root patterns in config, seeds, or fixtures.env files excluded from version control via .gitignoreChecks:
| CWE | Name | Typical Finding |
|---|---|---|
| CWE-287 | Improper Authentication | Missing auth check, bypassable auth |
| CWE-307 | Unrestricted Auth Attempts | No rate limit on login endpoint |
| CWE-384 | Session Fixation | Session ID not regenerated post-login |
| CWE-521 | Weak Password Requirements | No minimum length/complexity enforced |
| CWE-798 | Hard-coded Credentials | Secrets in source code |
| CWE-613 | Insufficient Session Expiration | Sessions survive logout; no timeout |
# Static analysis — catches CWE-798, weak crypto, debug mode
bandit -r . -ll -ii
# Specifically target hardcoded credentials
bandit -r . -t B105,B106,B107,B108
# Dependency vulnerabilities (may expose auth libraries with known CVEs)
pip-audit
safety check
# Check JWT library versions for known vulns
pip show python-jose PyJWT authlib"none" accepted → immediate fixslowapi/Flask-Limiter immediately~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.