Security Scanner Skill
This skill identifies security vulnerabilities, misconfigurations, and potential attack vectors in code. Use this whenever you need to audit code for security issues, review sensitive implementations, or ensure compliance with security best practices.
Security Analysis Scope
1. OWASP Top 10 Vulnerabilities
The most critical web application security risks:
| # | Vulnerability | Description |
|---|---|---|
| A01 | Broken Access Control | Unauthorized access to resources |
| A02 | Cryptographic Failures | Weak encryption, exposed sensitive data |
| A03 | Injection | SQL, NoSQL, OS, LDAP injection |
| A04 | Insecure Design | Missing security controls by design |
| A05 | Security Misconfiguration | Default configs, verbose errors |
| A06 | Vulnerable Components | Outdated dependencies with CVEs |
| A07 | Authentication Failures | Weak auth, credential stuffing |
| A08 | Data Integrity Failures | Insecure deserialization, CI/CD |
| A09 | Logging Failures | Insufficient logging/monitoring |
| A10 | SSRF | Server-Side Request Forgery |
2. CWE Categories
Common Weakness Enumeration patterns:
- CWE-79: Cross-Site Scripting (XSS)
- CWE-89: SQL Injection
- CWE-22: Path Traversal
- CWE-78: OS Command Injection
- CWE-352: Cross-Site Request Forgery (CSRF)
- CWE-434: Unrestricted File Upload
- CWE-502: Deserialization of Untrusted Data
- CWE-611: XML External Entity (XXE)
- CWE-798: Hardcoded Credentials
- CWE-918: Server-Side Request Forgery
3. Secret Detection
Exposed credentials and sensitive data:
- API keys and tokens
- Database passwords
- Private keys and certificates
- OAuth secrets
- AWS/GCP/Azure credentials
- JWT secrets
- Encryption keys
Security Scan Process
Step 1: Initial Assessment
- Identify the application type (web, API, mobile, etc.)
- Determine the technology stack
- Identify security-sensitive areas (auth, payments, data)
- Review the architecture for attack surface
- Check for existing security controls
Step 2: Vulnerability Analysis
Scan code systematically for:
Injection Vulnerabilities:
- SQL/NoSQL injection points
- Command injection
- LDAP injection
- XPath injection
- Template injection
Authentication Issues:
- Weak password policies
- Missing MFA
- Session management flaws
- Insecure password storage
- Credential exposure in logs
Authorization Flaws:
- Missing access controls
- Privilege escalation paths
- IDOR vulnerabilities
- Insecure direct object references
Data Protection:
- Unencrypted sensitive data
- Weak cryptographic algorithms
- Improper key management
- PII exposure
Step 3: Severity Classification
🔴 CRITICAL (CVSS 9.0-10.0)
- Remote code execution
- Authentication bypass
- SQL injection with data access
- Exposed production credentials
- Privilege escalation to admin
🟠 HIGH (CVSS 7.0-8.9)
- XSS with session hijacking potential
- CSRF on sensitive actions
- Broken access control
- Insecure deserialization
- SSRF with internal access
🟡 MEDIUM (CVSS 4.0-6.9)
- Information disclosure
- Missing security headers
- Verbose error messages
- Weak cryptographic practices
- Session fixation
🟢 LOW (CVSS 0.1-3.9)
- Missing best practices
- Non-sensitive information leak
- Deprecated functions
- Minor configuration issues
Vulnerability Patterns
SQL Injection
Vulnerable Code:
# ❌ VULNERABLE - Direct string concatenation
def get_user(username):
query = f"SELECT * FROM users WHERE username = '{username}'"
return db.execute(query)
# ❌ VULNERABLE - String formatting
def search_products(keyword):
query = "SELECT * FROM products WHERE name LIKE '%{}%'".format(keyword)
return db.execute(query)
Secure Code:
# ✅ SECURE - Parameterized queries
def get_user(username):
query = "SELECT * FROM users WHERE username = %s"
return db.execute(query, (username,))
# ✅ SECURE - ORM with proper escaping
def search_products(keyword):
return Product.query.filter(Product.name.ilike(f"%{keyword}%")).all()
# ✅ SECURE - SQLAlchemy text binding
from sqlalchemy import text
def get_user(username):
query = text("SELECT * FROM users WHERE username = :username")
return db.execute(query, {"username": username})
Cross-Site Scripting (XSS)
Vulnerable Code:
// ❌ VULNERABLE - Direct HTML insertion
function displayMessage(message) {
document.getElementById('output').innerHTML = message;
}
// ❌ VULNERABLE - Template without escaping
app.get('/profile', (req, res) => {
res.send(`<h1>Welcome, ${req.query.name}</h1>`);
});
Secure Code:
// ✅ SECURE - Text content (no HTML parsing)
function displayMessage(message) {
document.getElementById('output').textContent = message;
}
// ✅ SECURE - Proper escaping
import escape from 'escape-html';
app.get('/profile', (req, res) => {
res.send(`<h1>Welcome, ${escape(req.query.name)}</h1>`);
});
// ✅ SECURE - React auto-escapes by default
function Profile({ name }) {
return <h1>Welcome, {name}</h1>; // Safe - auto-escaped
}
// ⚠️ DANGEROUS - React dangerouslySetInnerHTML
function Profile({ htmlContent }) {
// Only use with sanitized content!
return <div dangerouslySetInnerHTML={{ __html: sanitize(htmlContent) }} />;
}
Command Injection
Vulnerable Code:
# ❌ VULNERABLE - Shell command with user input
import os
def ping_host(host):
os.system(f"ping -c 1 {host}") # host = "google.com; rm -rf /"
# ❌ VULNERABLE - subprocess with shell=True
import subprocess
def list_files(directory):
subprocess.call(f"ls -la {directory}", shell=True)
Secure Code:
# ✅ SECURE - Use subprocess with list arguments
import subprocess
import shlex
def ping_host(host):
# Validate input first
if not is_valid_hostname(host):
raise ValueError("Invalid hostname")
subprocess.run(["ping", "-c", "1", host], check=True)
# ✅ SECURE - Avoid shell entirely
def list_files(directory):
# Validate path
safe_path = os.path.realpath(directory)
if not safe_path.startswith(ALLOWED_BASE_PATH):
raise ValueError("Path traversal detected")
subprocess.run(["ls", "-la", safe_path], check=True)
# ✅ SECURE - Use Python libraries instead of shell
import pathlib
def list_files(directory):
path = pathlib.Path(directory).resolve()
if not str(path).startswith(str(ALLOWED_BASE_PATH)):
raise ValueError("Path traversal detected")
return list(path.iterdir())
Path Traversal
Vulnerable Code:
# ❌ VULNERABLE - Direct path concatenation
def read_file(filename):
with open(f"/var/uploads/{filename}", "r") as f:
return f.read() # filename = "../../../etc/passwd"
# ❌ VULNERABLE - Insufficient validation
def download(path):
if ".." in path:
raise ValueError("Invalid path") # Bypass: "..%2f" or "..\"
return send_file(f"/files/{path}")
Secure Code:
# ✅ SECURE - Resolve and verify path
import os
UPLOAD_DIR = "/var/uploads"
def read_file(filename):
# Resolve the full path
safe_path = os.path.realpath(os.path.join(UPLOAD_DIR, filename))
# Verify it's within allowed directory
if not safe_path.startswith(os.path.realpath(UPLOAD_DIR)):
raise ValueError("Path traversal detected")
with open(safe_path, "r") as f:
return f.read()
# ✅ SECURE - Use pathlib for cleaner handling
from pathlib import Path
def read_file(filename):
base = Path(UPLOAD_DIR).resolve()
target = (base / filename).resolve()
if not str(target).startswith(str(base)):
raise ValueError("Path traversal detected")
return target.read_text()
Insecure Authentication
Vulnerable Code:
# ❌ VULNERABLE - Plain text password storage
def create_user(username, password):
db.execute(
"INSERT INTO users (username, password) VALUES (%s, %s)",
(username, password) # Plain text!
)
# ❌ VULNERABLE - Weak hashing (MD5/SHA1)
import hashlib
def hash_password(password):
return hashlib.md5(password.encode()).hexdigest()
# ❌ VULNERABLE - Timing attack in comparison
def verify_password(stored, provided):
return stored == provided # Timing attack possible
Secure Code:
# ✅ SECURE - bcrypt with salt
import bcrypt
def hash_password(password: str) -> bytes:
salt = bcrypt.gensalt(rounds=12)
return bcrypt.hashpw(password.encode(), salt)
def verify_password(stored_hash: bytes, password: str) -> bool:
return bcrypt.checkpw(password.encode(), stored_hash)
# ✅ SECURE - Argon2 (recommended)
from argon2 import PasswordHasher
ph = PasswordHasher()
def hash_password(password: str) -> str:
return ph.hash(password)
def verify_password(stored_hash: str, password: str) -> bool:
try:
ph.verify(stored_hash, password)
return True
except Exception:
return False
# ✅ SECURE - Constant-time comparison
import hmac
def verify_token(stored, provided):
return hmac.compare_digest(stored, provided)
Insecure Session Management
Vulnerable Code:
# ❌ VULNERABLE - Predictable session ID
def create_session(user_id):
session_id = f"session_{user_id}_{int(time.time())}"
return session_id
# ❌ VULNERABLE - Session fixation
@app.route('/login', methods=['POST'])
def login():
if authenticate(request.form):
session['user'] = request.form['username']
# Session ID not regenerated!
return redirect('/dashboard')
# ❌ VULNERABLE - Missing secure flags
response.set_cookie('session', session_id) # No secure, httpOnly
Secure Code:
# ✅ SECURE - Cryptographic random session ID
import secrets
def create_session():
return secrets.token_urlsafe(32)
# ✅ SECURE - Regenerate session on login
from flask import session
@app.route('/login', methods=['POST'])
def login():
if authenticate(request.form):
session.clear() # Clear old session
session.regenerate() # New session ID
session['user'] = request.form['username']
return redirect('/dashboard')
# ✅ SECURE - Proper cookie flags
response.set_cookie(
'session',
session_id,
secure=True, # HTTPS only
httponly=True, # No JavaScript access
samesite='Lax', # CSRF protection
max_age=3600, # 1 hour expiry
path='/',
domain='.example.com'
)
CSRF Vulnerabilities
Vulnerable Code:
# ❌ VULNERABLE - No CSRF protection
@app.route('/transfer', methods=['POST'])
def transfer_money():
amount = request.form['amount']
to_account = request.form['to_account']
perform_transfer(amount, to_account)
return "Transfer complete"
Secure Code:
# ✅ SECURE - Flask-WTF CSRF protection
from flask_wtf import FlaskForm, CSRFProtect
from wtforms import StringField, DecimalField
csrf = CSRFProtect(app)
class TransferForm(FlaskForm):
amount = DecimalField('Amount')
to_account = StringField('To Account')
@app.route('/transfer', methods=['POST'])
def transfer_money():
form = TransferForm()
if form.validate_on_submit(): # Validates CSRF token
perform_transfer(form.amount.data, form.to_account.data)
return "Transfer complete"
return "Invalid request", 400
# ✅ SECURE - Manual CSRF token
@app.route('/transfer', methods=['POST'])
def transfer_money():
token = request.headers.get('X-CSRF-Token')
if not verify_csrf_token(token):
return "Invalid CSRF token", 403
# Process request...
Secret Exposure
Vulnerable Code:
# ❌ VULNERABLE - Hardcoded credentials
DATABASE_URL = "postgresql://admin:SuperSecret123@db.example.com/prod"
API_KEY = "sk_live_abc123def456ghi789"
JWT_SECRET = "my-super-secret-key"
# ❌ VULNERABLE - Secrets in logs
logger.info(f"Connecting with password: {password}")
logger.debug(f"API response: {response.text}") # May contain tokens
# ❌ VULNERABLE - Secrets in error messages
except Exception as e:
return f"Database connection failed: {connection_string}"
Secure Code:
# ✅ SECURE - Environment variables
import os
DATABASE_URL = os.environ["DATABASE_URL"]
API_KEY = os.environ["API_KEY"]
JWT_SECRET = os.environ["JWT_SECRET"]
# ✅ SECURE - Secret management service
from aws_secrets import get_secret
DATABASE_URL = get_secret("prod/database/url")
# ✅ SECURE - Sanitized logging
logger.info("Connecting to database...") # No credentials
logger.debug(f"API response status: {response.status_code}")
# ✅ SECURE - Generic error messages
except Exception as e:
logger.error(f"Database error: {e}") # Internal log only
return "Database connection failed" # Generic to user
Insecure Deserialization
Vulnerable Code:
# ❌ VULNERABLE - pickle with untrusted data
import pickle
def load_user_data(data):
return pickle.loads(base64.b64decode(data))
# ❌ VULNERABLE - yaml.load without safe loader
import yaml
def parse_config(config_str):
return yaml.load(config_str) # Can execute arbitrary code!
# ❌ VULNERABLE - eval with user input
def calculate(expression):
return eval(expression) # expression = "__import__('os').system('rm -rf /')"
Secure Code:
# ✅ SECURE - JSON for serialization
import json
def load_user_data(data):
return json.loads(data) # Safe - no code execution
# ✅ SECURE - yaml.safe_load
import yaml
def parse_config(config_str):
return yaml.safe_load(config_str)
# ✅ SECURE - Restricted evaluation
import ast
def calculate(expression):
# Only allow simple math expressions
tree = ast.parse(expression, mode='eval')
for node in ast.walk(tree):
if not isinstance(node, (ast.Expression, ast.BinOp, ast.Num,
ast.Add, ast.Sub, ast.Mult, ast.Div)):
raise ValueError("Invalid expression")
return eval(compile(tree, '<string>', 'eval'))
SSRF (Server-Side Request Forgery)
Vulnerable Code:
# ❌ VULNERABLE - No URL validation
import requests
@app.route('/fetch')
def fetch_url():
url = request.args.get('url')
response = requests.get(url) # url = "http://169.254.169.254/..."
return response.text
Secure Code:
# ✅ SECURE - URL validation and allowlist
from urllib.parse import urlparse
import ipaddress
ALLOWED_HOSTS = ['api.example.com', 'cdn.example.com']
BLOCKED_RANGES = [
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'), # AWS metadata
ipaddress.ip_network('127.0.0.0/8'),
]
def is_safe_url(url):
parsed = urlparse(url)
# Check protocol
if parsed.scheme not in ('http', 'https'):
return False
# Check against allowlist
if parsed.hostname not in ALLOWED_HOSTS:
return False
# Resolve and check IP
try:
ip = ipaddress.ip_address(socket.gethostbyname(parsed.hostname))
for blocked in BLOCKED_RANGES:
if ip in blocked:
return False
except Exception:
return False
return True
@app.route('/fetch')
def fetch_url():
url = request.args.get('url')
if not is_safe_url(url):
return "URL not allowed", 403
response = requests.get(url, timeout=5)
return response.text
Secret Detection Patterns
Common Secret Patterns
# AWS Access Key
AKIA[0-9A-Z]{16}
# AWS Secret Key
[A-Za-z0-9/+=]{40}
# GitHub Token
gh[pousr]_[A-Za-z0-9_]{36,255}
# Google API Key
AIza[0-9A-Za-z\-_]{35}
# Stripe Keys
sk_live_[0-9a-zA-Z]{24,}
pk_live_[0-9a-zA-Z]{24,}
# JWT
eyJ[A-Za-z0-9-_]+\.eyJ[A-Za-z0-9-_]+\.[A-Za-z0-9-_.+/]*
# Private Keys
-----BEGIN (RSA|DSA|EC|OPENSSH) PRIVATE KEY-----
# Generic Password in URL
[a-zA-Z]+://[^:]+:[^@]+@
# Slack Webhook
https://hooks\.slack\.com/services/T[A-Z0-9]+/B[A-Z0-9]+/[a-zA-Z0-9]+
Scanning Code for Secrets
import re
from pathlib import Path
SECRET_PATTERNS = {
'aws_access_key': r'AKIA[0-9A-Z]{16}',
'aws_secret_key': r'(?<![A-Za-z0-9/+=])[A-Za-z0-9/+=]{40}(?![A-Za-z0-9/+=])',
'github_token': r'gh[pousr]_[A-Za-z0-9_]{36,255}',
'google_api_key': r'AIza[0-9A-Za-z\-_]{35}',
'stripe_key': r'[sr]k_live_[0-9a-zA-Z]{24,}',
'jwt_token': r'eyJ[A-Za-z0-9-_]+\.eyJ[A-Za-z0-9-_]+\.[A-Za-z0-9-_.+/]*',
'private_key': r'-----BEGIN (RSA|DSA|EC|OPENSSH) PRIVATE KEY-----',
'password_in_url': r'[a-zA-Z]+://[^:]+:[^@]+@',
'generic_secret': r'(?i)(password|secret|token|apikey|api_key)\s*[=:]\s*["'][^"']+["']',
}
def scan_file(filepath: Path) -> list[dict]:
findings = []
content = filepath.read_text()
lines = content.split('\n')
for pattern_name, pattern in SECRET_PATTERNS.items():
for line_num, line in enumerate(lines, 1):
matches = re.finditer(pattern, line)
for match in matches:
findings.append({
'file': str(filepath),
'line': line_num,
'type': pattern_name,
'match': match.group()[:20] + '...', # Truncate
'severity': 'CRITICAL'
})
return findings
Security Headers Check
Required Headers
SECURITY_HEADERS = {
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block',
'Content-Security-Policy': "default-src 'self'",
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Permissions-Policy': 'geolocation=(), camera=(), microphone=()',
}
def check_security_headers(response):
findings = []
for header, expected in SECURITY_HEADERS.items():
actual = response.headers.get(header)
if not actual:
findings.append({
'header': header,
'status': 'MISSING',
'severity': 'MEDIUM',
'recommendation': f"Add header: {header}: {expected}"
})
elif actual != expected:
findings.append({
'header': header,
'status': 'WEAK',
'severity': 'LOW',
'actual': actual,
'expected': expected
})
return findings
Dependency Security Check
Known Vulnerable Packages
# Check against security advisories
npm audit
pip-audit
safety check
snyk test
Checking Dependencies
# Python - using pip-audit
import subprocess
import json
def check_dependencies():
result = subprocess.run(
['pip-audit', '--format', 'json'],
capture_output=True,
text=True
)
vulnerabilities = json.loads(result.stdout)
critical = []
for vuln in vulnerabilities:
if vuln['severity'] in ('CRITICAL', 'HIGH'):
critical.append({
'package': vuln['name'],
'version': vuln['version'],
'vulnerability': vuln['id'],
'severity': vuln['severity'],
'fix': vuln.get('fix_versions', ['Update required'])
})
return critical
Security Scan Checklist
Authentication & Authorization
- Passwords hashed with bcrypt/Argon2/scrypt
- No hardcoded credentials
- Session tokens cryptographically random
- Session regeneration on login
- Proper session expiration
- MFA available for sensitive actions
- Rate limiting on auth endpoints
- Account lockout after failed attempts
- Proper access control on all endpoints
- No privilege escalation paths
Input Validation
- All user input validated
- Parameterized queries for databases
- Output encoding for XSS prevention
- File upload restrictions (type, size)
- Path traversal prevention
- Command injection prevention
- XML/JSON parsing hardened
Data Protection
- Sensitive data encrypted at rest
- TLS 1.2+ for data in transit
- Proper key management
- PII minimization
- Secure backup practices
- Data retention policies
Security Configuration
- Debug mode disabled in production
- Verbose errors disabled
- Security headers configured
- CORS properly restricted
- Cookie flags set correctly
- No default credentials
Logging & Monitoring
- Security events logged
- No sensitive data in logs
- Log injection prevented
- Alerting configured
- Audit trail maintained
Dependencies
- No known vulnerable packages
- Dependencies regularly updated
- Minimal dependencies
- Dependency integrity verified
Output Format
When performing a security scan, provide:
# Security Scan Report
## Summary
| Severity | Count |
|----------|-------|
| 🔴 Critical | X |
| 🟠 High | Y |
| 🟡 Medium | Z |
| 🟢 Low | W |
## Executive Summary
[Brief overview of security posture and key findings]
---
## 🔴 Critical Findings
### Finding 1: [Title]
**Location:** `file.py:42`
**CWE:** CWE-89 (SQL Injection)
**CVSS:** 9.8
**Description:**
[Detailed description of the vulnerability]
**Vulnerable Code:**
```python
[Code snippet showing the issue]
Proof of Concept: [How this could be exploited]
Remediation:
[Code snippet showing the fix]
References:
- [OWASP Link]
- [CWE Link]
🟠 High Findings
[Same format as Critical]
🟡 Medium Findings
[Same format, can be more concise]
🟢 Low Findings
[Brief descriptions with recommendations]
Recommendations
Immediate Actions (24-48 hours)
- [Action 1]
- [Action 2]
Short-term (1-2 weeks)
- [Action 1]
- [Action 2]
Long-term (1-3 months)
- [Action 1]
- [Action 2]
Appendix
Tools Used
- [Tool 1]
- [Tool 2]
Scope
- [What was reviewed]
- [What was excluded]
## Notes
- Always prioritize findings by exploitability and impact
- Provide actionable remediation steps
- Consider the context (internal app vs public-facing)
- Check for security through obscurity (security theater)
- Verify fixes don't introduce new vulnerabilities
- Consider defense in depth - multiple layers of protection
- Remember: security is a process, not a destination