cryptographic-failures — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited cryptographic-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.
OWASP Rank: #4 (dropped from #2 due to improved TLS adoption) Incidence: 3.80% of applications, 1.6M+ occurrences, 32 CWEs
Cryptographic Failures cover the absence, misuse, or weakness of cryptography protecting data at rest and in transit. In Python, there is a distinctive set of pitfalls that differ from Java/.NET.
| Area | Key Risk | Python-Specific Pitfall |
|---|---|---|
| Password hashing | Weak/missing algorithm | Using hashlib.md5/sha1 instead of bcrypt/Argon2 |
| Randomness | Predictable values | import random instead of import secrets |
| Hardcoded keys | Key leakage | SECRET_KEY = "..." literals in source |
| TLS/SSL | Cert bypass | verify=False in requests/httpx |
| JWT secrets | Key strength/algorithm | Weak secret, missing algo pinning |
| Symmetric encryption | Deprecated modes | ECB mode, no authenticated encryption |
Run Bandit — the primary Python SAST tool for catching crypto issues automatically:
pip install bandit
bandit -r . -t B303,B304,B305,B306,B311,B324,B501,B502,B503,B504,B505,B506 -f json -o bandit_crypto.jsonKey Bandit test IDs for cryptographic failures:
| Bandit ID | What It Catches | Severity |
|---|---|---|
| B303 | MD5/SHA1 usage | Medium |
| B304/B305 | Weak cipher modes (ECB, DES) | High |
| B311 | random module in security context | Low |
| B324 | Hashlib use of weak hash | Medium |
| B501 | verify=False in SSL context | High |
| B502/B503/B504 | Insecure SSL/TLS versions | Medium–High |
| B105/B106 | Hardcoded password/secret | Medium |
Work through these patterns in priority order:
#### 🔴 CRITICAL — Password Hashing
# BAD — MD5/SHA1 for passwords: CWE-328, CWE-759, CWE-916
hashlib.md5(password.encode()).hexdigest()
hashlib.sha1(password.encode()).hexdigest()
hashlib.sha256(password.encode()).hexdigest() # also bad — no salt, no stretching
# GOOD — use passlib with bcrypt or argon2
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["argon2"], deprecated="auto")
# OR
pwd_context = CryptContext(schemes=["bcrypt"], bcrypt__rounds=12, deprecated="auto")
hashed = pwd_context.hash(plain_password)Check for:
hashlib.* call used to hash a password or tokenhashlib.pbkdf2_hmac)rounds < 12 — should be ≥12 to resist brute force#### 🔴 CRITICAL — Weak Randomness (CWE-330)
# BAD — random is NOT cryptographically secure
import random
token = random.randint(100000, 999999) # predictable OTP
reset_key = random.choice(string.ascii_letters + string.digits) # guessable
# GOOD
import secrets
token = secrets.randbelow(900000) + 100000 # secure OTP
reset_key = secrets.token_urlsafe(32) # secure reset token
session_id = secrets.token_hex(16) # secure session IDCheck for: Any random.random(), random.randint(), random.choice(), random.shuffle() in code paths involving tokens, OTPs, session IDs, keys, or nonces.
#### 🔴 HIGH — Hardcoded Cryptographic Keys (CWE-321)
# BAD — key in source code
SECRET_KEY = "my-super-secret-key"
JWT_SECRET = "hardcoded_jwt_secret_123"
app.config['SECRET_KEY'] = "flask_secret"
# GOOD — load from environment
import os
SECRET_KEY = os.environ["SECRET_KEY"] # raises KeyError if missing (intentional)
SECRET_KEY = os.getenv("SECRET_KEY") # returns None silently (verify it's not None)Minimum key lengths:
SECRET_KEY: ≥32 random bytes (use secrets.token_hex(32))#### 🔴 HIGH — TLS/SSL Certificate Bypass (CWE-295)
# BAD — disables certificate validation entirely
requests.get(url, verify=False)
httpx.get(url, verify=False)
ssl._create_unverified_context()
urllib3.disable_warnings() # companion to verify=False
# GOOD
requests.get(url) # verify=True is the default
requests.get(url, verify="/path/to/ca-bundle.crt") # custom CA#### 🟡 MEDIUM — JWT Configuration (CWE-327)
# BAD — algorithm confusion attacks
jwt.decode(token, key, algorithms=["none"]) # NEVER allow "none"
jwt.decode(token, key) # no algorithm pin = vulnerable
# BAD — weak secret loaded without validation
JWT_SECRET = os.getenv("JWT_SECRET", "default") # fallback default is dangerous
# GOOD
JWT_SECRET = os.environ["JWT_SECRET"] # fail hard if missing
assert len(JWT_SECRET) >= 32, "JWT secret too short"
jwt.decode(token, JWT_SECRET, algorithms=["HS256"]) # explicit allowlistAlso check:
exp claim) — access tokens should be 15–30 minaud and iss claims are validated on decode#### 🟡 MEDIUM — Deprecated Symmetric Encryption (CWE-327)
# BAD — ECB mode leaks patterns in data
from Crypto.Cipher import AES
cipher = AES.new(key, AES.MODE_ECB)
# BAD — no authentication = vulnerable to tampering
cipher = AES.new(key, AES.MODE_CBC, iv)
# GOOD — authenticated encryption
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
aesgcm = AESGCM(key)
ciphertext = aesgcm.encrypt(nonce, plaintext, associated_data)
# ChaCha20-Poly1305 is also acceptable# 1. Verify OAuth2/JWT middleware algorithm pinning
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
ALGORITHM = "HS256" # must be explicit, not a variable that could be overridden
# Check: is ALGORITHM loaded from config where it could be tampered with?
# 2. Verify password hashing in user creation/login endpoints
@router.post("/register")
async def register(user: UserCreate, db: Session = Depends(get_db)):
hashed = pwd_context.hash(user.password) # must NOT store plain or MD5
# 3. Token generation for password resets / email verification
def generate_reset_token() -> str:
return secrets.token_urlsafe(32) # GOOD
# NOT: str(uuid.uuid4()) — UUID v4 uses os.urandom, acceptable but secrets is preferred
# NOT: random-based anything# 1. Session cookie signing uses SECRET_KEY — must be strong
app.secret_key = os.environ["SECRET_KEY"] # GOOD
app.secret_key = "dev" # BAD — common in tutorials, dangerous in prod
# 2. Flask-Login password verification
from flask_bcrypt import Bcrypt
bcrypt = Bcrypt(app)
bcrypt.generate_password_hash(password, rounds=12) # rounds≥12
# 3. HTTPS enforcement via HSTS
from flask_talisman import Talisman
Talisman(app, force_https=True) # adds HSTS header| CWE | Name | Common Manifestation |
|---|---|---|
| CWE-327 | Use of Broken/Risky Crypto Algorithm | MD5, SHA1, ECB, DES, RC4 |
| CWE-328 | Reversible One-Way Hash | MD5/SHA1 for passwords (reversible via rainbow tables) |
| CWE-330 | Insufficiently Random Values | random module for tokens/keys |
| CWE-321 | Hard-coded Cryptographic Key | Secrets in source code |
| CWE-759 | One-Way Hash Without a Salt | hashlib.sha256(password) — no salt |
| CWE-916 | Password Hash With Insufficient Effort | bcrypt rounds<12, PBKDF2 iterations<600k |
| CWE-295 | Improper Certificate Validation | verify=False in HTTP clients |
Password hashing: passlib[argon2] or passlib[bcrypt] (rounds≥12)
Token generation: secrets module exclusively
Symmetric crypto: cryptography library, AES-GCM or ChaCha20-Poly1305
JWT: python-jose or PyJWT, algorithm pinned, secret≥32 chars
TLS: verify=True always; pin CA bundle where needed
Config/secrets: os.environ (fail-hard) or a secrets manager (Vault, AWS SSM)
HSTS: flask-talisman (Flask) or custom middleware (FastAPI)# Find weak hashing
grep -rn "hashlib\.md5\|hashlib\.sha1\|hashlib\.sha224" --include="*.py"
# Find insecure random
grep -rn "import random\b" --include="*.py"
grep -rn "random\.randint\|random\.choice\|random\.random()" --include="*.py"
# Find hardcoded secrets
grep -rn "SECRET_KEY\s*=\s*['\"]" --include="*.py"
grep -rn "verify=False" --include="*.py"
# Find ECB mode
grep -rn "MODE_ECB\|AES\.MODE_ECB" --include="*.py"references/owasp-a04-full.md — full OWASP A04:2025 spec and extended CWE listsoftware-supply-chain skillauthentication-failures skill~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.