# SAST Security Plugin - Detailed Guide
> This file contains the detailed procedure and reference material extracted from `SKILL.md` for focused loading. The root skill defines activation, examples, safety constraints, and limitations.
## 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
## Instructions
1. Identify the languages, frameworks, and scope to scan.
2. Select SAST tools and configure rules for the codebase.
3. Run scans in CI or locally with reproducible settings.
4. Triage findings, prioritize by severity, and propose fixes.
## SAST Tool Selection
### Python: Bandit
```bash
# Installation & scan
pip install bandit
bandit -r . -f json -o bandit-report.json
bandit -r . -lll -iii -f json # HIGH severity, HIGH confidence (Bandit has no CRITICAL level)
```
**Configuration**: `bandit.yaml` (pass `-c bandit.yaml`; `.bandit` is the INI form)
```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**: `eslint.config.mjs` (JavaScript; add the project’s TypeScript parser/config separately).
```javascript
import security from 'eslint-plugin-security';
import noSecrets from 'eslint-plugin-no-secrets';
export default [
security.configs.recommended,
{ files: ['**/*.{js,mjs,cjs}'], plugins: { 'no-secrets': noSecrets },
rules: { 'no-secrets/no-secrets': 'error' } }
];
```
### Multi-Language: Semgrep
```bash
pip install semgrep
semgrep scan --config=auto --json --output=semgrep-report.json
semgrep scan --config=p/security-audit --json
semgrep scan --config=p/owasp-top-ten --json
semgrep ci # authenticated platform CI; confirm upload/privacy policy first
```
**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
{userInput}
// 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')
if not isinstance(file_name, str) or not file_name:
raise ValueError('Missing file name')
file_path = os.path.join(ALLOWED_DIR, file_name)
file_path = os.path.realpath(file_path)
if os.path.commonpath([os.path.realpath(ALLOWED_DIR), file_path]) != os.path.realpath(ALLOWED_DIR):
raise ValueError("Invalid file path")
with open(file_path, 'r') as f:
content = f.read()
# This resolve-then-open form assumes an immutable trusted directory. For attacker-
# writable trees, use descriptor-relative no-follow traversal to prevent symlink races.
```
### 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
import ipaddress
validated_ip = str(ipaddress.ip_address(user_input))
subprocess.run(['ping', '-c', '4', validated_ip], check=True, timeout=10)
# Array arguments avoid shell parsing; input validation separately prevents
# option injection. shlex.quote is shell escaping, not 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 }));
```
## Scanner implementation boundary
The earlier all-language wrapper was incomplete (missing Bandit/ESLint methods), swallowed process/parse failures and mapped Semgrep ERROR/WARNING/INFO to unsupported severity weights. Use each tool's documented CLI and native report instead. Record tool version, exact reviewed ruleset, target, exclusions, exit status and parse errors separately from findings. Zero findings with a failed, partial or skipped scan is **not** a pass. Do not invent a 0–100 security score from counts or claim compliance from a static scan. Custom rules above are heuristic examples requiring positive/negative fixtures; a joined path is not automatically safe and a sink match is not confirmed exploitability.
## CI/CD Integration
### GitHub Actions
```yaml
name: SAST Scan
on:
pull_request:
branches: [main]
jobs:
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
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
semgrep scan --config=auto --error --json --output=semgrep.json
- name: Upload reports
if: always()
uses: actions/upload-artifact@v4
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
- semgrep scan --config=auto --error --json --output=semgrep.json
artifacts:
when: always
paths: [bandit.json, semgrep.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
Bandit JSON is not GitLab’s SAST report schema; the example stores raw artifacts only. Execute separate scan steps/jobs if both reports are required after one fails. Pin approved tool/ruleset/action versions before production use and scan untrusted source without running its build scripts or privileged hooks. Framework snippets are partial settings, not complete middleware stacks; CORS is not authorization. Referenced security-*.md files are not bundled.
Primary references: [Bandit CLI](https://bandit.readthedocs.io/en/latest/man/bandit.html), [ESLint Security](https://github.com/eslint-community/eslint-plugin-security), [Semgrep CLI](https://docs.semgrep.dev/cli-reference).