security-auditor
Scan for OWASP Top 10 vulnerabilities and security best practices. Checks for SQL injection, XSS, authentication issues, sensitive data exposure, and other common security risks.
When & Why to Use This Skill
The Security Auditor skill is a comprehensive automated security scanner designed to identify and remediate OWASP Top 10 vulnerabilities within a codebase. By analyzing code patterns for risks like SQL injection, Cross-Site Scripting (XSS), and broken authentication, it provides developers with actionable insights and secure coding patterns to prevent data breaches and ensure robust application security before deployment.
Use Cases
- Pre-PR Security Auditing: Automatically scan new code changes for security flaws like hardcoded secrets or unparameterized queries before merging into the main branch.
- Vulnerability Remediation: Identify unsafe code patterns (e.g., dangerouslySetInnerHTML) and receive immediate guidance on implementing safe alternatives like DOMPurify sanitization.
- Access Control Verification: Audit API endpoints and tRPC procedures to ensure that ownership checks and role-based access controls are correctly enforced to prevent IDOR vulnerabilities.
- Compliance & Best Practices: Perform a comprehensive check against OWASP standards to ensure sensitive data exposure is minimized and security headers are correctly configured.
- DevSecOps Integration: Use as a first line of defense in the development lifecycle to catch critical security risks during the coding phase rather than after deployment.
| name | security-auditor |
|---|---|
| description | Scan for OWASP Top 10 vulnerabilities and security best practices. Checks for SQL injection, XSS, authentication issues, sensitive data exposure, and other common security risks. |
| allowed-tools | Read, Grep, Glob |
Security Vulnerability Auditor
Purpose
Comprehensive security scanner focused on OWASP Top 10 vulnerabilities:
- Injection (SQL, NoSQL, Command)
- Broken Authentication
- Sensitive Data Exposure
- XML External Entities (XXE)
- Broken Access Control
- Security Misconfiguration
- Cross-Site Scripting (XSS)
- Insecure Deserialization
- Using Components with Known Vulnerabilities
- Insufficient Logging & Monitoring
Auto-Invocation Triggers
This skill activates when:
- User asks to "check security" or "audit security"
- Before deployment to production
- When modifying authentication/authorization code
- When adding new tRPC procedures
- When working with user input or database queries
- Before creating a pull request for sensitive features
Vulnerability Patterns
1. SQL Injection (CRITICAL 🔴)
What to Look For:
- String concatenation in SQL queries
- Unparameterized database queries
- Dynamic table/column names from user input
Safe Pattern (Drizzle ORM):
// ✅ SAFE - Parameterized query
const users = await db.query.users.findMany({
where: eq(users.email, userEmail)
});
// ✅ SAFE - Prepared statement
const result = await db.select()
.from(users)
.where(eq(users.id, userId));
Unsafe Pattern:
// ❌ VULNERABLE - String concatenation
const query = `SELECT * FROM users WHERE email = '${userEmail}'`;
await db.execute(query);
// ❌ VULNERABLE - Template literal
const result = await db.execute(
`SELECT * FROM ${tableName} WHERE id = ${userId}`
);
Grep Patterns:
db.execute.*\$\{
db.query.*\+.*user
SELECT.*\$\{
2. Cross-Site Scripting (XSS) (HIGH 🟠)
What to Look For:
dangerouslySetInnerHTMLwithout sanitization- Direct DOM manipulation with user input
- Unescaped user data in templates
Safe Pattern:
// ✅ SAFE - React auto-escapes
<div>{user.name}</div>
// ✅ SAFE - Sanitized HTML
import DOMPurify from 'dompurify';
<div dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(userContent)
}} />
Unsafe Pattern:
// ❌ VULNERABLE - Unsanitized HTML
<div dangerouslySetInnerHTML={{ __html: userContent }} />
// ❌ VULNERABLE - Direct DOM manipulation
element.innerHTML = userInput;
// ❌ VULNERABLE - Unescaped in template
const html = `<div>${userInput}</div>`;
Grep Patterns:
dangerouslySetInnerHTML
\.innerHTML\s*=
eval\(
Function\(
3. Broken Authentication (CRITICAL 🔴)
What to Look For:
- Missing authentication on sensitive routes
- JWT secrets in code or environment files
- Weak password requirements
- Session fixation vulnerabilities
- Missing rate limiting on auth endpoints
Safe Pattern:
// ✅ SAFE - Protected procedure
export const deleteUser = protectedProcedure
.input(z.object({ id: z.string() }))
.mutation(async ({ input, ctx }) => {
// ctx.user is guaranteed to exist
if (ctx.user.role !== 'admin') {
throw new TRPCError({ code: 'FORBIDDEN' });
}
return userService.delete(input.id);
});
// ✅ SAFE - Session validation via Lucia
const session = await lucia.validateSession(sessionId);
if (!session) throw new TRPCError({ code: 'UNAUTHORIZED' });
Unsafe Pattern:
// ❌ VULNERABLE - Public procedure for sensitive action
export const deleteUser = publicProcedure
.input(z.object({ id: z.string() }))
.mutation(({ input }) => db.delete(users).where(eq(users.id, input.id)));
// ❌ VULNERABLE - Hardcoded JWT secret
const JWT_SECRET = "my-super-secret-key";
// ❌ VULNERABLE - No rate limiting
export const login = publicProcedure
.input(loginSchema)
.mutation(async ({ input }) => {
// No rate limiting, vulnerable to brute force
});
Checks:
- All sensitive procedures use
protectedProcedure - JWT secrets from environment, not hardcoded
- Rate limiting on auth endpoints
- Password hashing with bcrypt/argon2
4. Sensitive Data Exposure (HIGH 🟠)
What to Look For:
- Passwords or hashes in responses
- API keys in logs or error messages
- Sensitive data in URLs or query params
- Unencrypted sensitive data transmission
- Sensitive data in client-side storage
Safe Pattern:
// ✅ SAFE - Exclude sensitive fields
export async function getById(id: string) {
const { password, ...user } = await db.query.users.findFirst({
where: eq(users.id, id)
});
return user;
}
// ✅ SAFE - Selective field return
const user = await db.select({
id: users.id,
email: users.email,
name: users.name
// password intentionally omitted
}).from(users).where(eq(users.id, id));
Unsafe Pattern:
// ❌ VULNERABLE - Returns full user object (includes password hash)
export async function getById(id: string) {
return await db.query.users.findFirst({
where: eq(users.id, id)
});
}
// ❌ VULNERABLE - Logging sensitive data
console.log('Login attempt:', { email, password });
// ❌ VULNERABLE - Sensitive data in error
throw new Error(`Login failed for ${email} with password ${password}`);
Checks:
- User objects never include password fields
- No logging of passwords, tokens, or API keys
- HTTPS enforced in production
- Sensitive data not in localStorage (use httpOnly cookies)
5. Broken Access Control (HIGH 🟠)
What to Look For:
- Missing ownership checks
- Horizontal privilege escalation (user accessing another user's data)
- Vertical privilege escalation (user accessing admin functions)
- Insecure direct object references (IDOR)
Safe Pattern:
// ✅ SAFE - Ownership verification
export async function updateProject(
id: string,
data: Partial<Project>,
userId: string
) {
const project = await getById(id);
// Verify ownership
if (project.userId !== userId) {
throw new TRPCError({ code: 'FORBIDDEN' });
}
return await db.update(projects)
.set(data)
.where(eq(projects.id, id));
}
// ✅ SAFE - Role-based access
export const adminAction = protectedProcedure
.use(requireRole('admin'))
.mutation(async ({ ctx }) => {
// Only admins reach here
});
Unsafe Pattern:
// ❌ VULNERABLE - No ownership check
export async function updateProject(id: string, data: Partial<Project>) {
// Any authenticated user can update any project!
return await db.update(projects)
.set(data)
.where(eq(projects.id, id));
}
// ❌ VULNERABLE - IDOR
export const getProject = publicProcedure
.input(z.object({ id: z.string() }))
.query(({ input }) => {
// Returns any project by ID, no access control
return db.query.projects.findFirst({
where: eq(projects.id, input.id)
});
});
Checks:
- All data access checks ownership (userId match)
- Admin functions check role/permissions
- Resource IDs validated against user context
- No direct object reference without authorization
6. Security Misconfiguration (MEDIUM 🟡)
What to Look For:
- CORS misconfiguration (allow all origins)
- Missing security headers
- Default credentials
- Verbose error messages in production
- Unnecessary services enabled
Safe Pattern:
// ✅ SAFE - Specific CORS origins
app.use(cors({
origin: process.env.NODE_ENV === 'production'
? ['https://studio535.com', 'https://app.studio535.com']
: ['http://localhost:5000'],
credentials: true
}));
// ✅ SAFE - Security headers
app.use(helmet({
contentSecurityPolicy: true,
hsts: true,
noSniff: true
}));
// ✅ SAFE - Generic error in production
if (process.env.NODE_ENV === 'production') {
return res.status(500).json({ error: 'Internal server error' });
} else {
return res.status(500).json({ error: error.message, stack: error.stack });
}
Unsafe Pattern:
// ❌ VULNERABLE - Allow all origins
app.use(cors({ origin: '*' }));
// ❌ VULNERABLE - No security headers
// Missing helmet() or equivalent
// ❌ VULNERABLE - Verbose errors in production
app.use((err, req, res, next) => {
res.status(500).json({
error: err.message,
stack: err.stack,
query: req.query
});
});
Checks:
- CORS configured for specific origins
- Helmet or equivalent security headers
- Generic error messages in production
- No default credentials in code
7. Command Injection (CRITICAL 🔴)
What to Look For:
- User input in shell commands
- Unvalidated file paths
- System command execution with template literals
Safe Pattern:
// ✅ SAFE - Use library instead of shell
import { readFile } from 'fs/promises';
const content = await readFile(filePath, 'utf-8');
// ✅ SAFE - Whitelist validation
const allowedCommands = ['build', 'test', 'lint'];
if (!allowedCommands.includes(userCommand)) {
throw new Error('Invalid command');
}
exec(userCommand); // Now safe
Unsafe Pattern:
// ❌ VULNERABLE - User input in command
exec(`convert ${userFilePath} output.png`);
// ❌ VULNERABLE - Template literal with user data
child_process.exec(`git clone ${userRepo}`);
// ❌ VULNERABLE - Unvalidated file path
fs.readFile(`./uploads/${userFileName}`, ...);
Grep Patterns:
exec\(.*\$\{
spawn\(.*\+
child_process.*user
8. Insufficient Logging (LOW 🟢)
What to Look For:
- Authentication failures not logged
- Authorization failures not logged
- Critical actions not audited
- No logging of security events
Safe Pattern:
// ✅ SAFE - Log security events
logger.warn('Failed login attempt', {
email: input.email,
ip: req.ip,
timestamp: new Date()
});
logger.info('User deleted', {
deletedBy: ctx.user.id,
deletedUser: userId,
timestamp: new Date()
});
Checks:
- Failed login attempts logged
- Authorization failures logged
- Admin actions audited
- Rate limit violations logged
Scan Process
Step 1: Critical Vulnerabilities
Scan for patterns that could lead to immediate compromise:
- SQL injection patterns
- Command injection patterns
- Hardcoded secrets
- Missing authentication on sensitive routes
Step 2: High-Risk Issues
Scan for patterns that could lead to data exposure:
- XSS vulnerabilities
- Sensitive data in responses
- Broken access control
- Missing ownership checks
Step 3: Medium-Risk Issues
Scan for configuration and design issues:
- CORS misconfiguration
- Missing security headers
- Weak error handling
- Missing rate limiting
Step 4: Best Practices
Check for security best practices:
- Logging of security events
- Input validation completeness
- Secure defaults
- Defense in depth
Output Format
Critical Issues Found (🔴)
🔴 CRITICAL SECURITY ISSUES FOUND
SQL Injection Risk:
- server/routers.ts:45
Pattern: db.execute(`SELECT * FROM ${table}`)
Risk: Arbitrary SQL execution
Fix: Use parameterized queries with Drizzle ORM
Command Injection Risk:
- server/services/file.ts:89
Pattern: exec(`convert ${userFile}`)
Risk: Arbitrary command execution
Fix: Use library instead of shell command
⚠️ DO NOT DEPLOY until these are fixed!
High-Risk Issues (🟠)
🟠 HIGH-RISK SECURITY ISSUES
Broken Access Control:
- server/services/project.ts:34
Issue: No ownership check before update
Risk: Users can modify other users' projects
Fix: Add userId verification
Sensitive Data Exposure:
- server/services/user.ts:23
Issue: Returning full user object (includes password hash)
Risk: Password hashes exposed to client
Fix: Exclude sensitive fields
All Clear
✅ Security Audit Passed
Checked:
✅ SQL Injection: No vulnerabilities
✅ XSS: React auto-escaping in place
✅ Authentication: All sensitive routes protected
✅ Access Control: Ownership checks present
✅ Data Exposure: Sensitive fields excluded
✅ Configuration: Security headers enabled
✅ Logging: Security events logged
No critical or high-risk issues found.
Checklist
Before Every Deployment
- No SQL injection patterns
- No command injection patterns
- All sensitive procedures use
protectedProcedure - User objects exclude password fields
- CORS configured for specific origins
- Security headers enabled (Helmet)
- No hardcoded secrets in code
- Access control checks present
- Rate limiting on auth endpoints
- Security events logged
Monthly Security Review
- Review recent security incidents
- Update vulnerability patterns
- Check for new OWASP guidelines
- Audit dependencies for vulnerabilities
- Review access control logic
- Test authentication flows
- Review logging coverage
Integration
CI/CD Pipeline
Add to GitHub Actions:
- name: Security Audit
run: |
npm audit --audit-level=moderate
# Run security-auditor skill via Claude
Pre-Deployment Checklist
Always run before deploying to production:
- Security-auditor skill
- npm audit
- Dependency vulnerability scan
- Manual review of auth/access control changes
Examples
Example 1: SQL Injection Found
🔴 CRITICAL: SQL Injection Vulnerability
Location: server/services/search.ts:45
Code:
const query = `SELECT * FROM projects WHERE name LIKE '%${searchTerm}%'`;
return await db.execute(query);
Risk: HIGH - Arbitrary SQL execution possible
Impact: Full database access, data theft, data destruction
Fix:
const results = await db.select()
.from(projects)
.where(like(projects.name, `%${searchTerm}%`));
Example 2: Missing Access Control
🟠 HIGH: Broken Access Control
Location: server/services/project.ts:78
Code:
export async function deleteProject(id: string) {
return await db.delete(projects).where(eq(projects.id, id));
}
Risk: MEDIUM - Users can delete other users' projects
Impact: Data loss, unauthorized modifications
Fix:
export async function deleteProject(id: string, userId: string) {
const project = await getById(id);
if (project.userId !== userId) {
throw new TRPCError({ code: 'FORBIDDEN' });
}
return await db.delete(projects).where(eq(projects.id, id));
}
Example 3: All Clear
✅ Security Audit Complete
Files Scanned: 47
Patterns Checked: 25
Issues Found: 0
Security Status: GOOD
Ready for deployment ✅
Resources
- OWASP Top 10: https://owasp.org/www-project-top-ten/
- OWASP Cheat Sheets: https://cheatsheetseries.owasp.org/
- tRPC Security: https://trpc.io/docs/server/authorization
- Lucia Auth Docs: https://lucia-auth.com/
This skill provides a first line of defense against common security vulnerabilities. It's not a replacement for penetration testing, but catches most issues before they reach production.