mirror of
https://github.com/github/awesome-copilot.git
synced 2026-04-13 03:35:55 +00:00
feat: add security-review skill for AI-powered codebase vulnerability scanning (#1211)
* feat: add security-review skill for AI-powered codebase vulnerability scanning * chore: regenerate README tables * fix: address Copilot review comments on reference files
This commit is contained in:
221
skills/security-review/references/language-patterns.md
Normal file
221
skills/security-review/references/language-patterns.md
Normal file
@@ -0,0 +1,221 @@
|
||||
# Language-Specific Vulnerability Patterns
|
||||
|
||||
Load the relevant section during Step 1 (Scope Resolution) after identifying languages.
|
||||
|
||||
---
|
||||
|
||||
## JavaScript / TypeScript (Node.js, React, Next.js, Express)
|
||||
|
||||
### Critical APIs/calls to flag
|
||||
```js
|
||||
eval() // arbitrary code execution
|
||||
Function('return ...') // same as eval
|
||||
child_process.exec() // command injection if user input reaches it
|
||||
fs.readFile // path traversal if user controls path
|
||||
fs.writeFile // path traversal if user controls path
|
||||
```
|
||||
|
||||
### Express.js specific
|
||||
```js
|
||||
// Missing helmet (security headers)
|
||||
const app = express()
|
||||
// Should have: app.use(helmet())
|
||||
|
||||
// Body size limits missing (DoS)
|
||||
app.use(express.json())
|
||||
// Should have: app.use(express.json({ limit: '10kb' }))
|
||||
|
||||
// CORS misconfiguration
|
||||
app.use(cors({ origin: '*' })) // too permissive
|
||||
app.use(cors({ origin: req.headers.origin })) // reflects any origin
|
||||
|
||||
// Trust proxy without validation
|
||||
app.set('trust proxy', true) // only safe behind known proxy
|
||||
```
|
||||
|
||||
### React specific
|
||||
```jsx
|
||||
<div dangerouslySetInnerHTML={{ __html: userContent }} /> // XSS
|
||||
<a href={userUrl}>link</a> // javascript: URL injection
|
||||
```
|
||||
|
||||
### Next.js specific
|
||||
```js
|
||||
// Server Actions without auth
|
||||
export async function deleteUser(id) { // missing: auth check
|
||||
await db.users.delete(id)
|
||||
}
|
||||
|
||||
// API Routes missing method validation
|
||||
export default function handler(req, res) {
|
||||
// Should check: if (req.method !== 'POST') return res.status(405)
|
||||
doSensitiveAction()
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Python (Django, Flask, FastAPI)
|
||||
|
||||
### Django specific
|
||||
```python
|
||||
# Raw SQL
|
||||
User.objects.raw(f"SELECT * FROM users WHERE name = '{name}'") # SQLi
|
||||
|
||||
# Missing CSRF
|
||||
@csrf_exempt # Only OK for APIs with token auth
|
||||
|
||||
# Debug mode in production
|
||||
DEBUG = True # in settings.py — exposes stack traces
|
||||
|
||||
# SECRET_KEY
|
||||
SECRET_KEY = 'django-insecure-...' # must be changed for production
|
||||
|
||||
# ALLOWED_HOSTS
|
||||
ALLOWED_HOSTS = ['*'] # too permissive
|
||||
```
|
||||
|
||||
### Flask specific
|
||||
```python
|
||||
# Debug mode
|
||||
app.run(debug=True) # never in production
|
||||
|
||||
# Secret key
|
||||
app.secret_key = 'dev' # weak
|
||||
|
||||
# eval/exec with user input
|
||||
eval(request.args.get('expr'))
|
||||
|
||||
# render_template_string with user input (SSTI)
|
||||
render_template_string(f"Hello {name}") # Server-Side Template Injection
|
||||
```
|
||||
|
||||
### FastAPI specific
|
||||
```python
|
||||
# Missing auth dependency
|
||||
@app.delete("/users/{user_id}") # No Depends(get_current_user)
|
||||
async def delete_user(user_id: int):
|
||||
...
|
||||
|
||||
# Arbitrary file read
|
||||
@app.get("/files/{filename}")
|
||||
async def read_file(filename: str):
|
||||
return FileResponse(f"uploads/{filename}") # path traversal
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Java (Spring Boot)
|
||||
|
||||
### Spring Boot specific
|
||||
```java
|
||||
// SQL Injection
|
||||
String query = "SELECT * FROM users WHERE name = '" + name + "'";
|
||||
jdbcTemplate.query(query, ...);
|
||||
|
||||
// XXE
|
||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
// Missing: dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true)
|
||||
|
||||
// Deserialization
|
||||
ObjectInputStream ois = new ObjectInputStream(inputStream);
|
||||
Object obj = ois.readObject(); // only safe with allowlist
|
||||
|
||||
// Spring Security — permitAll on sensitive endpoint
|
||||
.antMatchers("/admin/**").permitAll()
|
||||
|
||||
// Actuator endpoints exposed
|
||||
management.endpoints.web.exposure.include=* # in application.properties
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PHP
|
||||
|
||||
```php
|
||||
// Direct user input in queries
|
||||
$result = mysql_query("SELECT * FROM users WHERE id = " . $_GET['id']);
|
||||
|
||||
// File inclusion
|
||||
include($_GET['page'] . ".php"); // local/remote file inclusion
|
||||
|
||||
// eval
|
||||
eval($_POST['code']);
|
||||
|
||||
// extract() with user input
|
||||
extract($_POST); // overwrites any variable
|
||||
|
||||
// Loose comparison
|
||||
if ($password == "admin") {} // use === instead
|
||||
|
||||
// Unserialize
|
||||
unserialize($_COOKIE['data']); // remote code execution
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Go
|
||||
|
||||
```go
|
||||
// Command injection
|
||||
exec.Command("sh", "-c", userInput)
|
||||
|
||||
// SQL injection
|
||||
db.Query("SELECT * FROM users WHERE name = '" + name + "'")
|
||||
|
||||
// Path traversal
|
||||
filePath := filepath.Join("/uploads/", userInput) // sanitize userInput first
|
||||
|
||||
// Insecure TLS
|
||||
http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
|
||||
|
||||
// Goroutine leak / missing context cancellation
|
||||
go func() {
|
||||
// No done channel or context
|
||||
for { ... }
|
||||
}()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Ruby on Rails
|
||||
|
||||
```ruby
|
||||
# SQL injection (safe alternatives use placeholders)
|
||||
User.where("name = '#{params[:name]}'") # VULNERABLE
|
||||
User.where("name = ?", params[:name]) # SAFE
|
||||
|
||||
# Mass assignment without strong params
|
||||
@user.update(params[:user]) # should be params.require(:user).permit(...)
|
||||
|
||||
# eval / send with user input
|
||||
eval(params[:code])
|
||||
send(params[:method]) # arbitrary method call
|
||||
|
||||
# Redirect to user-supplied URL (open redirect)
|
||||
redirect_to params[:url]
|
||||
|
||||
# YAML.load (allows arbitrary object creation)
|
||||
YAML.load(user_input) # use YAML.safe_load instead
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rust
|
||||
|
||||
```rust
|
||||
// Unsafe blocks — flag for manual review
|
||||
unsafe {
|
||||
// Reason for unsafety should be documented
|
||||
}
|
||||
|
||||
// Integer overflow (debug builds panic, release silently wraps)
|
||||
let result = a + b; // use checked_add/saturating_add for financial math
|
||||
|
||||
// Unwrap/expect in production code (panics on None/Err)
|
||||
let value = option.unwrap(); // prefer ? or match
|
||||
|
||||
// Deserializing arbitrary types
|
||||
serde_json::from_str::<serde_json::Value>(&user_input) // generally safe
|
||||
// But: bincode::deserialize from untrusted input — can be exploited
|
||||
```
|
||||
194
skills/security-review/references/report-format.md
Normal file
194
skills/security-review/references/report-format.md
Normal file
@@ -0,0 +1,194 @@
|
||||
# Security Report Format
|
||||
|
||||
Use this template for all `/security-review` output. Generated during Step 7.
|
||||
|
||||
---
|
||||
|
||||
## Report Structure
|
||||
|
||||
### Header
|
||||
```
|
||||
╔══════════════════════════════════════════════════════════╗
|
||||
║ 🔐 SECURITY REVIEW REPORT ║
|
||||
║ Generated by: /security-review skill ║
|
||||
╚══════════════════════════════════════════════════════════╝
|
||||
|
||||
Project: <project name or path>
|
||||
Scan Date: <today's date>
|
||||
Scope: <files/directories scanned>
|
||||
Languages Detected: <list>
|
||||
Frameworks Detected: <list>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Executive Summary Table
|
||||
|
||||
Always show this first — at a glance overview:
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────┐
|
||||
│ FINDINGS SUMMARY │
|
||||
├──────────────┬──────────────────────────────── ┤
|
||||
│ 🔴 CRITICAL │ <n> findings │
|
||||
│ 🟠 HIGH │ <n> findings │
|
||||
│ 🟡 MEDIUM │ <n> findings │
|
||||
│ 🔵 LOW │ <n> findings │
|
||||
│ ⚪ INFO │ <n> findings │
|
||||
├──────────────┼─────────────────────────────────┤
|
||||
│ TOTAL │ <n> findings │
|
||||
└──────────────┴─────────────────────────────────┘
|
||||
|
||||
Dependency Audit: <n> vulnerable packages found
|
||||
Secrets Scan: <n> exposed credentials found
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Findings (Grouped by Category)
|
||||
|
||||
For EACH finding, use this card format:
|
||||
|
||||
```
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
[SEVERITY EMOJI] [SEVERITY] — [VULNERABILITY TYPE]
|
||||
Confidence: HIGH / MEDIUM / LOW
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
📍 Location: src/routes/users.js, Line 47
|
||||
|
||||
🔍 Vulnerable Code:
|
||||
const query = `SELECT * FROM users WHERE id = ${req.params.id}`;
|
||||
db.execute(query);
|
||||
|
||||
⚠️ Risk:
|
||||
An attacker can manipulate the `id` parameter to execute arbitrary
|
||||
SQL commands, potentially dumping the entire database, bypassing
|
||||
authentication, or deleting data.
|
||||
|
||||
Example attack: GET /users/1 OR 1=1--
|
||||
|
||||
✅ Recommended Fix:
|
||||
Use parameterized queries:
|
||||
|
||||
const query = 'SELECT * FROM users WHERE id = ?';
|
||||
db.execute(query, [req.params.id]);
|
||||
|
||||
📚 Reference: OWASP A03:2021 – Injection
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Dependency Audit Section
|
||||
|
||||
```
|
||||
📦 DEPENDENCY AUDIT
|
||||
══════════════════
|
||||
|
||||
🟠 HIGH — lodash@4.17.20 (package.json)
|
||||
CVE-2021-23337: Prototype pollution via zipObjectDeep()
|
||||
Fix: npm install lodash@4.17.21
|
||||
|
||||
🟡 MEDIUM — axios@0.27.2 (package.json)
|
||||
CVE-2023-45857: CSRF via withCredentials
|
||||
Fix: npm install axios@1.6.0
|
||||
|
||||
⚪ INFO — express@4.18.2
|
||||
No known CVEs. Current version is 4.19.2 — consider updating.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Secrets Scan Section
|
||||
|
||||
```
|
||||
🔑 SECRETS & EXPOSURE SCAN
|
||||
═══════════════════════════
|
||||
|
||||
🔴 CRITICAL — Hardcoded API Key
|
||||
File: src/config/database.js, Line 12
|
||||
|
||||
Found: STRIPE_SECRET_KEY = "sk_live_FAKE_KEY_..."
|
||||
|
||||
Action Required:
|
||||
1. Rotate this key IMMEDIATELY at https://dashboard.stripe.com
|
||||
2. Remove from source code
|
||||
3. Add to .env file and load via process.env.STRIPE_SECRET_KEY
|
||||
4. Add .env to .gitignore
|
||||
5. Audit git history — key may be in previous commits:
|
||||
git log --all -p | grep "sk_live_"
|
||||
Use git-filter-repo or BFG to purge from history if found.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Patch Proposals Section
|
||||
|
||||
Only include for CRITICAL and HIGH findings:
|
||||
|
||||
````
|
||||
🛠️ PATCH PROPOSALS
|
||||
══════════════════
|
||||
⚠️ REVIEW EACH PATCH BEFORE APPLYING — Nothing has been changed yet.
|
||||
|
||||
─────────────────────────────────────────────
|
||||
Patch 1/3: SQL Injection in src/routes/users.js
|
||||
─────────────────────────────────────────────
|
||||
|
||||
BEFORE (vulnerable):
|
||||
```js
|
||||
// Line 47
|
||||
const query = `SELECT * FROM users WHERE id = ${req.params.id}`;
|
||||
db.execute(query);
|
||||
```
|
||||
|
||||
AFTER (fixed):
|
||||
```js
|
||||
// Line 47 — Fixed: Use parameterized query to prevent SQL injection
|
||||
const query = 'SELECT * FROM users WHERE id = ?';
|
||||
db.execute(query, [req.params.id]);
|
||||
```
|
||||
|
||||
Apply this patch? (Review first — AI-generated patches may need adjustment)
|
||||
─────────────────────────────────────────────
|
||||
````
|
||||
|
||||
---
|
||||
|
||||
### Footer
|
||||
|
||||
```
|
||||
══════════════════════════════════════════════════════════
|
||||
|
||||
📋 SCAN COVERAGE
|
||||
Files scanned: <n>
|
||||
Lines analyzed: <n>
|
||||
Scan duration: <time>
|
||||
|
||||
⚡ NEXT STEPS
|
||||
1. Address all CRITICAL findings immediately
|
||||
2. Schedule HIGH findings for current sprint
|
||||
3. Add MEDIUM/LOW to your security backlog
|
||||
4. Set up automated re-scanning in CI/CD pipelines
|
||||
|
||||
💡 NOTE: This is a static analysis scan. It does not execute your
|
||||
application and cannot detect all runtime vulnerabilities. Pair
|
||||
with dynamic testing (DAST) for comprehensive coverage.
|
||||
|
||||
══════════════════════════════════════════════════════════
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Confidence Ratings Guide
|
||||
|
||||
Apply to every finding:
|
||||
|
||||
| Confidence | When to Use |
|
||||
|------------|-------------|
|
||||
| **HIGH** | Vulnerability is unambiguous. Sanitization is clearly absent. Exploitable as-is. |
|
||||
| **MEDIUM** | Vulnerability likely exists but depends on runtime context, config, or call path the agent couldn't fully trace. |
|
||||
| **LOW** | Suspicious pattern detected but could be a false positive. Flag for human review. |
|
||||
|
||||
Never omit confidence — it helps developers prioritize their review effort.
|
||||
178
skills/security-review/references/secret-patterns.md
Normal file
178
skills/security-review/references/secret-patterns.md
Normal file
@@ -0,0 +1,178 @@
|
||||
# Secret & Credential Detection Patterns
|
||||
|
||||
Load this file during Step 3 (Secrets & Exposure Scan).
|
||||
|
||||
---
|
||||
|
||||
## High-Confidence Secret Patterns
|
||||
|
||||
These patterns almost always indicate a real secret:
|
||||
|
||||
### API Keys & Tokens
|
||||
```regex
|
||||
# OpenAI
|
||||
sk-[a-zA-Z0-9]{48}
|
||||
|
||||
# Anthropic
|
||||
sk-ant-[a-zA-Z0-9\-_]{90,}
|
||||
|
||||
# AWS Access Key
|
||||
AKIA[0-9A-Z]{16}
|
||||
|
||||
# AWS Secret Key (look for near AWS_ACCESS_KEY_ID assignment)
|
||||
[0-9a-zA-Z/+]{40}
|
||||
|
||||
# GitHub Token
|
||||
gh[pousr]_[a-zA-Z0-9]{36,}
|
||||
github_pat_[a-zA-Z0-9]{82}
|
||||
|
||||
# Stripe
|
||||
sk_live_[a-zA-Z0-9]{24,}
|
||||
rk_live_[a-zA-Z0-9]{24,}
|
||||
|
||||
# Twilio Account SID
|
||||
AC[a-z0-9]{32}
|
||||
# Twilio API Key
|
||||
SK[a-z0-9]{32}
|
||||
|
||||
# SendGrid
|
||||
SG\.[a-zA-Z0-9\-_.]{66}
|
||||
|
||||
# Slack
|
||||
xoxb-[0-9]+-[0-9]+-[a-zA-Z0-9]+
|
||||
xoxp-[0-9]+-[0-9]+-[0-9]+-[a-zA-Z0-9]+
|
||||
xapp-[0-9]+-[A-Z0-9]+-[0-9]+-[a-zA-Z0-9]+
|
||||
|
||||
# Google API Key
|
||||
AIza[0-9A-Za-z\-_]{35}
|
||||
|
||||
# Google OAuth
|
||||
[0-9]+-[0-9A-Za-z_]{32}\.apps\.googleusercontent\.com
|
||||
|
||||
# Cloudflare (near CF_API_TOKEN)
|
||||
[a-zA-Z0-9_\-]{37}
|
||||
|
||||
# Mailgun
|
||||
key-[a-zA-Z0-9]{32}
|
||||
|
||||
# Heroku
|
||||
[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}
|
||||
```
|
||||
|
||||
### Private Keys
|
||||
```regex
|
||||
-----BEGIN (RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY( BLOCK)?-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
```
|
||||
|
||||
### Database Connection Strings
|
||||
```regex
|
||||
# MongoDB
|
||||
mongodb(\+srv)?:\/\/[^:]+:[^@]+@
|
||||
|
||||
# PostgreSQL / MySQL
|
||||
(postgres|postgresql|mysql):\/\/[^:]+:[^@]+@
|
||||
|
||||
# Redis with password
|
||||
redis:\/\/:[^@]+@
|
||||
|
||||
# Generic connection string with password
|
||||
(connection[_-]?string|connstr|db[_-]?url).*password=
|
||||
```
|
||||
|
||||
### Hardcoded Passwords (variable name signals)
|
||||
```regex
|
||||
# Variable names that suggest secrets
|
||||
(password|passwd|pwd|secret|api_key|apikey|auth_token|access_token|private_key)
|
||||
\s*[=:]\s*["'][^"']{8,}["']
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Entropy-Based Detection
|
||||
|
||||
Apply to string literals > 20 characters in assignment context.
|
||||
High entropy (Shannon entropy > 4.5 bits/char) + length > 20 = likely secret.
|
||||
|
||||
```
|
||||
Calculate entropy: -sum(p * log2(p)) for each character frequency p
|
||||
Threshold: > 4.5 bits/char AND > 20 chars AND assigned to a variable
|
||||
```
|
||||
|
||||
Common false positives to exclude:
|
||||
- Lorem ipsum text
|
||||
- HTML/CSS content
|
||||
- Base64-encoded non-sensitive config (but flag and note)
|
||||
- UUID/GUID (entropy is high but format is recognizable)
|
||||
|
||||
---
|
||||
|
||||
## Files That Should Never Be Committed
|
||||
|
||||
Flag if these files exist in the repo root or are tracked by git:
|
||||
```
|
||||
.env
|
||||
.env.local
|
||||
.env.production
|
||||
.env.staging
|
||||
*.pem
|
||||
*.key
|
||||
*.p12
|
||||
*.pfx
|
||||
id_rsa
|
||||
id_ed25519
|
||||
credentials.json
|
||||
service-account.json
|
||||
gcp-key.json
|
||||
secrets.yaml
|
||||
secrets.json
|
||||
config/secrets.yml
|
||||
```
|
||||
|
||||
Also check `.gitignore` — if a secret file pattern is NOT in .gitignore, flag it.
|
||||
|
||||
---
|
||||
|
||||
## CI/CD & IaC Secret Risks
|
||||
|
||||
### GitHub Actions — flag these patterns:
|
||||
```yaml
|
||||
# Hardcoded values in env: blocks (should use ${{ secrets.NAME }})
|
||||
env:
|
||||
API_KEY: "actual-value-here" # VULNERABLE
|
||||
|
||||
# Printing secrets
|
||||
- run: echo ${{ secrets.MY_SECRET }} # leaks to logs
|
||||
```
|
||||
|
||||
### Docker — flag these:
|
||||
```dockerfile
|
||||
# Secrets in ENV (persisted in image layers)
|
||||
ENV AWS_SECRET_KEY=actual-value
|
||||
|
||||
# Secrets passed as build args (visible in image history)
|
||||
ARG API_KEY=actual-value
|
||||
```
|
||||
|
||||
### Terraform — flag these:
|
||||
```hcl
|
||||
# Hardcoded sensitive values (should use var or data source)
|
||||
password = "hardcoded-password"
|
||||
access_key = "AKIAIOSFODNN7EXAMPLE"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Safe Patterns (Do NOT flag)
|
||||
|
||||
These are intentional placeholders — recognize and skip:
|
||||
```
|
||||
"your-api-key-here"
|
||||
"<YOUR_API_KEY>"
|
||||
"${API_KEY}"
|
||||
"${process.env.API_KEY}"
|
||||
"os.environ.get('API_KEY')"
|
||||
"REPLACE_WITH_YOUR_KEY"
|
||||
"xxx...xxx"
|
||||
"sk-..." (in documentation/comments)
|
||||
```
|
||||
281
skills/security-review/references/vuln-categories.md
Normal file
281
skills/security-review/references/vuln-categories.md
Normal file
@@ -0,0 +1,281 @@
|
||||
# Vulnerability Categories — Deep Reference
|
||||
|
||||
This file contains detailed detection guidance for every vulnerability category.
|
||||
Load this during Step 4 of the scan workflow.
|
||||
|
||||
---
|
||||
|
||||
## 1. Injection Flaws
|
||||
|
||||
### SQL Injection
|
||||
**What to look for:**
|
||||
- String concatenation or interpolation inside SQL queries
|
||||
- Raw `.query()`, `.execute()`, `.raw()` calls with variables
|
||||
- ORM `whereRaw()`, `selectRaw()`, `orderByRaw()` with user input
|
||||
- Second-order SQLi: data stored safely, then used unsafely later
|
||||
- Stored procedures called with unsanitized input
|
||||
|
||||
**Detection signals (all languages):**
|
||||
```
|
||||
"SELECT ... " + variable
|
||||
`SELECT ... ${variable}`
|
||||
f"SELECT ... {variable}"
|
||||
"SELECT ... %s" % variable # Only safe with proper driver parameterization
|
||||
cursor.execute("... " + input)
|
||||
db.raw(`... ${req.params.id}`)
|
||||
```
|
||||
|
||||
**Safe patterns (parameterized):**
|
||||
```js
|
||||
db.query('SELECT * FROM users WHERE id = ?', [userId])
|
||||
User.findOne({ where: { id: userId } }) // ORM safe
|
||||
```
|
||||
|
||||
**Escalation checkers:**
|
||||
- Is the query result ever used in another query? (second-order)
|
||||
- Is the table/column name user-controlled? (cannot be parameterized — must allowlist)
|
||||
|
||||
---
|
||||
|
||||
### Cross-Site Scripting (XSS)
|
||||
**What to look for:**
|
||||
- `innerHTML`, `outerHTML`, `document.write()` with user data
|
||||
- `dangerouslySetInnerHTML` in React
|
||||
- Template engines rendering unescaped: `{{{ var }}}` (Handlebars), `!= var` (Pug)
|
||||
- jQuery `.html()`, `.append()` with user data
|
||||
- `eval()`, `setTimeout(string)`, `setInterval(string)` with user data
|
||||
- DOM-based: `location.hash`, `document.referrer`, `window.name` written to DOM
|
||||
- Stored XSS: user input saved to DB, rendered without escaping later
|
||||
|
||||
**Detection by framework:**
|
||||
- **React**: Safe by default EXCEPT `dangerouslySetInnerHTML`
|
||||
- **Angular**: Safe by default EXCEPT `bypassSecurityTrustHtml`
|
||||
- **Vue**: Safe by default EXCEPT `v-html`
|
||||
- **Vanilla JS**: Every DOM write is suspect
|
||||
|
||||
---
|
||||
|
||||
### Command Injection
|
||||
**What to look for (Node.js):**
|
||||
```js
|
||||
exec(userInput)
|
||||
execSync(`ping ${host}`)
|
||||
spawn('sh', ['-c', userInput])
|
||||
child_process.exec('ls ' + dir)
|
||||
```
|
||||
|
||||
**What to look for (Python):**
|
||||
```python
|
||||
os.system(user_input)
|
||||
subprocess.call(user_input, shell=True)
|
||||
eval(user_input)
|
||||
```
|
||||
|
||||
**What to look for (PHP):**
|
||||
```php
|
||||
exec($input)
|
||||
system($_GET['cmd'])
|
||||
passthru($input)
|
||||
`$input` # backtick operator
|
||||
```
|
||||
|
||||
**Safe alternatives:** Use array form of spawn/subprocess without shell=True; use allowlists for commands.
|
||||
|
||||
---
|
||||
|
||||
### Server-Side Request Forgery (SSRF)
|
||||
**What to look for:**
|
||||
- HTTP requests where the URL is user-controlled
|
||||
- Webhooks, URL preview, image fetch features
|
||||
- PDF generators that fetch external URLs
|
||||
- Redirects to user-supplied URLs
|
||||
|
||||
**High-risk targets:**
|
||||
- AWS metadata service: `169.254.169.254`
|
||||
- Internal services: `localhost`, `127.0.0.1`, `10.x.x.x`, `192.168.x.x`
|
||||
- Cloud metadata endpoints
|
||||
|
||||
**Detection:**
|
||||
```js
|
||||
fetch(req.body.url)
|
||||
axios.get(userSuppliedUrl)
|
||||
http.get(params.webhook)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Authentication & Access Control
|
||||
|
||||
### Broken Object Level Authorization (BOLA / IDOR)
|
||||
**What to look for:**
|
||||
- Resource IDs taken directly from URL/params without ownership check
|
||||
- `findById(req.params.id)` without verifying `userId === currentUser.id`
|
||||
- Numeric sequential IDs (easily guessable)
|
||||
|
||||
**Example vulnerable pattern:**
|
||||
```js
|
||||
// VULNERABLE: no ownership check
|
||||
app.get('/api/documents/:id', async (req, res) => {
|
||||
const doc = await Document.findById(req.params.id);
|
||||
res.json(doc);
|
||||
});
|
||||
|
||||
// SAFE: verify ownership
|
||||
app.get('/api/documents/:id', async (req, res) => {
|
||||
const doc = await Document.findOne({ _id: req.params.id, owner: req.user.id });
|
||||
if (!doc) return res.status(403).json({ error: 'Forbidden' });
|
||||
res.json(doc);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### JWT Vulnerabilities
|
||||
**What to look for:**
|
||||
- `alg: "none"` accepted
|
||||
- Weak or hardcoded secrets: `secret`, `password`, `1234`
|
||||
- No expiry (`exp` claim) validation
|
||||
- Algorithm confusion (RS256 → HS256 downgrade)
|
||||
- JWT stored in `localStorage` (XSS risk; prefer httpOnly cookie)
|
||||
|
||||
**Detection:**
|
||||
```js
|
||||
jwt.verify(token, secret, { algorithms: ['HS256'] }) // Check algorithms array
|
||||
jwt.decode(token) // WARNING: decode does NOT verify signature
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Missing Authentication / Authorization
|
||||
**What to look for:**
|
||||
- Admin or sensitive endpoints missing auth middleware
|
||||
- Routes defined after `app.use(authMiddleware)` vs before it
|
||||
- Feature flags or debug endpoints left exposed in production
|
||||
- GraphQL resolvers missing auth checks at field level
|
||||
|
||||
---
|
||||
|
||||
### CSRF
|
||||
**What to look for:**
|
||||
- State-changing operations (POST/PUT/DELETE) without CSRF token
|
||||
- APIs relying only on cookies for auth without SameSite attribute
|
||||
- Missing `SameSite=Strict` or `SameSite=Lax` on session cookies
|
||||
|
||||
---
|
||||
|
||||
## 3. Secrets & Sensitive Data Exposure
|
||||
|
||||
### In-Code Secrets
|
||||
Look for patterns like:
|
||||
```
|
||||
API_KEY = "sk-..."
|
||||
password = "hunter2"
|
||||
SECRET = "abc123"
|
||||
private_key = "-----BEGIN RSA PRIVATE KEY-----"
|
||||
aws_secret_access_key = "wJalrXUtn..."
|
||||
```
|
||||
|
||||
Entropy heuristic: strings > 20 chars with high character variety in assignment context
|
||||
are likely secrets even if the variable name doesn't say so.
|
||||
|
||||
### In Logs / Error Messages
|
||||
```js
|
||||
console.log('User password:', password)
|
||||
logger.info({ user, token }) // token shouldn't be logged
|
||||
res.status(500).json({ error: err.stack }) // stack traces expose internals
|
||||
```
|
||||
|
||||
### Sensitive Data in API Responses
|
||||
- Returning full user object including `password_hash`, `ssn`, `credit_card`
|
||||
- Including internal IDs or system paths in error responses
|
||||
|
||||
---
|
||||
|
||||
## 4. Cryptography
|
||||
|
||||
### Weak Algorithms
|
||||
| Algorithm | Issue | Replace With |
|
||||
|-----------|-------|--------------|
|
||||
| MD5 | Broken for security | SHA-256 or bcrypt (passwords) |
|
||||
| SHA-1 | Collision attacks | SHA-256 |
|
||||
| DES / 3DES | Weak key size | AES-256-GCM |
|
||||
| RC4 | Broken | AES-GCM |
|
||||
| ECB mode | No IV, patterns visible | GCM or CBC with random IV |
|
||||
|
||||
### Weak Randomness
|
||||
```js
|
||||
// VULNERABLE
|
||||
Math.random() // not cryptographically secure
|
||||
Date.now() // predictable
|
||||
Math.random().toString(36) // weak token generation
|
||||
|
||||
// SAFE
|
||||
crypto.randomBytes(32) // Node.js
|
||||
secrets.token_urlsafe(32) // Python
|
||||
```
|
||||
|
||||
### Password Hashing
|
||||
```python
|
||||
# VULNERABLE
|
||||
hashlib.md5(password.encode()).hexdigest()
|
||||
hashlib.sha256(password.encode()).hexdigest()
|
||||
|
||||
# SAFE
|
||||
bcrypt.hashpw(password, bcrypt.gensalt(rounds=12))
|
||||
argon2.hash(password)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Insecure Dependencies
|
||||
|
||||
### What to flag:
|
||||
- Packages with known CVEs in installed version range
|
||||
- Packages abandoned > 2 years with no security updates
|
||||
- Packages with extremely broad permissions for their stated purpose
|
||||
- Transitive dependencies pulling in known-bad packages
|
||||
- Pinned versions that are significantly behind current (possible unpatched vulns)
|
||||
|
||||
### High-risk package watchlist: see `references/vulnerable-packages.md`
|
||||
|
||||
---
|
||||
|
||||
## 6. Business Logic
|
||||
|
||||
### Race Conditions (TOCTOU)
|
||||
```js
|
||||
// VULNERABLE: check then act without atomic lock
|
||||
const balance = await getBalance(userId);
|
||||
if (balance >= amount) {
|
||||
await deductBalance(userId, amount); // race condition between check and deduct
|
||||
}
|
||||
|
||||
// SAFE: use atomic DB transaction or optimistic locking
|
||||
await db.transaction(async (trx) => {
|
||||
const user = await User.query(trx).forUpdate().findById(userId);
|
||||
if (user.balance < amount) throw new Error('Insufficient funds');
|
||||
await user.$query(trx).patch({ balance: user.balance - amount });
|
||||
});
|
||||
```
|
||||
|
||||
### Missing Rate Limiting
|
||||
Flag endpoints that:
|
||||
- Accept authentication credentials (login, 2FA)
|
||||
- Send emails or SMS
|
||||
- Perform expensive operations
|
||||
- Expose user enumeration (password reset, registration)
|
||||
|
||||
---
|
||||
|
||||
## 7. Path Traversal
|
||||
```python
|
||||
# VULNERABLE
|
||||
filename = request.args.get('file')
|
||||
with open(f'/var/uploads/{filename}') as f: # ../../../../etc/passwd
|
||||
|
||||
# SAFE
|
||||
filename = os.path.basename(request.args.get('file'))
|
||||
safe_path = os.path.join('/var/uploads', filename)
|
||||
if not safe_path.startswith('/var/uploads/'):
|
||||
abort(400)
|
||||
```
|
||||
111
skills/security-review/references/vulnerable-packages.md
Normal file
111
skills/security-review/references/vulnerable-packages.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# Vulnerable & High-Risk Package Watchlist
|
||||
|
||||
Load this during Step 2 (Dependency Audit). Check versions in the project's lock files.
|
||||
|
||||
---
|
||||
|
||||
## npm / Node.js
|
||||
|
||||
| Package | Vulnerable Versions | Issue | Safe Version |
|
||||
|---------|-------------------|-------|--------------|
|
||||
| lodash | < 4.17.21 | Prototype pollution (CVE-2021-23337) | >= 4.17.21 |
|
||||
| axios | < 1.6.0 | SSRF, open redirect | >= 1.6.0 |
|
||||
| jsonwebtoken | < 9.0.0 | Algorithm confusion bypass | >= 9.0.0 |
|
||||
| node-jose | < 2.2.0 | Key confusion | >= 2.2.0 |
|
||||
| shelljs | < 0.8.5 | ReDoS | >= 0.8.5 |
|
||||
| tar | < 6.1.9 | Path traversal | >= 6.1.9 |
|
||||
| minimist | < 1.2.6 | Prototype pollution | >= 1.2.6 |
|
||||
| qs | < 6.7.3 | Prototype pollution | >= 6.7.3 |
|
||||
| express | < 4.19.2 | Open redirect | >= 4.19.2 |
|
||||
| multer | < 1.4.4 | DoS | >= 1.4.4-lts.1 |
|
||||
| xml2js | < 0.5.0 | Prototype pollution | >= 0.5.0 |
|
||||
| fast-xml-parser | < 4.2.4 | ReDoS | >= 4.2.4 |
|
||||
| semver | < 7.5.2 | ReDoS | >= 7.5.2 |
|
||||
| tough-cookie | < 4.1.3 | Prototype pollution | >= 4.1.3 |
|
||||
| word-wrap | < 1.2.4 | ReDoS | >= 1.2.4 |
|
||||
| vm2 | ANY | Sandbox escape (deprecated) | Use isolated-vm instead |
|
||||
| serialize-javascript | < 3.1.0 | XSS | >= 3.1.0 |
|
||||
| node-fetch | < 2.6.7 | Open redirect | >= 2.6.7 or 3.x |
|
||||
|
||||
### Patterns to flag (regardless of version):
|
||||
- `eval` or `vm.runInContext` in dependencies
|
||||
- Any package pulling in `node-gyp` native addons from unknown publishers
|
||||
- Packages with < 1000 weekly downloads but required in production code (supply chain risk)
|
||||
|
||||
---
|
||||
|
||||
## Python / pip
|
||||
|
||||
| Package | Vulnerable Versions | Issue | Safe Version |
|
||||
|---------|-------------------|-------|--------------|
|
||||
| Pillow | < 10.0.1 | Multiple CVEs, buffer overflow | >= 10.0.1 |
|
||||
| cryptography | < 41.0.0 | OpenSSL vulnerabilities | >= 41.0.0 |
|
||||
| PyYAML | < 6.0 | Arbitrary code via yaml.load() | >= 6.0 |
|
||||
| paramiko | < 3.4.0 | Authentication bypass | >= 3.4.0 |
|
||||
| requests | < 2.31.0 | Proxy auth info leak | >= 2.31.0 |
|
||||
| urllib3 | < 2.0.7 | Header injection | >= 2.0.7 |
|
||||
| Django | < 4.2.16 | Various | >= 4.2.16 |
|
||||
| Flask | < 3.0.3 | Various | >= 3.0.3 |
|
||||
| Jinja2 | < 3.1.4 | HTML attribute injection | >= 3.1.4 |
|
||||
| sqlalchemy | < 2.0.28 | Various | >= 2.0.28 |
|
||||
| aiohttp | < 3.9.4 | SSRF, path traversal | >= 3.9.4 |
|
||||
| werkzeug | < 3.0.3 | Various | >= 3.0.3 |
|
||||
|
||||
---
|
||||
|
||||
## Java / Maven
|
||||
|
||||
| Package | Vulnerable Versions | Issue |
|
||||
|---------|-------------------|-------|
|
||||
| log4j-core | 2.0-2.14.1 | Log4Shell RCE (CVE-2021-44228) — CRITICAL |
|
||||
| log4j-core | 2.15.0 | Incomplete fix — still vulnerable |
|
||||
| Spring Framework | < 5.3.28, < 6.0.13 | Various CVEs |
|
||||
| Spring Boot | < 3.1.4 | Various |
|
||||
| Jackson-databind | < 2.14.0 | Deserialization |
|
||||
| Apache Commons Text | < 1.10.0 | Text4Shell RCE (CVE-2022-42889) |
|
||||
| Apache Struts | < 6.3.0 | Various RCE |
|
||||
| Netty | < 4.1.94 | HTTP request smuggling |
|
||||
|
||||
---
|
||||
|
||||
## Ruby / Gems
|
||||
|
||||
| Gem | Vulnerable Versions | Issue |
|
||||
|-----|-------------------|-------|
|
||||
| rails | < 7.1.3 | Various |
|
||||
| nokogiri | < 1.16.2 | XXE, various |
|
||||
| rexml | < 3.2.7 | ReDoS |
|
||||
| rack | < 3.0.9 | Various |
|
||||
| devise | < 4.9.3 | Various |
|
||||
|
||||
---
|
||||
|
||||
## Rust / Cargo
|
||||
|
||||
| Crate | Issue |
|
||||
|-------|-------|
|
||||
| openssl | Check advisory db for current version |
|
||||
| hyper | Check advisory db for current version |
|
||||
|
||||
Reference: https://rustsec.org/advisories/
|
||||
|
||||
---
|
||||
|
||||
## Go
|
||||
|
||||
Reference: https://pkg.go.dev/vuln/ and https://vuln.go.dev
|
||||
|
||||
Common risky patterns:
|
||||
- `golang.org/x/crypto` — check if version is within 6 months of current
|
||||
- Any dependency using `syscall` package directly — review carefully
|
||||
|
||||
---
|
||||
|
||||
## General Red Flags (Any Ecosystem)
|
||||
|
||||
Flag any dependency that:
|
||||
1. Has not been updated in > 2 years AND has > 10 open security issues
|
||||
2. Has been deprecated by its maintainer with a security advisory
|
||||
3. Is a fork of a known package from an unknown publisher (typosquatting)
|
||||
4. Has a name that's one character off from a popular package (e.g., `lodash` vs `1odash`)
|
||||
5. Was recently transferred to a new owner (check git history / npm transfer notices)
|
||||
Reference in New Issue
Block a user