
Security Expert
- 213 installs
- 41 repo stars
- Updated March 30, 2026
- personamanagmentlayer/pcl
Review auth flows, secrets handling, input validation, dependency risks, and deployment hardening before release to reduce exploitable vulnerabilities.
About
Security-expert enables Claude to audit SaaS, API, and mobile codepaths for common vulnerabilities, enforce least-privilege auth, validate inputs, and apply release-ready hardening so teams ship with fewer exploitable gaps.
- Threat modeling
- Auth and secrets review
- Input validation hardening
- Dependency vulnerability checks
- Secure deployment defaults
Security Expert by the numbers
- 213 all-time installs (skills.sh)
- +3 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #753 of 2,203 Security skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/personamanagmentlayer/pcl --skill security-expertAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 213 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 30, 2026 |
| Repository | personamanagmentlayer/pcl ↗ |
What it does
Review auth flows, secrets handling, input validation, dependency risks, and deployment hardening before release to reduce exploitable vulnerabilities.
Files
Security Expert
Expert guidance for application security, vulnerability assessment, penetration testing, OWASP Top 10, secure coding practices, and security architecture.
Core Concepts
Security Principles
- Defense in depth
- Least privilege
- Secure by default
- Fail securely
- Complete mediation
- Separation of duties
- Zero trust architecture
OWASP Top 10 (2021)
1. Broken Access Control 2. Cryptographic Failures 3. Injection 4. Insecure Design 5. Security Misconfiguration 6. Vulnerable and Outdated Components 7. Identification and Authentication Failures 8. Software and Data Integrity Failures 9. Security Logging and Monitoring Failures 10. Server-Side Request Forgery (SSRF)
Security Domains
- Authentication & Authorization
- Cryptography
- Input validation
- Session management
- Error handling
- Secure communications
- Data protection
OWASP Top 10 Vulnerabilities
1. Broken Access Control
// ❌ Vulnerable: No authorization check
app.get('/api/users/:id/profile', async (req, res) => {
const profile = await db.users.findById(req.params.id);
res.json(profile);
});
// ✅ Secure: Verify user owns the resource
app.get('/api/users/:id/profile', authenticate, async (req, res) => {
if (req.user.id !== req.params.id && !req.user.isAdmin) {
return res.status(403).json({ error: 'Forbidden' });
}
const profile = await db.users.findById(req.params.id);
res.json(profile);
});
// ✅ Better: Use middleware
const authorizeResource = (resourceType) => async (req, res, next) => {
const resourceId = req.params.id;
const resource = await db[resourceType].findById(resourceId);
if (!resource) {
return res.status(404).json({ error: 'Not found' });
}
if (resource.userId !== req.user.id && !req.user.isAdmin) {
return res.status(403).json({ error: 'Forbidden' });
}
req.resource = resource;
next();
};
app.delete('/api/posts/:id', authenticate, authorizeResource('posts'), async (req, res) => {
await req.resource.delete();
res.status(204).send();
});2. Injection (SQL, NoSQL, Command)
// ❌ SQL Injection vulnerability
app.get('/users', (req, res) => {
const query = `SELECT * FROM users WHERE name = '${req.query.name}'`;
db.query(query, (err, results) => {
res.json(results);
});
});
// Attack: ?name=' OR '1'='1
// ✅ Secure: Use parameterized queries
app.get('/users', async (req, res) => {
const results = await db.query(
'SELECT * FROM users WHERE name = ?',
[req.query.name]
);
res.json(results);
});
// ❌ Command Injection
const { exec } = require('child_process');
app.post('/convert', (req, res) => {
exec(`convert ${req.body.filename} output.pdf`, (err, stdout) => {
res.send(stdout);
});
});
// Attack: filename="; rm -rf / #"
// ✅ Secure: Use safe APIs, validate input
const { spawn } = require('child_process');
app.post('/convert', (req, res) => {
const filename = path.basename(req.body.filename); // Remove path traversal
if (!/^[a-zA-Z0-9_-]+\.(jpg|png)$/.test(filename)) {
return res.status(400).json({ error: 'Invalid filename' });
}
const process = spawn('convert', [filename, 'output.pdf']);
// Handle process output safely
});
// ❌ NoSQL Injection (MongoDB)
app.post('/login', async (req, res) => {
const user = await User.findOne({
username: req.body.username,
password: req.body.password
});
});
// Attack: {"username": {"$ne": null}, "password": {"$ne": null}}
// ✅ Secure: Sanitize input, use proper types
app.post('/login', async (req, res) => {
const { username, password } = req.body;
if (typeof username !== 'string' || typeof password !== 'string') {
return res.status(400).json({ error: 'Invalid input' });
}
const user = await User.findOne({ username });
if (!user || !(await bcrypt.compare(password, user.passwordHash))) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Create session
});3. Cross-Site Scripting (XSS)
// ❌ Reflected XSS
app.get('/search', (req, res) => {
res.send(`<h1>Results for: ${req.query.q}</h1>`);
});
// Attack: ?q=<script>alert(document.cookie)</script>
// ✅ Secure: Escape output
const escapeHtml = (unsafe) => {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
};
app.get('/search', (req, res) => {
res.send(`<h1>Results for: ${escapeHtml(req.query.q)}</h1>`);
});
// ✅ Better: Use templating engine with auto-escaping
app.get('/search', (req, res) => {
res.render('search', { query: req.query.q }); // Automatically escaped
});
// ✅ Content Security Policy
app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;"
);
next();
});4. Cross-Site Request Forgery (CSRF)
// ❌ Vulnerable: No CSRF protection
app.post('/api/transfer', authenticate, async (req, res) => {
await transferMoney(req.user.id, req.body.to, req.body.amount);
res.json({ success: true });
});
// ✅ Secure: Use CSRF tokens
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });
app.get('/transfer', csrfProtection, (req, res) => {
res.render('transfer', { csrfToken: req.csrfToken() });
});
app.post('/api/transfer', csrfProtection, authenticate, async (req, res) => {
await transferMoney(req.user.id, req.body.to, req.body.amount);
res.json({ success: true });
});
// ✅ Also use SameSite cookies
app.use(session({
cookie: {
httpOnly: true,
secure: true,
sameSite: 'strict'
}
}));5. Security Misconfiguration
// ❌ Exposed sensitive information
app.use((err, req, res, next) => {
res.status(500).json({
error: err.message,
stack: err.stack // Exposes internal details
});
});
// ✅ Secure: Generic error messages
app.use((err, req, res, next) => {
console.error(err); // Log internally
if (process.env.NODE_ENV === 'production') {
res.status(500).json({ error: 'Internal server error' });
} else {
res.status(500).json({ error: err.message, stack: err.stack });
}
});
// ✅ Security headers
const helmet = require('helmet');
app.use(helmet());
// ✅ Disable unnecessary features
app.disable('x-powered-by');
// ✅ Rate limiting
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
app.use('/api/', limiter);Authentication & Authorization
Password Security
const bcrypt = require('bcrypt');
const SALT_ROUNDS = 12;
// Hash password
async function hashPassword(password) {
// Validate password strength
if (password.length < 12) {
throw new Error('Password must be at least 12 characters');
}
if (!/[A-Z]/.test(password) || !/[a-z]/.test(password) ||
!/[0-9]/.test(password) || !/[^A-Za-z0-9]/.test(password)) {
throw new Error('Password must contain uppercase, lowercase, number, and special character');
}
return await bcrypt.hash(password, SALT_ROUNDS);
}
// Verify password
async function verifyPassword(password, hash) {
return await bcrypt.compare(password, hash);
}
// Registration
app.post('/register', async (req, res) => {
const { email, password } = req.body;
// Check if user exists
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(400).json({ error: 'Email already registered' });
}
// Hash password
const passwordHash = await hashPassword(password);
// Create user
const user = await User.create({
email: email.toLowerCase(),
passwordHash
});
res.status(201).json({ id: user.id });
});JWT Authentication
const jwt = require('jsonwebtoken');
const JWT_SECRET = process.env.JWT_SECRET; // Use strong secret
const JWT_EXPIRY = '15m';
const REFRESH_TOKEN_EXPIRY = '7d';
// Generate tokens
function generateTokens(user) {
const accessToken = jwt.sign(
{ userId: user.id, email: user.email },
JWT_SECRET,
{ expiresIn: JWT_EXPIRY }
);
const refreshToken = jwt.sign(
{ userId: user.id, type: 'refresh' },
JWT_SECRET,
{ expiresIn: REFRESH_TOKEN_EXPIRY }
);
return { accessToken, refreshToken };
}
// Verify token middleware
function authenticate(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'No token provided' });
}
const token = authHeader.substring(7);
try {
const decoded = jwt.verify(token, JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
if (err.name === 'TokenExpiredError') {
return res.status(401).json({ error: 'Token expired' });
}
return res.status(401).json({ error: 'Invalid token' });
}
}
// Login
app.post('/login', async (req, res) => {
const { email, password } = req.body;
const user = await User.findOne({ email: email.toLowerCase() });
if (!user || !(await verifyPassword(password, user.passwordHash))) {
// Generic error to prevent username enumeration
return res.status(401).json({ error: 'Invalid credentials' });
}
// Update last login
await user.update({ lastLoginAt: new Date() });
const tokens = generateTokens(user);
res.json(tokens);
});
// Refresh token
app.post('/refresh', async (req, res) => {
const { refreshToken } = req.body;
try {
const decoded = jwt.verify(refreshToken, JWT_SECRET);
if (decoded.type !== 'refresh') {
return res.status(401).json({ error: 'Invalid token type' });
}
const user = await User.findById(decoded.userId);
if (!user) {
return res.status(401).json({ error: 'User not found' });
}
const tokens = generateTokens(user);
res.json(tokens);
} catch (err) {
return res.status(401).json({ error: 'Invalid refresh token' });
}
});Multi-Factor Authentication (MFA)
const speakeasy = require('speakeasy');
const QRCode = require('qrcode');
// Generate MFA secret
app.post('/mfa/setup', authenticate, async (req, res) => {
const secret = speakeasy.generateSecret({
name: `MyApp (${req.user.email})`
});
// Store secret temporarily
await redis.setex(`mfa:setup:${req.user.id}`, 300, secret.base32);
// Generate QR code
const qrCode = await QRCode.toDataURL(secret.otpauth_url);
res.json({
secret: secret.base32,
qrCode
});
});
// Verify and enable MFA
app.post('/mfa/verify', authenticate, async (req, res) => {
const { token } = req.body;
const secret = await redis.get(`mfa:setup:${req.user.id}`);
if (!secret) {
return res.status(400).json({ error: 'MFA setup expired' });
}
const verified = speakeasy.totp.verify({
secret,
encoding: 'base32',
token,
window: 2
});
if (!verified) {
return res.status(400).json({ error: 'Invalid token' });
}
// Enable MFA for user
await User.update(req.user.id, {
mfaEnabled: true,
mfaSecret: secret
});
await redis.del(`mfa:setup:${req.user.id}`);
res.json({ success: true });
});
// Login with MFA
app.post('/login/mfa', async (req, res) => {
const { email, password, mfaToken } = req.body;
const user = await User.findOne({ email });
if (!user || !(await verifyPassword(password, user.passwordHash))) {
return res.status(401).json({ error: 'Invalid credentials' });
}
if (user.mfaEnabled) {
if (!mfaToken) {
return res.status(401).json({ error: 'MFA token required' });
}
const verified = speakeasy.totp.verify({
secret: user.mfaSecret,
encoding: 'base32',
token: mfaToken,
window: 2
});
if (!verified) {
return res.status(401).json({ error: 'Invalid MFA token' });
}
}
const tokens = generateTokens(user);
res.json(tokens);
});Cryptography
Encryption at Rest
const crypto = require('crypto');
const ALGORITHM = 'aes-256-gcm';
const KEY = Buffer.from(process.env.ENCRYPTION_KEY, 'hex'); // 32 bytes
function encrypt(plaintext) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(ALGORITHM, KEY, iv);
let encrypted = cipher.update(plaintext, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
return {
iv: iv.toString('hex'),
encrypted,
authTag: authTag.toString('hex')
};
}
function decrypt(iv, encrypted, authTag) {
const decipher = crypto.createDecipheriv(
ALGORITHM,
KEY,
Buffer.from(iv, 'hex')
);
decipher.setAuthTag(Buffer.from(authTag, 'hex'));
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
// Store sensitive data
async function storeSensitiveData(userId, data) {
const { iv, encrypted, authTag } = encrypt(JSON.stringify(data));
await db.sensitiveData.create({
userId,
iv,
encrypted,
authTag
});
}Secure Random Generation
// ✅ Cryptographically secure random
const crypto = require('crypto');
function generateSecureToken(length = 32) {
return crypto.randomBytes(length).toString('hex');
}
function generateSecureId() {
return crypto.randomUUID();
}
// ❌ Don't use Math.random() for security
const insecureToken = Math.random().toString(36); // Predictable!Input Validation
const { body, validationResult } = require('express-validator');
app.post('/api/users',
// Validation rules
body('email').isEmail().normalizeEmail(),
body('name').trim().isLength({ min: 2, max: 100 }).escape(),
body('age').optional().isInt({ min: 18, max: 120 }),
body('website').optional().isURL(),
async (req, res) => {
// Check validation results
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Process validated data
const user = await User.create(req.body);
res.status(201).json(user);
}
);
// File upload validation
const multer = require('multer');
const upload = multer({
limits: {
fileSize: 5 * 1024 * 1024, // 5MB max
},
fileFilter: (req, file, cb) => {
// Check file type
const allowedMimes = ['image/jpeg', 'image/png', 'image/gif'];
if (!allowedMimes.includes(file.mimetype)) {
return cb(new Error('Invalid file type'));
}
// Check file extension
const ext = path.extname(file.originalname).toLowerCase();
if (!['.jpg', '.jpeg', '.png', '.gif'].includes(ext)) {
return cb(new Error('Invalid file extension'));
}
cb(null, true);
}
});
app.post('/upload', upload.single('image'), (req, res) => {
// Verify file content matches extension
// Store with random filename to prevent path traversal
const filename = `${crypto.randomUUID()}${path.extname(req.file.originalname)}`;
// Save file...
});Security Headers
const helmet = require('helmet');
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"], // Avoid unsafe-inline in production
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:', 'https:'],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
},
frameguard: {
action: 'deny'
},
noSniff: true,
xssFilter: true,
}));
// Additional headers
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-XSS-Protection', '1; mode=block');
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
res.setHeader('Permissions-Policy', 'geolocation=(), microphone=(), camera=()');
next();
});Secure Logging
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
// Log security events
function logSecurityEvent(event, details) {
logger.warn('Security Event', {
event,
...details,
timestamp: new Date().toISOString()
});
}
// Failed login attempts
app.post('/login', async (req, res) => {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user || !(await verifyPassword(password, user.passwordHash))) {
logSecurityEvent('failed_login', {
email,
ip: req.ip,
userAgent: req.headers['user-agent']
});
return res.status(401).json({ error: 'Invalid credentials' });
}
// Success
logSecurityEvent('successful_login', {
userId: user.id,
ip: req.ip
});
// Generate tokens...
});
// ❌ Don't log sensitive data
logger.info('User data', user); // May contain passwordHash, tokens
// ✅ Sanitize before logging
logger.info('User data', {
id: user.id,
email: user.email,
// Omit sensitive fields
});Best Practices
Secure Development Lifecycle
1. Threat modeling 2. Security requirements 3. Secure coding standards 4. Code review 5. Security testing (SAST/DAST) 6. Vulnerability scanning 7. Penetration testing 8. Security monitoring
Defense in Depth
- Multiple layers of security
- Assume breach mentality
- Principle of least privilege
- Input validation at every layer
- Output encoding
- Secure configuration
Security Testing
- Static Application Security Testing (SAST)
- Dynamic Application Security Testing (DAST)
- Interactive Application Security Testing (IAST)
- Software Composition Analysis (SCA)
- Penetration testing
- Bug bounty programs
Anti-Patterns to Avoid
❌ Storing passwords in plaintext: Always hash with bcrypt/argon2 ❌ Rolling your own crypto: Use established libraries ❌ Trusting user input: Validate and sanitize everything ❌ Exposing sensitive errors: Use generic error messages ❌ No rate limiting: Implement rate limiting on all endpoints ❌ Weak session management: Use secure, httpOnly cookies ❌ No logging: Log security events for monitoring ❌ Hardcoded secrets: Use environment variables
Resources
- OWASP: https://owasp.org/
- OWASP Top 10: https://owasp.org/Top10/
- CWE Top 25: https://cwe.mitre.org/top25/
- NIST Guidelines: https://www.nist.gov/cybersecurity
- Security Headers: https://securityheaders.com/
- SSL Labs: https://www.ssllabs.com/