Vibecosystem sast-patterns
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.
git clone https://github.com/vibeeval/vibecosystem
T=$(mktemp -d) && git clone --depth=1 https://github.com/vibeeval/vibecosystem "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/sast-patterns" ~/.claude/skills/vibeeval-vibecosystem-sast-patterns && rm -rf "$T"
skills/sast-patterns/SKILL.mdSAST 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.