Sast patterns
AI software team for Claude Code - 138 agents, 295 skills, 73 hooks. Self-learning, multi-agent swarm, autonomous skill evolution.
npx -y skills add vibeeval/vibecosystem --skill sast-patternsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
What its author says it does
Copied from the file, not written here
Static Application Security Testing patterns, OWASP Top 10 checklist, language-specific vulnerability patterns, Semgrep rule writing guide, and CI/CD integration. Use when scanning code for security vulnerabilities or writing custom SAST rules.
SKILL.md
18.8 KB, as published. Nobody here has run it
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
A07: Identification and Authentication Failures
What to Look For:
- Weak password policies
- Missing MFA
- Session fixation
- Credential stuffing vulnerability
- Plaintext password storage/comparison
Detection Patterns:
// VULNERABLE: Plaintext password comparison
if (password === user.password) { /* login */ }
// SECURE: Hashed comparison
const isValid = await bcrypt.compare(password, user.passwordHash)
// VULNERABLE: Weak session management
app.use(session({ secret: 'secret' }))
// SECURE: Strong session
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: true,
httpOnly: true,
sameSite: 'strict',
maxAge: 3600000
}
}))
A08: Software and Data Integrity Failures
What to Look For:
- Missing Subresource Integrity (SRI) on CDN scripts
- Insecure deserialization
- Missing code signing
- Auto-update without integrity verification
- CI/CD pipeline without integrity checks
Detection Patterns:
<!-- VULNERABLE: No SRI -->
<script src="https://cdn.example.com/lib.js"></script>
<!-- SECURE: With SRI -->
<script src="https://cdn.example.com/lib.js"
integrity="sha384-abc123..."
crossorigin="anonymous"></script>
# VULNERABLE: Insecure deserialization
import pickle
data = pickle.loads(user_input)
# SECURE: Use JSON
import json
data = json.loads(user_input)
A09: Security Logging and Monitoring Failures
What to Look For:
- Missing audit logs for auth events
- Sensitive data in logs
- No log integrity protection
- Missing alerting for suspicious activity
Detection Patterns:
// VULNERABLE: No logging
app.post('/login', async (req, res) => {
const user = await authenticate(req.body)
if (!user) return res.status(401).json({ error: 'Invalid' })
res.json({ token: generateToken(user) })
})
// SECURE: Audit logging
app.post('/login', async (req, res) => {
const user = await authenticate(req.body)
if (!user) {
logger.warn('Failed login attempt', {
email: req.body.email,
ip: req.ip,
timestamp: new Date().toISOString()
})
return res.status(401).json({ error: 'Invalid credentials' })
}
logger.info('Successful login', { userId: user.id, ip: req.ip })
res.json({ token: generateToken(user) })
})
// VULNERABLE: Sensitive data in logs
console.log('Login:', { email, password, token })
// SECURE: Redacted logs
console.log('Login:', { email, passwordProvided: !!password })
A10: Server-Side Request Forgery (SSRF)
What to Look For:
- User-controlled URLs in server-side requests
- Missing URL validation/whitelist
- Internal service access via SSRF
- Cloud metadata endpoint access
Detection Patterns:
// VULNERABLE: SSRF
const response = await fetch(req.query.url)
// SECURE: URL whitelist
const ALLOWED_DOMAINS = ['api.example.com', 'cdn.example.com']
const url = new URL(req.query.url)
if (!ALLOWED_DOMAINS.includes(url.hostname)) {
throw new Error('Domain not allowed')
}
// Also block internal IPs
const ip = await dns.resolve(url.hostname)
if (isPrivateIP(ip)) {
throw new Error('Internal addresses not allowed')
}
const response = await fetch(url.toString())
# VULNERABLE: SSRF
response = requests.get(user_url)
# SECURE: URL validation
from urllib.parse import urlparse
parsed = urlparse(user_url)
if parsed.hostname not in ALLOWED_HOSTS:
raise ValueError("Domain not allowed")
if is_private_ip(socket.gethostbyname(parsed.hostname)):
raise ValueError("Internal addresses blocked")
response = requests.get(user_url, allow_redirects=False)
Semgrep Custom Rule Writing Guide
Basic Rule Structure
rules:
- id: rule-unique-id
pattern: |
eval($USER_INPUT)
message: "eval() with dynamic input detected - potential RCE"
languages: [javascript, typescript]
severity: ERROR
metadata:
cwe:
- CWE-94
owasp:
- A03:2021
category: security
confidence: HIGH
Pattern Operators
# pattern: match exactly
- pattern: eval($X)
# pattern-not: exclude matches
- pattern-not: eval("static-string")
# patterns: AND (all must match)
- patterns:
- pattern: $DB.query($SQL)
- pattern-not: $DB.query($SQL, $PARAMS)
# pattern-either: OR (any can match)
- pattern-either:
- pattern: eval($X)
- pattern: new Function($X)
# pattern-inside: match within a context
- pattern-inside: |
app.$METHOD($PATH, (req, res) => {
...
})
# pattern-not-inside: exclude context
- pattern-not-inside: |
app.$METHOD($PATH, authenticate, ...)
# pattern-regex: regex in code
- pattern-regex: "password\s*=\s*['\"][^'\"]{3,}['\"]"
Metavariable Constraints
rules:
- id: weak-hash-for-passwords
patterns:
- pattern: crypto.createHash($ALG).update($INPUT)
- metavariable-regex:
metavariable: $ALG
regex: "('md5'|'sha1')"
- metavariable-regex:
metavariable: $INPUT
regex: ".*password.*"
message: "Weak hash algorithm used for password: $ALG"
languages: [javascript, typescript]
severity: ERROR
Taint Analysis (Pro)
rules:
- id: sql-injection-taint
mode: taint
pattern-sources:
- pattern: req.body.$PARAM
- pattern: req.query.$PARAM
- pattern: req.params.$PARAM
pattern-sinks:
- pattern: $DB.query($SQL)
pattern-sanitizers:
- pattern: $DB.escape($X)
- pattern: sanitize($X)
message: "User input flows to SQL query without sanitization"
languages: [javascript]
severity: ERROR
Project .semgrep.yml Example
rules:
- id: no-hardcoded-secrets
pattern-regex: |
(?:api[_-]?key|secret|password|token)\s*[:=]\s*['"][A-Za-z0-9+/=_\-]{16,}['"]
paths:
exclude:
- "*.test.*"
- "*.spec.*"
- "__tests__/*"
- "*.example"
message: "Potential hardcoded secret detected"
languages: [generic]
severity: ERROR
- id: no-console-log-sensitive
patterns:
- pattern: console.log(..., $DATA, ...)
- metavariable-regex:
metavariable: $DATA
regex: ".*(?:password|secret|token|key|credential).*"
message: "Sensitive data in console.log"
languages: [javascript, typescript]
severity: WARNING
- id: require-input-validation
patterns:
- pattern: |
app.post($PATH, async (req, res) => {
...
$DB.$METHOD(req.body)
...
})
- pattern-not: |
app.post($PATH, async (req, res) => {
...
$SCHEMA.parse(...)
...
})
message: "POST endpoint without input validation"
languages: [javascript, typescript]
severity: WARNING
CI/CD Integration
GitHub Actions
name: SAST Security Scan
on:
pull_request:
branches: [main, develop]
push:
branches: [main]
jobs:
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Semgrep Scan
uses: semgrep/semgrep-action@v1
with:
config: >-
p/owasp-top-ten
p/secrets
p/typescript
generateSarif: "1"
env:
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: semgrep.sarif
if: always()
- name: Dependency Audit
run: npm audit --audit-level=high
- name: Check for Secrets
uses: trufflesecurity/trufflehog@main
with:
extra_args: --only-verified
Pre-commit Hook
# .pre-commit-config.yaml
repos:
- repo: https://github.com/semgrep/semgrep
rev: 'v1.60.0'
hooks:
- id: semgrep
args: ['--config', 'auto', '--error', '--severity', 'ERROR']
GitLab CI
sast:
stage: test
image: semgrep/semgrep
script:
- semgrep --config auto --config "p/secrets" --json --output gl-sast-report.json .
artifacts:
reports:
sast: gl-sast-report.json
rules:
- if: $CI_MERGE_REQUEST_ID
CLI Commands Quick Reference
# Full scan with auto rules
semgrep --config auto .
# OWASP + Secrets
semgrep --config "p/owasp-top-ten" --config "p/secrets" .
# Only errors (for CI gates)
semgrep --config auto --error --severity ERROR .
# JSON output for processing
semgrep --config auto --json --output report.json .
# Scan specific files
semgrep --config auto src/api/ src/auth/
# Diff-aware (only changed code)
semgrep --config auto --baseline-commit origin/main
# Custom rules
semgrep --config .semgrep.yml .
# Exclude test files
semgrep --config auto --exclude="*test*" --exclude="*spec*" .
# With metrics disabled (privacy)
semgrep --config auto --metrics=off .
Severity Classification Guide
| Severity | Criteria | Action | SLA |
|---|---|---|---|
| CRITICAL | Exploitable RCE, SQLi, auth bypass, data breach | Fix IMMEDIATELY, block deploy | < 1 hour |
| HIGH | XSS, SSRF, IDOR, missing auth, weak crypto | Fix before production | < 24 hours |
| MEDIUM | Missing validation, verbose errors, weak headers | Fix in current sprint | < 1 week |
| LOW | Debug mode, outdated lib (no known CVE), info leak | Fix in backlog | < 1 month |
Quick Decision Tree
Is user input involved?
YES -> Does it reach a dangerous sink (DB, exec, DOM)?
YES -> Is it sanitized/validated?
NO -> CRITICAL (injection)
YES -> Check sanitizer adequacy -> MEDIUM if weak
NO -> MEDIUM (missing validation)
NO -> Is it a configuration issue?
YES -> Affects security posture?
YES -> HIGH (misconfiguration)
NO -> LOW (best practice)
NO -> Is it a dependency issue?
YES -> Known CVE?
YES -> Match CVE severity
NO -> LOW (outdated)
NO -> Informational
Resources
- OWASP Top 10 (2021)
- Semgrep Registry
- Semgrep Rule Syntax
- CWE Database
- NIST NVD
- Snyk Vulnerability DB
Remember: SAST catches patterns, not intent. Always verify findings with manual review. A finding is only a vulnerability if it can be exploited in the application's specific context.