# Security Scanner

> 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.

- Skill: `mehdiozdemir/security-scanner` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mehdiozdemir/security-scanner`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mehdiozdemir/security-scanner/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: mehdiozdemir (https://skillmd.com/u/mehdiozdemir)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/mehdiozdemir/security-scanner

---


# 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
1. Identify the application type (web, API, mobile, etc.)
2. Determine the technology stack
3. Identify security-sensitive areas (auth, payments, data)
4. Review the architecture for attack surface
5. 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:**
```python
# ❌ 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:**
```python
# ✅ 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:**
```javascript
// ❌ 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:**
```javascript
// ✅ 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:**
```python
# ❌ 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:**
```python
# ✅ 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:**
```python
# ❌ 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:**
```python
# ✅ 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:**
```python
# ❌ 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:**
```python
# ✅ 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:**
```python
# ❌ 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:**
```python
# ✅ 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:**
```python
# ❌ 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:**
```python
# ✅ 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:**
```python
# ❌ 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:**
```python
# ✅ 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:**
```python
# ❌ 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:**
```python
# ✅ 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:**
```python
# ❌ 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:**
```python
# ✅ 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

```regex
# 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

```python
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

```python
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

```yaml
# Check against security advisories
npm audit
pip-audit
safety check
snyk test
```

### Checking Dependencies

```python
# 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:**
```python
[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)
1. [Action 1]
2. [Action 2]

### Short-term (1-2 weeks)
1. [Action 1]
2. [Action 2]

### Long-term (1-3 months)
1. [Action 1]
2. [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

