SAST Patterns Skill
Comprehensive vulnerability pattern library for static application security testing. Covers OWASP Top 10, language-specific patterns, Semgrep custom rules, and CI/CD pipeline integration.
When to Activate
- Running security scans on code
- Writing custom Semgrep rules
- Setting up CI/CD security gates
- Reviewing code for security vulnerabilities
- After sast-on-edit hook triggers
- Before production deployment
OWASP Top 10 (2021) Checklist
A01: Broken Access Control
What to Look For:
- Missing authorization checks on endpoints
- Direct object reference without ownership validation
- CORS misconfiguration allowing wildcard origins
- Missing function-level access control
- Metadata manipulation (JWT, cookies, hidden fields)
Detection Patterns:
// VULNERABLE: No authorization check
app.get('/api/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id)
res.json(user) // Anyone can access any user
})
// SECURE: Authorization verified
app.get('/api/users/:id', authenticate, async (req, res) => {
if (req.user.id !== req.params.id && !req.user.isAdmin) {
return res.status(403).json({ error: 'Forbidden' })
}
const user = await db.users.findById(req.params.id)
res.json(user)
})
# VULNERABLE: No permission check
@app.route('/admin/delete/<user_id>', methods=['DELETE'])
def delete_user(user_id):
db.session.delete(User.query.get(user_id))
db.session.commit()
# SECURE: Permission verified
@app.route('/admin/delete/<user_id>', methods=['DELETE'])
@login_required
@admin_required
def delete_user(user_id):
db.session.delete(User.query.get(user_id))
db.session.commit()
Semgrep Rules:
rules:
- id: missing-auth-check
patterns:
- pattern: |
app.$METHOD($PATH, async (req, res) => {
...
$DB.$QUERY(...)
...
})
- pattern-not: |
app.$METHOD($PATH, authenticate, ...)
message: "Endpoint missing authentication middleware"
severity: ERROR
A02: Cryptographic Failures
What to Look For:
- Hardcoded secrets in source code
- Weak hashing (MD5, SHA1) for passwords
- Missing encryption for sensitive data at rest
- HTTP instead of HTTPS
- Weak TLS configuration
Detection Patterns:
// VULNERABLE: Weak password hashing
const hash = crypto.createHash('md5').update(password).digest('hex')
// SECURE: Strong password hashing
const hash = await bcrypt.hash(password, 12)
# VULNERABLE: Hardcoded secret
SECRET_KEY = "my-super-secret-key-123"
# SECURE: Environment variable
SECRET_KEY = os.environ.get('SECRET_KEY')
if not SECRET_KEY:
raise ValueError("SECRET_KEY environment variable required")
Regex Patterns for Detection:
# API Keys
(?:api[_-]?key|apikey)\s*[:=]\s*['"][A-Za-z0-9_\-]{20,}['"]
# AWS Keys
(?:AKIA|ASIA)[A-Z0-9]{16}
# Generic Secrets
(?:password|passwd|pwd|secret|token)\s*[:=]\s*['"][^'"]{8,}['"]
# Private Keys
-----BEGIN (?:RSA |EC |DSA )?PRIVATE KEY-----
# JWT Tokens
eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+
A03: Injection
What to Look For:
- SQL injection (string concatenation in queries)
- Command injection (unsanitized shell execution)
- NoSQL injection (MongoDB query operators in user input)
- LDAP injection
- Expression Language injection
Detection Patterns:
// SQL Injection
const query = `SELECT * FROM users WHERE id = ${userId}` // VULNERABLE
const query = 'SELECT * FROM users WHERE id = $1' // SECURE
// Command Injection
exec(`ping ${hostname}`) // VULNERABLE
execFile('ping', [hostname]) // SECURE
// NoSQL Injection (MongoDB)
db.users.find({ email: req.body.email }) // VULNERABLE if email = {"$gt": ""}
db.users.find({ email: String(req.body.email) }) // SECURE: type coercion
# SQL Injection
cursor.execute(f"SELECT * FROM users WHERE id = {user_id}") # VULNERABLE
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,)) # SECURE
# Command Injection
os.system(f"convert {filename} output.png") # VULNERABLE
subprocess.run(['convert', filename, 'output.png'], check=True) # SECURE
// SQL Injection
db.Query("SELECT * FROM users WHERE id = " + userID) // VULNERABLE
db.Query("SELECT * FROM users WHERE id = $1", userID) // SECURE
// Command Injection
exec.Command("sh", "-c", userInput) // VULNERABLE
exec.Command("ping", "-c", "1", hostname) // SECURE
// SQL Injection
stmt.executeQuery("SELECT * FROM users WHERE id = " + id); // VULNERABLE
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
ps.setString(1, id); // SECURE
A04: Insecure Design
What to Look For:
- Missing rate limiting on critical operations
- No account lockout after failed attempts
- Missing CAPTCHA on public forms
- Business logic flaws
- Missing transaction atomicity
Detection Patterns:
// VULNERABLE: No rate limiting on login
app.post('/login', async (req, res) => {
const user = await authenticate(req.body)
res.json({ token: generateToken(user) })
})
// SECURE: Rate limited login
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
message: 'Too many login attempts'
})
app.post('/login', loginLimiter, async (req, res) => {
const user = await authenticate(req.body)
res.json({ token: generateToken(user) })
})
# VULNERABLE: Race condition in balance check
balance = get_balance(user_id)
if balance >= amount:
withdraw(user_id, amount) # Another request could drain first
# SECURE: Atomic transaction
with db.transaction():
balance = db.query("SELECT balance FROM accounts WHERE id = %s FOR UPDATE", user_id)
if balance >= amount:
db.execute("UPDATE accounts SET balance = balance - %s WHERE id = %s", amount, user_id)
A05: Security Misconfiguration
What to Look For:
- Debug mode in production
- Default credentials
- Unnecessary features enabled
- Missing security headers
- Verbose error messages
- Directory listing enabled
Detection Patterns:
// VULNERABLE: Debug mode
app.set('env', 'development') // In production config
// VULNERABLE: Missing security headers
// No helmet or manual headers
// SECURE: Security headers
import helmet from 'helmet'
app.use(helmet())
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:', 'https:'],
}
}))
# VULNERABLE: Debug in production
DEBUG = True # In production settings
# VULNERABLE: Default secret key
SECRET_KEY = 'django-insecure-change-me'
# SECURE
DEBUG = os.environ.get('DEBUG', 'False') == 'True'
SECRET_KEY = os.environ['DJANGO_SECRET_KEY']
Security Headers Checklist:
Content-Security-Policy: default-src 'self'
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 0
Strict-Transport-Security: max-age=31536000; includeSubDomains
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
A06: Vulnerable and Outdated Components
Detection Commands:
# Node.js
npm audit
npm audit --json
npm outdated
# Python
pip-audit
safety check
pip list --outdated
# Go
go list -m -u all
govulncheck ./...
# Java
mvn dependency-check:check
gradle dependencyCheckAnalyze
# Ruby
bundle audit check