| name | security-scanner |
| description | Analyzes code for security vulnerabilities, misconfigurations, and potential attack vectors. Use when auditing codebases for OWASP Top 10 vulnerabilities, reviewing authentication/authorization implementations, checking for secret exposure, validating cryptographic practices, or performing security-focused code reviews. |
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:
def get_user(username):
query = f"SELECT * FROM users WHERE username = '{username}'"
return db.execute(query)
def search_products(keyword):
query = "SELECT * FROM products WHERE name LIKE '%{}%'".format(keyword)
return db.execute(query)
Secure Code:
def get_user(username):
query = "SELECT * FROM users WHERE username = %s"
return db.execute(query, (username,))
def search_products(keyword):
return Product.query.filter(Product.name.ilike(f"%{keyword}%")).all()
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:
function displayMessage(message) {
document.getElementById('output').innerHTML = message;
}
app.get('/profile', (req, res) => {
res.send(`<h1>Welcome, ${req.query.name}</h1>`);
});
Secure Code:
function displayMessage(message) {
document.getElementById('output').textContent = message;
}
import escape from 'escape-html';
app.get('/profile', (req, res) => {
res.send(`<h1>Welcome, ${escape(req.query.name)}</h1>`);
});
function Profile({ name }) {
return <h1>Welcome, {name}</h1>;
}
function Profile({ htmlContent }) {
return <div dangerouslySetInnerHTML={{ __html: sanitize(htmlContent) }} />;
}
Command Injection
Vulnerable Code:
import os
def ping_host(host):
os.system(f"ping -c 1 {host}")
import subprocess
def list_files(directory):
subprocess.call(f"ls -la {directory}", shell=True)
Secure Code:
import subprocess
import shlex
def ping_host(host):
if not is_valid_hostname(host):
raise ValueError("Invalid hostname")
subprocess.run(["ping", "-c", "1", host], check=True)
def list_files(directory):
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)
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:
def read_file(filename):
with open(f"/var/uploads/{filename}", "r") as f:
return f.read()
def download(path):
if ".." in path:
raise ValueError("Invalid path")
return send_file(f"/files/{path}")
Secure Code:
import os
UPLOAD_DIR = "/var/uploads"
def read_file(filename):
safe_path = os.path.realpath(os.path.join(UPLOAD_DIR, filename))
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()
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:
def create_user(username, password):
db.execute(
"INSERT INTO users (username, password) VALUES (%s, %s)",
(username, password)
)
import hashlib
def hash_password(password):
return hashlib.md5(password.encode()).hexdigest()
def verify_password(stored, provided):
return stored == provided
Secure Code:
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)
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
import hmac
def verify_token(stored, provided):
return hmac.compare_digest(stored, provided)
Insecure Session Management
Vulnerable Code:
def create_session(user_id):
session_id = f"session_{user_id}_{int(time.time())}"
return session_id
@app.route('/login', methods=['POST'])
def login():
if authenticate(request.form):
session['user'] = request.form['username']
return redirect('/dashboard')
response.set_cookie('session', session_id)
Secure Code:
import secrets
def create_session():
return secrets.token_urlsafe(32)
from flask import session
@app.route('/login', methods=['POST'])
def login():
if authenticate(request.form):
session.clear()
session.regenerate()
session['user'] = request.form['username']
return redirect('/dashboard')
response.set_cookie(
'session',
session_id,
secure=True,
httponly=True,
samesite='Lax',
max_age=3600,
path='/',
domain='.example.com'
)
CSRF Vulnerabilities
Vulnerable Code:
@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:
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():
perform_transfer(form.amount.data, form.to_account.data)
return "Transfer complete"
return "Invalid request", 400
@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
Secret Exposure
Vulnerable Code:
DATABASE_URL = "postgresql://admin:SuperSecret123@db.example.com/prod"
API_KEY = "sk_live_abc123def456ghi789"
JWT_SECRET = "my-super-secret-key"
logger.info(f"Connecting with password: {password}")
logger.debug(f"API response: {response.text}")
except Exception as e:
return f"Database connection failed: {connection_string}"
Secure Code:
import os
DATABASE_URL = os.environ["DATABASE_URL"]
API_KEY = os.environ["API_KEY"]
JWT_SECRET = os.environ["JWT_SECRET"]
from aws_secrets import get_secret
DATABASE_URL = get_secret("prod/database/url")
logger.info("Connecting to database...")
logger.debug(f"API response status: {response.status_code}")
except Exception as e:
logger.error(f"Database error: {e}")
return "Database connection failed"
Insecure Deserialization
Vulnerable Code:
import pickle
def load_user_data(data):
return pickle.loads(base64.b64decode(data))
import yaml
def parse_config(config_str):
return yaml.load(config_str)
def calculate(expression):
return eval(expression)
Secure Code:
import json
def load_user_data(data):
return json.loads(data)
import yaml
def parse_config(config_str):
return yaml.safe_load(config_str)
import ast
def calculate(expression):
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:
import requests
@app.route('/fetch')
def fetch_url():
url = request.args.get('url')
response = requests.get(url)
return response.text
Secure Code:
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'),
ipaddress.ip_network('127.0.0.0/8'),
]
def is_safe_url(url):
parsed = urlparse(url)
if parsed.scheme not in ('http', 'https'):
return False
if parsed.hostname not in ALLOWED_HOSTS:
return False
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] + '...',
'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
npm audit
pip-audit
safety check
snyk test
Checking Dependencies
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
Input Validation
Data Protection
Security Configuration
Logging & Monitoring
Dependencies
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:
🟠 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
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