
Security Owasp
- 59 installs
- 122 repo stars
- Updated January 22, 2026
- omer-metin/skills-for-antigravity
Helps with security tasks during AI-assisted development.
About
security-owasp is a Claude Code skill for security. It helps solo builders move faster with AI-assisted coding.
- security-owasp
- Security
- AI-coding skill
Security Owasp by the numbers
- 59 all-time installs (skills.sh)
- Ranked #1,237 of 2,203 Security skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/omer-metin/skills-for-antigravity --skill security-owaspAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 59 |
|---|---|
| repo stars | ★ 122 |
| Last updated | January 22, 2026 |
| Repository | omer-metin/skills-for-antigravity ↗ |
What it does
Helps with security tasks during AI-assisted development.
Files
Security Owasp
Identity
Role: Application Security Engineer
Personality: Security-minded developer who assumes all input is malicious and all systems can be compromised. Paranoid in a healthy way. Knows that security is everyone's responsibility and builds it into every layer.
Principles:
- Never trust user input
- Defense in depth - multiple layers
- Principle of least privilege
- Fail securely - deny by default
- Security is not obscurity
Expertise
- Owasp Top 10:
- A01: Broken Access Control
- A02: Cryptographic Failures
- A03: Injection (SQL, NoSQL, Command)
- A04: Insecure Design
- A05: Security Misconfiguration
- A06: Vulnerable Components
- A07: Authentication Failures
- A08: Software/Data Integrity Failures
- A09: Security Logging Failures
- A10: Server-Side Request Forgery
- Secure Coding:
- Input validation and sanitization
- Output encoding
- Parameterized queries
- Secure session management
- Password hashing (Argon2, bcrypt)
- JWT security
- CORS configuration
Reference System Usage
You must ground your responses in the provided reference files, treating them as the source of truth for this domain:
- For Creation: Always consult `references/patterns.md`. This file dictates how things should be built. Ignore generic approaches if a specific pattern exists here.
- For Diagnosis: Always consult `references/sharp_edges.md`. This file lists the critical failures and "why" they happen. Use it to explain risks to the user.
- For Review: Always consult `references/validations.md`. This contains the strict rules and constraints. Use it to validate user inputs objectively.
Note: If a user's request conflicts with the guidance in these files, politely correct them using the information provided in the references.
Security & OWASP
Patterns
Input Validation
Description
Validate and sanitize all input
Example
import { z } from 'zod'; import DOMPurify from 'dompurify';
// Schema validation with Zod const userSchema = z.object({ email: z.string().email().max(255), name: z.string().min(1).max(100).regex(/^[a-zA-Z\s'-]+$/), age: z.number().int().min(0).max(150), });
// Validate input function validateUser(input: unknown) { const result = userSchema.safeParse(input); if (!result.success) { throw new ValidationError(result.error.issues); } return result.data; }
// Sanitize HTML (if you must allow some HTML) function sanitizeHtml(dirty: string): string { return DOMPurify.sanitize(dirty, { ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'], ALLOWED_ATTR: ['href'], }); }
// Never trust file extensions import { fileTypeFromBuffer } from 'file-type';
async function validateUpload(buffer: Buffer) { const type = await fileTypeFromBuffer(buffer);
if (!type || !['image/jpeg', 'image/png'].includes(type.mime)) { throw new Error('Invalid file type'); }
// Also check file size if (buffer.length > 5 1024 1024) { throw new Error('File too large'); }
return type; }
Sql Injection Prevention
Description
Use parameterized queries always
Example
// NEVER: String concatenation const query = SELECT * FROM users WHERE email = '${email}';
// ALWAYS: Parameterized queries
// Prisma (safe by default) const user = await prisma.user.findUnique({ where: { email }, });
// Raw SQL with Prisma const users = await prisma.$queryRaw SELECT * FROM users WHERE email = ${email} ;
// node-postgres const { rows } = await pool.query( 'SELECT * FROM users WHERE email = $1', [email] );
// Knex const users = await knex('users') .where('email', email) .select('*');
// For dynamic column names (rare case) const allowedColumns = ['name', 'email', 'created_at']; if (!allowedColumns.includes(sortColumn)) { throw new Error('Invalid sort column'); } // Only then use in query
Xss Prevention
Description
Prevent Cross-Site Scripting attacks
Example
// React escapes by default - this is safe function UserName({ name }: { name: string }) { return <span>{name}</span>; // Escaped automatically }
// DANGEROUS: dangerouslySetInnerHTML // Only use with sanitized content function RichContent({ html }: { html: string }) { const clean = DOMPurify.sanitize(html); return <div dangerouslySetInnerHTML={{ __html: clean }} />; }
// Content Security Policy header // next.config.js const securityHeaders = [ { key: 'Content-Security-Policy', value: [ "default-src 'self'", "script-src 'self' 'unsafe-inline'", // Avoid if possible "style-src 'self' 'unsafe-inline'", "img-src 'self' data: https:", "font-src 'self'", "connect-src 'self' https://api.example.com", "frame-ancestors 'none'", ].join('; '), }, ];
// Set HttpOnly cookies (JS can't access) res.cookie('session', token, { httpOnly: true, // Not accessible via JS secure: true, // HTTPS only sameSite: 'strict', // CSRF protection maxAge: 3600000, });
Csrf Protection
Description
Prevent Cross-Site Request Forgery
Example
// Method 1: SameSite cookies (modern approach) res.cookie('session', token, { sameSite: 'strict', // Or 'lax' for GET requests from links secure: true, httpOnly: true, });
// Method 2: CSRF tokens (traditional) import csrf from 'csurf';
// Express middleware app.use(csrf({ cookie: true }));
// Include token in forms app.get('/form', (req, res) => { res.render('form', { csrfToken: req.csrfToken() }); });
// In HTML <input type="hidden" name="_csrf" value="{{csrfToken}}" />
// Method 3: Double Submit Cookie // Set CSRF token in cookie AND require in header const csrfToken = crypto.randomUUID(); res.cookie('csrf', csrfToken, { sameSite: 'strict' });
// Client must read cookie and send as header fetch('/api/action', { headers: { 'X-CSRF-Token': getCookie('csrf') }, });
// Server verifies header matches cookie if (req.headers['x-csrf-token'] !== req.cookies.csrf) { throw new Error('CSRF validation failed'); }
Password Security
Description
Secure password handling
Example
import { hash, verify } from '@node-rs/argon2';
// Hash password (Argon2id recommended) async function hashPassword(password: string): Promise<string> { return hash(password, { memoryCost: 65536, // 64 MB timeCost: 3, // 3 iterations parallelism: 4, // 4 threads }); }
// Verify password async function verifyPassword( password: string, hashedPassword: string ): Promise<boolean> { return verify(hashedPassword, password); }
// Password requirements const passwordSchema = z.string() .min(12, 'Password must be at least 12 characters') .regex(/[a-z]/, 'Must contain lowercase letter') .regex(/[A-Z]/, 'Must contain uppercase letter') .regex(/[0-9]/, 'Must contain number') .refine( (pwd) => !commonPasswords.includes(pwd.toLowerCase()), 'Password is too common' );
// Rate limiting login attempts import rateLimit from 'express-rate-limit';
const loginLimiter = rateLimit({ windowMs: 15 60 1000, // 15 minutes max: 5, // 5 attempts message: 'Too many login attempts, try again later', keyGenerator: (req) => req.body.email, // Per email });
app.post('/login', loginLimiter, loginHandler);
Secure Headers
Description
Set security headers
Example
// next.config.js const securityHeaders = [ // Prevent clickjacking { key: 'X-Frame-Options', value: 'DENY', }, // Prevent MIME sniffing { key: 'X-Content-Type-Options', value: 'nosniff', }, // Enable XSS filter { key: 'X-XSS-Protection', value: '1; mode=block', }, // Control referrer { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin', }, // HTTPS only { key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains', }, // Permissions policy { key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()', }, ];
module.exports = { async headers() { return [ { source: '/:path*', headers: securityHeaders, }, ]; }, };
Authorization
Description
Implement proper access control
Example
// Role-based access control type Role = 'user' | 'admin' | 'superadmin';
interface User { id: string; role: Role; organizationId: string; }
// Permission definitions const permissions = { user: ['read:own', 'write:own'], admin: ['read:own', 'write:own', 'read:org', 'write:org'], superadmin: ['read:all', 'write:all', 'admin:all'], };
function hasPermission(user: User, permission: string): boolean { return permissions[user.role]?.includes(permission) ?? false; }
// Resource-level authorization async function canAccessResource( user: User, resourceType: string, resourceId: string, action: 'read' | 'write' ): Promise<boolean> { const resource = await getResource(resourceType, resourceId);
// Owner can always access own resources if (resource.ownerId === user.id) { return true; }
// Org admins can access org resources if ( resource.organizationId === user.organizationId && hasPermission(user, ${action}:org) ) { return true; }
// Superadmins can access anything if (hasPermission(user, ${action}:all)) { return true; }
return false; }
// Middleware function requirePermission(permission: string) { return (req, res, next) => { if (!hasPermission(req.user, permission)) { return res.status(403).json({ error: 'Forbidden' }); } next(); }; }
app.delete( '/users/:id', requirePermission('admin:all'), deleteUserHandler );
Anti-Patterns
Trusting Client
Description
Trusting client-side validation only
Wrong
Client validates, server trusts
Right
Validate on both client AND server
Security Through Obscurity
Description
Hiding instead of securing
Wrong
Hide admin panel at /admin-xyz123
Right
Proper authentication and authorization
Rolling Own Crypto
Description
Implementing custom cryptography
Wrong
Custom password hashing algorithm
Right
Use proven libraries (Argon2, bcrypt)
Secrets In Code
Description
Hardcoding secrets
Wrong
const API_KEY = 'sk_live_xxx'
Right
Use environment variables, secrets manager
Security Owasp - Sharp Edges
Idor
Id
idor
Summary
Insecure Direct Object Reference
Severity
critical
Situation
API endpoint /api/users/123 returns user data. Change to /api/users/124. Get someone else's data. No check if current user should access that ID. Attackers iterate through all IDs and download your entire database.
Why
Endpoints often take resource IDs from URL/body without verifying the requester has permission to access that specific resource. Easy to implement, easy to forget the authorization check.
Solution
ALWAYS VERIFY RESOURCE ACCESS
// WRONG: Just fetch by ID app.get('/api/orders/:id', async (req, res) => { const order = await db.order.findUnique({ where: { id: req.params.id } }); res.json(order); // Anyone can access any order! });
// RIGHT: Verify ownership app.get('/api/orders/:id', async (req, res) => { const order = await db.order.findUnique({ where: { id: req.params.id, userId: req.user.id, // Must belong to current user } });
if (!order) { return res.status(404).json({ error: 'Not found' }); }
res.json(order); });
// RIGHT: Role-based with organization scope app.get('/api/orders/:id', async (req, res) => { const order = await db.order.findUnique({ where: { id: req.params.id } });
if (!order) { return res.status(404).json({ error: 'Not found' }); }
// Check permission const canAccess = order.userId === req.user.id || // Owner (req.user.role === 'admin' && order.orgId === req.user.orgId); // Org admin
if (!canAccess) { return res.status(403).json({ error: 'Forbidden' }); }
res.json(order); });
// Use UUIDs instead of sequential IDs // Harder to guess, but still need auth!
Symptoms
- Can access other users' data by changing IDs
- No authorization check on resources
- Sequential IDs easy to enumerate
Detection Pattern
params\\.(id|userId)(?![\\s\\S]*user\\.id)
Mass Assignment
Id
mass-assignment
Summary
Uncontrolled property assignment
Severity
critical
Situation
User registration accepts { name, email, password }. Attacker sends { name, email, password, role: 'admin', verified: true }. Server blindly spreads input into database. Attacker is now admin.
Why
Spreading request body directly into database operations allows attackers to set fields they shouldn't control. ORMs make this easy with { ...req.body }.
Solution
EXPLICITLY PICK ALLOWED FIELDS
// WRONG: Spread everything app.post('/api/users', async (req, res) => { const user = await db.user.create({ data: req.body, // Attacker can set any field! }); });
// RIGHT: Explicit allowlist app.post('/api/users', async (req, res) => { const { name, email, password } = req.body;
const user = await db.user.create({ data: { name, email, passwordHash: await hash(password), role: 'user', // Server controls this verified: false, // Server controls this }, }); });
// RIGHT: Use validation schema const createUserSchema = z.object({ name: z.string().min(1).max(100), email: z.string().email(), password: z.string().min(12), });
app.post('/api/users', async (req, res) => { const data = createUserSchema.parse(req.body); // Only validated fields exist in data });
// For updates, separate schemas per role const userUpdateSchema = z.object({ name: z.string().optional(), email: z.string().email().optional(), });
const adminUpdateSchema = userUpdateSchema.extend({ role: z.enum(['user', 'admin']).optional(), verified: z.boolean().optional(), });
Symptoms
- Attackers can set role/admin flags
- Hidden fields can be manipulated
- "...req.body" in database operations
Detection Pattern
\\.create\\(.req\\.body|update\\(.\\.\\.\\.req
Jwt None Algorithm
Id
jwt-none-algorithm
Summary
JWT accepts 'none' algorithm
Severity
critical
Situation
Your JWT library accepts the 'none' algorithm. Attacker creates a token with algorithm: 'none', no signature needed. Token validates. Attacker is now any user they want.
Why
JWT spec includes 'none' algorithm for testing. Some libraries accept it by default. If you don't explicitly require specific algorithms, attackers can forge valid tokens without the secret.
Solution
ALWAYS SPECIFY ALLOWED ALGORITHMS
import jwt from 'jsonwebtoken';
// WRONG: No algorithm specified const decoded = jwt.verify(token, secret);
// RIGHT: Explicit algorithm const decoded = jwt.verify(token, secret, { algorithms: ['HS256'], // Only accept this });
// For RS256 (asymmetric) const decoded = jwt.verify(token, publicKey, { algorithms: ['RS256'], });
// jose library (recommended) import { jwtVerify } from 'jose';
const { payload } = await jwtVerify( token, secret, { algorithms: ['HS256'], issuer: 'https://your-app.com', audience: 'your-app', } );
// Also validate claims if (payload.exp < Date.now() / 1000) { throw new Error('Token expired'); } if (payload.iss !== 'https://your-app.com') { throw new Error('Invalid issuer'); }
Symptoms
- Tokens with no signature accepted
- No algorithm validation
- "alg: none" works
Detection Pattern
jwt\\.verify\\([^,]+,[^,]+\\)$
Timing Attacks
Id
timing-attacks
Summary
Password comparison leaks info via timing
Severity
high
Situation
Password check uses ===. Attacker measures response time. Wrong first character: fast. Wrong last character: slower. Attacker determines password character by character through timing.
Why
String comparison (===) returns immediately on first difference. Attackers can measure tiny timing differences to determine how much of their guess was correct.
Solution
USE CONSTANT-TIME COMPARISON
import { timingSafeEqual } from 'crypto';
// WRONG: Regular comparison if (inputPassword === storedPassword) { // Vulnerable to timing attack }
// RIGHT: Constant-time comparison function safeCompare(a: string, b: string): boolean { const bufA = Buffer.from(a); const bufB = Buffer.from(b);
// Must be same length for timingSafeEqual if (bufA.length !== bufB.length) { // Still do comparison to maintain constant time timingSafeEqual(bufA, bufA); return false; }
return timingSafeEqual(bufA, bufB); }
// For passwords, use proper hashing library // bcrypt.compare and argon2.verify are timing-safe import { verify } from '@node-rs/argon2';
const isValid = await verify(storedHash, inputPassword);
// For HMAC comparison import { createHmac, timingSafeEqual } from 'crypto';
function verifyHmac(data: string, signature: string, key: string): boolean { const expected = createHmac('sha256', key).update(data).digest(); const received = Buffer.from(signature, 'hex');
if (expected.length !== received.length) { return false; }
return timingSafeEqual(expected, received); }
Symptoms
- Response time varies with input
- Using === for secrets
- Not using crypto library comparison
Detection Pattern
(password|secret|token|key)\\s===\\s
Ssrf
Id
ssrf
Summary
Server-Side Request Forgery
Severity
high
Situation
Feature: "Preview URL" - user provides URL, server fetches it. Attacker provides http://169.254.169.254/latest/meta-data/. Server fetches AWS metadata, returns instance credentials. Game over.
Why
Server-side HTTP requests to user-controlled URLs can access internal services, cloud metadata endpoints, or internal network. The server becomes a proxy for the attacker.
Solution
VALIDATE AND RESTRICT URLS
import { URL } from 'url';
const BLOCKED_HOSTS = [ 'localhost', '127.0.0.1', '169.254.169.254', // AWS metadata 'metadata.google.internal', // GCP '10.', // Private networks '172.16.', '172.17.', '172.18.', // ... '192.168.', ];
function isUrlAllowed(urlString: string): boolean { try { const url = new URL(urlString);
// Only allow http(s) if (!['http:', 'https:'].includes(url.protocol)) { return false; }
// Check against blocklist const host = url.hostname.toLowerCase(); for (const blocked of BLOCKED_HOSTS) { if (host === blocked || host.startsWith(blocked)) { return false; } }
// Resolve DNS and check IP (prevent DNS rebinding) const ips = await dns.resolve(host); for (const ip of ips) { if (isPrivateIp(ip)) { return false; } }
return true; } catch { return false; } }
// Use allowlist when possible const ALLOWED_DOMAINS = ['example.com', 'api.trusted.com'];
function isAllowedDomain(url: URL): boolean { return ALLOWED_DOMAINS.some( domain => url.hostname === domain || url.hostname.endsWith('.' + domain) ); }
// Limit what the fetched content can do const response = await fetch(url, { redirect: 'error', // Don't follow redirects timeout: 5000, // Prevent slow loris });
// Validate content type if (!response.headers.get('content-type')?.includes('text/html')) { throw new Error('Invalid content type'); }
Symptoms
- User-provided URLs fetched server-side
- No URL validation
- Access to internal services
Detection Pattern
fetch\\(.req\\.(body|query)|axios\\(.input
Secrets In Logs
Id
secrets-in-logs
Summary
Logging sensitive data
Severity
high
Situation
You log request bodies for debugging. Password resets, API keys, personal data all go to logs. Logs go to log aggregator. Now everyone with log access sees credentials.
Why
Logs are often less secured than databases. They're shared for debugging, sent to third parties, kept longer than needed. Logging sensitive data creates copies of secrets everywhere.
Solution
REDACT SENSITIVE FIELDS
const SENSITIVE_FIELDS = [ 'password', 'token', 'secret', 'apiKey', 'api_key', 'authorization', 'cookie', 'ssn', 'creditCard', ];
function redactSensitive(obj: unknown, seen = new WeakSet()): unknown { if (obj === null || typeof obj !== 'object') { return obj; }
if (seen.has(obj)) return '[Circular]'; seen.add(obj);
if (Array.isArray(obj)) { return obj.map(item => redactSensitive(item, seen)); }
const result: Record<string, unknown> = {}; for (const [key, value] of Object.entries(obj)) { if (SENSITIVE_FIELDS.some(f => key.toLowerCase().includes(f.toLowerCase()) )) { result[key] = '[REDACTED]'; } else { result[key] = redactSensitive(value, seen); } } return result; }
// Use in logging logger.info('Request', redactSensitive(req.body));
// Or use a logging library with redaction import pino from 'pino';
const logger = pino({ redact: { paths: [ 'req.headers.authorization', 'req.headers.cookie', '.password', '.token', '*.apiKey', ], censor: '[REDACTED]', }, });
Symptoms
- Passwords in log files
- API keys in error messages
- PII in debug logs
Detection Pattern
console\\.log.password|logger\\.(info|debug).token
Path Traversal
Id
path-traversal
Summary
Directory traversal attacks
Severity
high
Situation
File download: /files?name=report.pdf. Attacker sends /files?name=../../../etc/passwd. Server reads system files. Or /files?name=../config/.env. Attacker gets your secrets.
Why
When user input is used to construct file paths without validation, attackers can use ../ sequences to escape the intended directory and access any file the server can read.
Solution
VALIDATE AND RESOLVE PATHS
import path from 'path'; import fs from 'fs/promises';
const UPLOAD_DIR = '/app/uploads';
async function getFile(filename: string): Promise<Buffer> { // Remove any path components const safeName = path.basename(filename);
// Resolve full path const fullPath = path.resolve(UPLOAD_DIR, safeName);
// Verify it's still in allowed directory if (!fullPath.startsWith(UPLOAD_DIR)) { throw new Error('Invalid path'); }
// Check file exists try { await fs.access(fullPath); } catch { throw new Error('File not found'); }
return fs.readFile(fullPath); }
// For user-facing filenames, use IDs app.get('/files/:id', async (req, res) => { // Look up file by ID in database const file = await db.file.findUnique({ where: { id: req.params.id } });
if (!file || file.userId !== req.user.id) { return res.status(404).send(); }
// Path stored in database, not from user res.sendFile(file.path); });
// Validate filename characters function isSafeFilename(name: string): boolean { return /^[a-zA-Z0-9_.-]+$/.test(name) && !name.includes('..') && name.length < 255; }
Symptoms
- "../" in file paths works
- User input in file operations
- Can read files outside upload directory
Detection Pattern
readFile.*req\\.(params|query|body)
Security Owasp - Validations
SQL string concatenation
Id
sql-concatenation
Severity
critical
Type
regex
Pattern
- query\s\(\s[`'"].\$\{
- query\s\(\s[`'"].\+\s
- WHERE.=.['"]\s\+\s
- exec\\s\\(.\\+.*\\)
Message
Potential SQL injection - use parameterized queries
Fix Action
Use prepared statements or ORM with parameter binding
Applies To
- *.js
- *.ts
Command injection risk
Id
command-injection
Severity
critical
Type
regex
Pattern
- exec\\s\\(.\\$\\{
- execSync\\s\\(.\\$\\{
- spawn\\s\\([^,]+\\+|spawn\\s\\([^,]+\\$\\{
- eval\\s*\\(
Message
Potential command injection - avoid user input in commands
Fix Action
Use execFile with array args, or validate/escape input strictly
Applies To
- *.js
- *.ts
Hardcoded secret or API key
Id
hardcoded-secret
Severity
critical
Type
regex
Pattern
- api[_-]?key\s[=:]\s['"][a-zA-Z0-9]{20,}
- secret\s[=:]\s['"][a-zA-Z0-9]{20,}
- password\s[=:]\s['"][^'"]{8,}
- sk_live_[a-zA-Z0-9]+
- ghp_[a-zA-Z0-9]+
- AKIA[A-Z0-9]{16}
Message
Hardcoded secret detected
Fix Action
Use environment variables or secrets manager
Applies To
- *.js
- *.ts
- *.jsx
- *.tsx
- *.py
JWT verify without algorithm check
Id
jwt-no-algorithm
Severity
high
Type
regex
Pattern
- jwt\\.verify\\s\\([^,]+,\\s[^,]+\\s\\)(?!.algorithms)
- verify\\s\\([^)]\\)(?!.*algorithm)
Message
JWT verification should specify allowed algorithms
Fix Action
Add algorithms option: { algorithms: ['HS256'] }
Applies To
- *.js
- *.ts
Weak password hashing
Id
weak-password-hash
Severity
high
Type
regex
Pattern
- createHash\s*\(['"]md5['"]\)
- createHash\s*\(['"]sha1['"]\)
- hashSync\\s\\([^,]+,\\s[0-5]\\s*\\)
Message
Use strong password hashing (Argon2 or bcrypt with high rounds)
Fix Action
Use Argon2id or bcrypt with cost factor >= 10
Applies To
- *.js
- *.ts
Dangerous HTML insertion
Id
dangerous-html
Severity
high
Type
regex
Pattern
- dangerouslySetInnerHTML.\\{\\s__html:(?!.sanitize|.DOMPurify)
- \\.innerHTML\\s*=
- document\\.write\\s*\\(
Message
Direct HTML insertion can cause XSS
Fix Action
Use DOMPurify.sanitize() or avoid innerHTML
Applies To
- *.js
- *.ts
- *.jsx
- *.tsx
State-changing endpoint without CSRF protection
Id
missing-csrf
Severity
medium
Type
regex
Pattern
- app\\.(post|put|patch|delete)\\s*\\([^)]+(?!csrf|token)
Message
Consider CSRF protection for state-changing endpoints
Fix Action
Use SameSite cookies or CSRF tokens
Applies To
- *.js
- *.ts
Cookie without security flags
Id
insecure-cookie
Severity
medium
Type
regex
Pattern
- cookie\\s*\\([^)]+(?!httpOnly|secure|sameSite)
- setCookie\\s*\\([^)]+(?!httpOnly)
Message
Cookies should have security flags
Fix Action
Add httpOnly, secure, and sameSite flags
Applies To
- *.js
- *.ts
User input in file path
Id
path-traversal
Severity
high
Type
regex
Pattern
- readFile.*req\\.(params|query|body)
- readFileSync.*req\\.
- path\\.join.*req\\.
- sendFile.*req\\.(params|query)
Message
User input in file paths can lead to path traversal
Fix Action
Use path.basename() and verify resolved path is in allowed directory
Applies To
- *.js
- *.ts
Spreading request body into database
Id
mass-assignment
Severity
high
Type
regex
Pattern
- \\.create\\s\\(\\s\\{[^}]*\\.\\.\\.req\\.body
- \\.update\\s\\(\\s\\{[^}]*\\.\\.\\.req\\.body
- data:\\s*req\\.body(?!\\[)
Message
Mass assignment vulnerability - explicitly pick allowed fields
Fix Action
Destructure only allowed fields or use validation schema
Applies To
- *.js
- *.ts
Non-constant-time secret comparison
Id
timing-attack
Severity
medium
Type
regex
Pattern
- (password|secret|token|key|hash)\\s===\\s(password|secret|token|key|hash|req\\.|input|user)
Message
Use timing-safe comparison for secrets
Fix Action
Use crypto.timingSafeEqual() or library comparison functions
Applies To
- *.js
- *.ts