Initial commit
This commit is contained in:
473
commands/security-sast.md
Normal file
473
commands/security-sast.md
Normal file
@@ -0,0 +1,473 @@
|
||||
---
|
||||
description: Static Application Security Testing (SAST) for code vulnerability analysis across multiple languages and frameworks
|
||||
globs: ['**/*.py', '**/*.js', '**/*.ts', '**/*.java', '**/*.rb', '**/*.go', '**/*.rs', '**/*.php']
|
||||
keywords: [sast, static analysis, code security, vulnerability scanning, bandit, semgrep, eslint, sonarqube, codeql, security patterns, code review, ast analysis]
|
||||
---
|
||||
|
||||
# SAST Security Plugin
|
||||
|
||||
Static Application Security Testing (SAST) for comprehensive code vulnerability detection across multiple languages, frameworks, and security patterns.
|
||||
|
||||
## Capabilities
|
||||
|
||||
- **Multi-language SAST**: Python, JavaScript/TypeScript, Java, Ruby, PHP, Go, Rust
|
||||
- **Tool integration**: Bandit, Semgrep, ESLint Security, SonarQube, CodeQL, PMD, SpotBugs, Brakeman, gosec, cargo-clippy
|
||||
- **Vulnerability patterns**: SQL injection, XSS, hardcoded secrets, path traversal, IDOR, CSRF, insecure deserialization
|
||||
- **Framework analysis**: Django, Flask, React, Express, Spring Boot, Rails, Laravel
|
||||
- **Custom rule authoring**: Semgrep pattern development for organization-specific security policies
|
||||
|
||||
## When to Use This Tool
|
||||
|
||||
Use for code review security analysis, injection vulnerabilities, hardcoded secrets, framework-specific patterns, custom security policy enforcement, pre-deployment validation, legacy code assessment, and compliance (OWASP, PCI-DSS, SOC2).
|
||||
|
||||
**Specialized tools**: Use `security-secrets.md` for advanced credential scanning, `security-owasp.md` for Top 10 mapping, `security-api.md` for REST/GraphQL endpoints.
|
||||
|
||||
## SAST Tool Selection
|
||||
|
||||
### Python: Bandit
|
||||
|
||||
```bash
|
||||
# Installation & scan
|
||||
pip install bandit
|
||||
bandit -r . -f json -o bandit-report.json
|
||||
bandit -r . -ll -ii -f json # High/Critical only
|
||||
```
|
||||
|
||||
**Configuration**: `.bandit`
|
||||
```yaml
|
||||
exclude_dirs: ['/tests/', '/venv/', '/.tox/', '/build/']
|
||||
tests: [B201, B301, B302, B303, B304, B305, B307, B308, B312, B323, B324, B501, B502, B506, B602, B608]
|
||||
skips: [B101]
|
||||
```
|
||||
|
||||
### JavaScript/TypeScript: ESLint Security
|
||||
|
||||
```bash
|
||||
npm install --save-dev eslint @eslint/plugin-security eslint-plugin-no-secrets
|
||||
eslint . --ext .js,.jsx,.ts,.tsx --format json > eslint-security.json
|
||||
```
|
||||
|
||||
**Configuration**: `.eslintrc-security.json`
|
||||
```json
|
||||
{
|
||||
"plugins": ["@eslint/plugin-security", "eslint-plugin-no-secrets"],
|
||||
"extends": ["plugin:security/recommended"],
|
||||
"rules": {
|
||||
"security/detect-object-injection": "error",
|
||||
"security/detect-non-literal-fs-filename": "error",
|
||||
"security/detect-eval-with-expression": "error",
|
||||
"security/detect-pseudo-random-prng": "error",
|
||||
"no-secrets/no-secrets": "error"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Multi-Language: Semgrep
|
||||
|
||||
```bash
|
||||
pip install semgrep
|
||||
semgrep --config=auto --json --output=semgrep-report.json
|
||||
semgrep --config=p/security-audit --json
|
||||
semgrep --config=p/owasp-top-ten --json
|
||||
semgrep ci --config=auto # CI mode
|
||||
```
|
||||
|
||||
**Custom Rules**: `.semgrep.yml`
|
||||
```yaml
|
||||
rules:
|
||||
- id: sql-injection-format-string
|
||||
pattern: cursor.execute("... %s ..." % $VAR)
|
||||
message: SQL injection via string formatting
|
||||
severity: ERROR
|
||||
languages: [python]
|
||||
metadata:
|
||||
cwe: "CWE-89"
|
||||
owasp: "A03:2021-Injection"
|
||||
|
||||
- id: dangerous-innerHTML
|
||||
pattern: $ELEM.innerHTML = $VAR
|
||||
message: XSS via innerHTML assignment
|
||||
severity: ERROR
|
||||
languages: [javascript, typescript]
|
||||
metadata:
|
||||
cwe: "CWE-79"
|
||||
|
||||
- id: hardcoded-aws-credentials
|
||||
patterns:
|
||||
- pattern: $KEY = "AKIA..."
|
||||
- metavariable-regex:
|
||||
metavariable: $KEY
|
||||
regex: "(aws_access_key_id|AWS_ACCESS_KEY_ID)"
|
||||
message: Hardcoded AWS credentials detected
|
||||
severity: ERROR
|
||||
languages: [python, javascript, java]
|
||||
|
||||
- id: path-traversal-open
|
||||
patterns:
|
||||
- pattern: open($PATH, ...)
|
||||
- pattern-not: open(os.path.join(SAFE_DIR, ...), ...)
|
||||
- metavariable-pattern:
|
||||
metavariable: $PATH
|
||||
patterns:
|
||||
- pattern: $REQ.get(...)
|
||||
message: Path traversal via user input
|
||||
severity: ERROR
|
||||
languages: [python]
|
||||
|
||||
- id: command-injection
|
||||
patterns:
|
||||
- pattern-either:
|
||||
- pattern: os.system($CMD)
|
||||
- pattern: subprocess.call($CMD, shell=True)
|
||||
- metavariable-pattern:
|
||||
metavariable: $CMD
|
||||
patterns:
|
||||
- pattern-either:
|
||||
- pattern: $X + $Y
|
||||
- pattern: f"...{$VAR}..."
|
||||
message: Command injection via shell=True
|
||||
severity: ERROR
|
||||
languages: [python]
|
||||
```
|
||||
|
||||
### Other Language Tools
|
||||
|
||||
**Java**: `mvn spotbugs:check`
|
||||
**Ruby**: `brakeman -o report.json -f json`
|
||||
**Go**: `gosec -fmt=json -out=gosec.json ./...`
|
||||
**Rust**: `cargo clippy -- -W clippy::unwrap_used`
|
||||
|
||||
## Vulnerability Patterns
|
||||
|
||||
### SQL Injection
|
||||
|
||||
**VULNERABLE**: String formatting/concatenation with user input in SQL queries
|
||||
|
||||
**SECURE**:
|
||||
```python
|
||||
# Parameterized queries
|
||||
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
|
||||
User.objects.filter(id=user_id) # ORM
|
||||
```
|
||||
|
||||
### Cross-Site Scripting (XSS)
|
||||
|
||||
**VULNERABLE**: Direct HTML manipulation with unsanitized user input (innerHTML, outerHTML, document.write)
|
||||
|
||||
**SECURE**:
|
||||
```javascript
|
||||
// Use textContent for plain text
|
||||
element.textContent = userInput;
|
||||
|
||||
// React auto-escapes
|
||||
<div>{userInput}</div>
|
||||
|
||||
// Sanitize when HTML required
|
||||
import DOMPurify from 'dompurify';
|
||||
element.innerHTML = DOMPurify.sanitize(userInput);
|
||||
```
|
||||
|
||||
### Hardcoded Secrets
|
||||
|
||||
**VULNERABLE**: Hardcoded API keys, passwords, tokens in source code
|
||||
|
||||
**SECURE**:
|
||||
```python
|
||||
import os
|
||||
API_KEY = os.environ.get('API_KEY')
|
||||
PASSWORD = os.getenv('DB_PASSWORD')
|
||||
```
|
||||
|
||||
### Path Traversal
|
||||
|
||||
**VULNERABLE**: Opening files using unsanitized user input
|
||||
|
||||
**SECURE**:
|
||||
```python
|
||||
import os
|
||||
ALLOWED_DIR = '/var/www/uploads'
|
||||
file_name = request.args.get('file')
|
||||
file_path = os.path.join(ALLOWED_DIR, file_name)
|
||||
file_path = os.path.realpath(file_path)
|
||||
if not file_path.startswith(os.path.realpath(ALLOWED_DIR)):
|
||||
raise ValueError("Invalid file path")
|
||||
with open(file_path, 'r') as f:
|
||||
content = f.read()
|
||||
```
|
||||
|
||||
### Insecure Deserialization
|
||||
|
||||
**VULNERABLE**: pickle.loads(), yaml.load() with untrusted data
|
||||
|
||||
**SECURE**:
|
||||
```python
|
||||
import json
|
||||
data = json.loads(user_input) # SECURE
|
||||
import yaml
|
||||
config = yaml.safe_load(user_input) # SECURE
|
||||
```
|
||||
|
||||
### Command Injection
|
||||
|
||||
**VULNERABLE**: os.system() or subprocess with shell=True and user input
|
||||
|
||||
**SECURE**:
|
||||
```python
|
||||
subprocess.run(['ping', '-c', '4', user_input]) # Array args
|
||||
import shlex
|
||||
safe_input = shlex.quote(user_input) # Input validation
|
||||
```
|
||||
|
||||
### Insecure Random
|
||||
|
||||
**VULNERABLE**: random module for security-critical operations
|
||||
|
||||
**SECURE**:
|
||||
```python
|
||||
import secrets
|
||||
token = secrets.token_hex(16)
|
||||
session_id = secrets.token_urlsafe(32)
|
||||
```
|
||||
|
||||
## Framework Security
|
||||
|
||||
### Django
|
||||
|
||||
**VULNERABLE**: @csrf_exempt, DEBUG=True, weak SECRET_KEY, missing security middleware
|
||||
|
||||
**SECURE**:
|
||||
```python
|
||||
# settings.py
|
||||
DEBUG = False
|
||||
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
SECURE_SSL_REDIRECT = True
|
||||
SESSION_COOKIE_SECURE = True
|
||||
CSRF_COOKIE_SECURE = True
|
||||
X_FRAME_OPTIONS = 'DENY'
|
||||
```
|
||||
|
||||
### Flask
|
||||
|
||||
**VULNERABLE**: debug=True, weak secret_key, CORS wildcard
|
||||
|
||||
**SECURE**:
|
||||
```python
|
||||
import os
|
||||
from flask_talisman import Talisman
|
||||
|
||||
app.secret_key = os.environ.get('FLASK_SECRET_KEY')
|
||||
Talisman(app, force_https=True)
|
||||
CORS(app, origins=['https://example.com'])
|
||||
```
|
||||
|
||||
### Express.js
|
||||
|
||||
**VULNERABLE**: Missing helmet, CORS wildcard, no rate limiting
|
||||
|
||||
**SECURE**:
|
||||
```javascript
|
||||
const helmet = require('helmet');
|
||||
const rateLimit = require('express-rate-limit');
|
||||
|
||||
app.use(helmet());
|
||||
app.use(cors({ origin: 'https://example.com' }));
|
||||
app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));
|
||||
```
|
||||
|
||||
## Multi-Language Scanner Implementation
|
||||
|
||||
```python
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Any
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
|
||||
@dataclass
|
||||
class SASTFinding:
|
||||
tool: str
|
||||
severity: str
|
||||
category: str
|
||||
title: str
|
||||
description: str
|
||||
file_path: str
|
||||
line_number: int
|
||||
cwe: str
|
||||
owasp: str
|
||||
confidence: str
|
||||
|
||||
class MultiLanguageSASTScanner:
|
||||
def __init__(self, project_path: str):
|
||||
self.project_path = Path(project_path)
|
||||
self.findings: List[SASTFinding] = []
|
||||
|
||||
def detect_languages(self) -> List[str]:
|
||||
"""Auto-detect languages"""
|
||||
languages = []
|
||||
indicators = {
|
||||
'python': ['*.py', 'requirements.txt'],
|
||||
'javascript': ['*.js', 'package.json'],
|
||||
'typescript': ['*.ts', 'tsconfig.json'],
|
||||
'java': ['*.java', 'pom.xml'],
|
||||
'ruby': ['*.rb', 'Gemfile'],
|
||||
'go': ['*.go', 'go.mod'],
|
||||
'rust': ['*.rs', 'Cargo.toml'],
|
||||
}
|
||||
for lang, patterns in indicators.items():
|
||||
for pattern in patterns:
|
||||
if list(self.project_path.glob(f'**/{pattern}')):
|
||||
languages.append(lang)
|
||||
break
|
||||
return languages
|
||||
|
||||
def run_comprehensive_sast(self) -> Dict[str, Any]:
|
||||
"""Execute all applicable SAST tools"""
|
||||
languages = self.detect_languages()
|
||||
|
||||
scan_results = {
|
||||
'timestamp': datetime.now().isoformat(),
|
||||
'languages': languages,
|
||||
'tools_executed': [],
|
||||
'findings': []
|
||||
}
|
||||
|
||||
self.run_semgrep_scan()
|
||||
scan_results['tools_executed'].append('semgrep')
|
||||
|
||||
if 'python' in languages:
|
||||
self.run_bandit_scan()
|
||||
scan_results['tools_executed'].append('bandit')
|
||||
if 'javascript' in languages or 'typescript' in languages:
|
||||
self.run_eslint_security_scan()
|
||||
scan_results['tools_executed'].append('eslint-security')
|
||||
|
||||
scan_results['findings'] = [vars(f) for f in self.findings]
|
||||
scan_results['summary'] = self.generate_summary()
|
||||
return scan_results
|
||||
|
||||
def run_semgrep_scan(self):
|
||||
"""Run Semgrep"""
|
||||
for ruleset in ['auto', 'p/security-audit', 'p/owasp-top-ten']:
|
||||
try:
|
||||
result = subprocess.run([
|
||||
'semgrep', '--config', ruleset, '--json', '--quiet',
|
||||
str(self.project_path)
|
||||
], capture_output=True, text=True, timeout=300)
|
||||
|
||||
if result.stdout:
|
||||
data = json.loads(result.stdout)
|
||||
for f in data.get('results', []):
|
||||
self.findings.append(SASTFinding(
|
||||
tool='semgrep',
|
||||
severity=f.get('extra', {}).get('severity', 'MEDIUM').upper(),
|
||||
category='sast',
|
||||
title=f.get('check_id', ''),
|
||||
description=f.get('extra', {}).get('message', ''),
|
||||
file_path=f.get('path', ''),
|
||||
line_number=f.get('start', {}).get('line', 0),
|
||||
cwe=f.get('extra', {}).get('metadata', {}).get('cwe', ''),
|
||||
owasp=f.get('extra', {}).get('metadata', {}).get('owasp', ''),
|
||||
confidence=f.get('extra', {}).get('metadata', {}).get('confidence', 'MEDIUM')
|
||||
))
|
||||
except Exception as e:
|
||||
print(f"Semgrep {ruleset} failed: {e}")
|
||||
|
||||
def generate_summary(self) -> Dict[str, Any]:
|
||||
"""Generate statistics"""
|
||||
severity_counts = {'CRITICAL': 0, 'HIGH': 0, 'MEDIUM': 0, 'LOW': 0}
|
||||
for f in self.findings:
|
||||
severity_counts[f.severity] = severity_counts.get(f.severity, 0) + 1
|
||||
|
||||
return {
|
||||
'total_findings': len(self.findings),
|
||||
'severity_breakdown': severity_counts,
|
||||
'risk_score': self.calculate_risk_score(severity_counts)
|
||||
}
|
||||
|
||||
def calculate_risk_score(self, severity_counts: Dict[str, int]) -> int:
|
||||
"""Risk score 0-100"""
|
||||
weights = {'CRITICAL': 10, 'HIGH': 7, 'MEDIUM': 4, 'LOW': 1}
|
||||
total = sum(weights[s] * c for s, c in severity_counts.items())
|
||||
return min(100, int((total / 50) * 100))
|
||||
```
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
```yaml
|
||||
name: SAST Scan
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
sast:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install tools
|
||||
run: |
|
||||
pip install bandit semgrep
|
||||
npm install -g eslint @eslint/plugin-security
|
||||
|
||||
- name: Run scans
|
||||
run: |
|
||||
bandit -r . -f json -o bandit.json || true
|
||||
semgrep --config=auto --json --output=semgrep.json || true
|
||||
|
||||
- name: Upload reports
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: sast-reports
|
||||
path: |
|
||||
bandit.json
|
||||
semgrep.json
|
||||
```
|
||||
|
||||
### GitLab CI
|
||||
|
||||
```yaml
|
||||
sast:
|
||||
stage: test
|
||||
image: python:3.11
|
||||
script:
|
||||
- pip install bandit semgrep
|
||||
- bandit -r . -f json -o bandit.json || true
|
||||
- semgrep --config=auto --json --output=semgrep.json || true
|
||||
artifacts:
|
||||
reports:
|
||||
sast: bandit.json
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Run early and often** - Pre-commit hooks and CI/CD
|
||||
2. **Combine multiple tools** - Different tools catch different vulnerabilities
|
||||
3. **Tune false positives** - Configure exclusions and thresholds
|
||||
4. **Prioritize findings** - Focus on CRITICAL/HIGH first
|
||||
5. **Framework-aware scanning** - Use specific rulesets
|
||||
6. **Custom rules** - Organization-specific patterns
|
||||
7. **Developer training** - Secure coding practices
|
||||
8. **Incremental remediation** - Fix gradually
|
||||
9. **Baseline management** - Track known issues
|
||||
10. **Regular updates** - Keep tools current
|
||||
|
||||
## Related Tools
|
||||
|
||||
- **security-secrets.md** - Advanced credential detection
|
||||
- **security-owasp.md** - OWASP Top 10 assessment
|
||||
- **security-api.md** - API security testing
|
||||
- **security-scan.md** - Comprehensive security scanning
|
||||
Reference in New Issue
Block a user