
security-auditor
by gregsuptown
SKILL.md
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.
Score
Total Score
Based on repository quality metrics
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
3ヶ月以内に更新がある
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
Reviews
Reviews coming soon