Systematically reviews code for SQL injection, XSS, SSRF, broken access control, cryptographic failures, and other common OWASP Top 10 vulnerabilities, providing vulnerable code examples and ready-to-use remediation guidance. Trigger this skill when users ask for a security review, vulnerability scan, or penetration testing assistance, or mention keywords like OWASP, SQL injection, XSS, code audit, or security checklist.
68
82%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
A systematic security review based on the OWASP Top 10 (2021) standard. Each item includes: vulnerability description, typical vulnerable code, inspection checkpoints, and remediation examples. Designed for security-focused code review of web applications.
Provide the code files or code snippets to review, and specify which OWASP categories to check (or request a full review) to receive an item-by-item audit report.
Example prompts:
| ID | Category | Key Check |
|---|---|---|
| A01 | Broken Access Control | Does every endpoint verify the current user's identity? Can users access others' data by changing IDs? |
| A02 | Cryptographic Failures | Are passwords hashed with bcrypt/argon2? Are secrets hardcoded? |
| A03 | Injection | String-concatenated SQL? shell=True? Unescaped template output? |
| A04 | Insecure Design | Is rate limiting in place? Can critical workflows be bypassed? |
| A05 | Security Misconfiguration | DEBUG enabled? Stack traces in error pages? Default credentials? |
| A06 | Vulnerable Components | Any CVEs from pip audit / npm audit? |
| A07 | Authentication Failures | Is JWT signature verified? Can tokens be revoked? Is MFA available? |
| A08 | Integrity Failures | Any pickle.loads deserializing untrusted data? |
| A09 | Logging & Monitoring Failures | Are plaintext passwords in logs? Are failed logins recorded? |
| A10 | SSRF | Are user-supplied URLs filtered against internal IPs? |
Core principle: prefer false positives over missed true positives.
file:line_numberRisk: Users can access other users' data or perform unauthorized operations.
Checkpoints:
# ❌ Vulnerable: No authorization check — any user can view others' orders by changing user_id
@app.route("/api/orders/<user_id>")
def get_orders(user_id):
orders = db.query(f"SELECT * FROM orders WHERE user_id = {user_id}")
return jsonify(orders)# ✅ Fixed: Verify the authenticated user can only access their own data
@app.route("/api/orders")
@login_required
def get_orders():
current_user_id = get_current_user().id
orders = db.query("SELECT * FROM orders WHERE user_id = %s", (current_user_id,))
return jsonify(orders)Risk: Sensitive data (passwords, credit card numbers, personal information) is unencrypted or uses weak cryptographic algorithms.
Checkpoints:
# ❌ Vulnerable: MD5 for password storage, hardcoded secret key
import hashlib
SECRET_KEY = "my-secret-key-123"
def save_password(password):
hashed = hashlib.md5(password.encode()).hexdigest()
db.save(hashed)# ✅ Fixed: bcrypt for password hashing, secret key from environment variable
import bcrypt
import os
SECRET_KEY = os.environ["SECRET_KEY"]
def save_password(password):
salt = bcrypt.gensalt()
hashed = bcrypt.hashpw(password.encode(), salt)
db.save(hashed)Risk: User input is concatenated directly into SQL, OS commands, LDAP queries, etc., allowing attackers to execute arbitrary queries or commands.
Checkpoints:
os.system() or subprocess.call(shell=True) calls that concatenate user input?# ❌ Vulnerable: String-concatenated SQL — attacker can input ' OR 1=1 --
@app.route("/api/user")
def get_user():
username = request.args.get("username")
query = f"SELECT * FROM users WHERE username = '{username}'"
result = db.execute(query)
return jsonify(result)# ✅ Fixed: Parameterized query
@app.route("/api/user")
def get_user():
username = request.args.get("username")
result = db.execute(
"SELECT * FROM users WHERE username = %s",
(username,)
)
return jsonify(result)# ❌ Vulnerable: User input concatenated directly into shell command
import os
def ping_host(host):
os.system(f"ping -c 4 {host}")# ✅ Fixed: Use subprocess with list arguments, shell disabled
import subprocess
import re
def ping_host(host):
if not re.match(r'^[a-zA-Z0-9.\-]+$', host):
raise ValueError("Invalid hostname")
subprocess.run(["ping", "-c", "4", host], check=True)Risk: Business logic design flaws that cannot be fixed by a perfect implementation.
Checkpoints:
# ❌ Vulnerable: No attempt limit on verification code — can be brute-forced
@app.route("/api/verify-code", methods=["POST"])
def verify_code():
code = request.json["code"]
stored_code = session.get("verification_code")
if code == stored_code:
return jsonify({"status": "verified"})
return jsonify({"status": "invalid"}), 400# ✅ Fixed: Added attempt limit and expiration
@app.route("/api/verify-code", methods=["POST"])
def verify_code():
attempts = session.get("verify_attempts", 0)
if attempts >= 5:
return jsonify({"error": "Too many attempts, please request a new code"}), 429
code = request.json["code"]
stored = session.get("verification_code")
expire_at = session.get("code_expire_at", 0)
if time.time() > expire_at:
return jsonify({"error": "Verification code has expired"}), 400
session["verify_attempts"] = attempts + 1
if code == stored:
session.pop("verify_attempts", None)
return jsonify({"status": "verified"})
return jsonify({"status": "invalid"}), 400Risk: Applications or servers use default configurations, enable unnecessary features, or expose sensitive information in error messages.
Checkpoints:
# ❌ Vulnerable: DEBUG enabled in production, leaking sensitive information
app = Flask(__name__)
app.config["DEBUG"] = True
app.config["SECRET_KEY"] = "default-secret"
@app.errorhandler(500)
def error_handler(e):
return jsonify({"error": str(e), "traceback": traceback.format_exc()}), 500# ✅ Fixed: Configuration from environment variables, DEBUG off in production
import os
app = Flask(__name__)
app.config["DEBUG"] = os.environ.get("FLASK_DEBUG", "false").lower() == "true"
app.config["SECRET_KEY"] = os.environ["FLASK_SECRET_KEY"]
@app.errorhandler(500)
def error_handler(e):
app.logger.error(f"Internal error: {e}")
return jsonify({"error": "Internal server error, please try again later"}), 500Risk: Using third-party libraries or framework versions with known vulnerabilities.
Checkpoints:
pip audit, npm audit, snyk, etc.)?# Python projects
pip audit
# Node.js projects
npm audit
# General scanning
# Use open-source tools like trivy or grype to scan container/project dependencies# Update vulnerable packages
pip install --upgrade package_name
# Auto-fix npm vulnerabilities
npm audit fix
# Pin dependency versions to prevent implicit upgrades
pip freeze > requirements.txtRisk: Authentication mechanisms have flaws that allow brute-force attacks, credential stuffing, or session hijacking.
Checkpoints:
# ❌ Vulnerable: JWT signature not verified, accepts alg=none
import jwt
def verify_token(token):
payload = jwt.decode(token, options={"verify_signature": False})
return payload# ✅ Fixed: Enforce signature and expiration verification, specify algorithm
import jwt
import os
JWT_SECRET = os.environ["JWT_SECRET"]
def verify_token(token):
try:
payload = jwt.decode(
token,
JWT_SECRET,
algorithms=["HS256"],
options={"require": ["exp", "iat", "sub"]}
)
return payload
except jwt.ExpiredSignatureError:
raise AuthError("Token has expired")
except jwt.InvalidTokenError:
raise AuthError("Invalid token")Risk: Failure to verify the integrity of software updates, critical data, or CI/CD pipelines, enabling supply chain attacks or data tampering.
Checkpoints:
pickle.loads on untrusted data)?# ❌ Vulnerable: Deserializing untrusted data — can lead to remote code execution
import pickle
@app.route("/api/import", methods=["POST"])
def import_data():
data = pickle.loads(request.data)
process(data)
return "OK"# ✅ Fixed: Use a safe data format (JSON), refuse to deserialize arbitrary objects
import json
@app.route("/api/import", methods=["POST"])
def import_data():
try:
data = json.loads(request.data)
except json.JSONDecodeError:
return jsonify({"error": "Invalid JSON format"}), 400
process(data)
return "OK"Risk: Lack of security event logging and monitoring, preventing timely detection and response to attacks.
Checkpoints:
# ❌ Vulnerable: No logging on login failure, and plaintext password in logs
def login(username, password):
user = db.get_user(username)
if not user or not check_password(password, user.password_hash):
print(f"Login failed for {username} with password {password}")
return None
return create_session(user)# ✅ Fixed: Log security events without logging sensitive data
import logging
security_logger = logging.getLogger("security")
def login(username, password):
user = db.get_user(username)
if not user or not check_password(password, user.password_hash):
security_logger.warning(
"Login failed",
extra={"username": username, "ip": request.remote_addr}
)
return None
security_logger.info(
"Login successful",
extra={"username": username, "ip": request.remote_addr}
)
return create_session(user)Risk: The application accepts user-provided URLs and makes server-side requests, allowing attackers to access internal network resources or cloud metadata.
Checkpoints:
# ❌ Vulnerable: Directly requesting user-supplied URL — can access internal network and cloud metadata
import requests
@app.route("/api/fetch-url")
def fetch_url():
url = request.args.get("url")
response = requests.get(url)
return response.text# ✅ Fixed: Validate URL protocol and target address, block internal network access
import requests
import ipaddress
from urllib.parse import urlparse
import socket
BLOCKED_NETWORKS = [
ipaddress.ip_network("127.0.0.0/8"),
ipaddress.ip_network("10.0.0.0/8"),
ipaddress.ip_network("172.16.0.0/12"),
ipaddress.ip_network("192.168.0.0/16"),
ipaddress.ip_network("169.254.0.0/16"),
]
def is_safe_url(url):
parsed = urlparse(url)
if parsed.scheme not in ("http", "https"):
return False
try:
ip = ipaddress.ip_address(socket.gethostbyname(parsed.hostname))
for network in BLOCKED_NETWORKS:
if ip in network:
return False
except (socket.gaierror, ValueError):
return False
return True
@app.route("/api/fetch-url")
def fetch_url():
url = request.args.get("url")
if not is_safe_url(url):
return jsonify({"error": "Access to this address is not allowed"}), 403
response = requests.get(url, timeout=10, allow_redirects=False)
return response.textAfter completing the review, output a report in the following format. All 10 items must appear — mark items with no findings as pass:
# OWASP Top 10 Security Review Report
## Review Summary
- Scope: [list of files/modules]
- Date: [date]
- Risk summary: RED High x | YELLOW Medium x | GREEN Low x | PASS No findings x
## Findings (sorted by severity, descending)
### [Severity] [OWASP ID] — [Issue Title]
- **Location:** [file:line_number]
- **Description:** [issue description]
- **Impact:** [potential consequences]
- **Fix:** (ready-to-use code, not just a description)
### PASS A0X — [Category] — No issues found
## Remediation Priority
1. [Most urgent fix — rationale]
2. [Next priority — rationale]
3. ...| Tool | Language | Purpose |
|---|---|---|
bandit | Python | Python code security scanning |
semgrep | Multi-language | Rule-based code scanning |
eslint-plugin-security | JavaScript | JS security rules |
npm audit / pip audit | JS / Python | Dependency vulnerability scanning |
trivy | Multi-language | Container and dependency scanning |
sqlmap | — | SQL injection detection |
OWASP ZAP | — | Web application dynamic scanning |
Note: This checklist is a supplementary review tool and does not replace professional penetration testing. For high-security systems, combine automated scanning + manual code audit + penetration testing.
af7d491
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.