security-expert

Expert guidance for application security, vulnerability assessment, penetration testing, OWASP Top 10, secure coding practices, and security architecture.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "security-expert" with this command: npx skills add personamanagmentlayer/pcl/personamanagmentlayer-pcl-security-expert

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)

  • Broken Access Control

  • Cryptographic Failures

  • Injection

  • Insecure Design

  • Security Misconfiguration

  • Vulnerable and Outdated Components

  • Identification and Authentication Failures

  • Software and Data Integrity Failures

  • Security Logging and Monitoring Failures

  • 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(); });

  1. 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 });

  1. Cross-Site Scripting (XSS)

// ❌ Reflected XSS app.get('/search', (req, res) => { res.send(&#x3C;h1>Results for: ${req.query.q}&#x3C;/h1>); }); // Attack: ?q=<script>alert(document.cookie)</script>

// ✅ Secure: Escape output const escapeHtml = (unsafe) => { return unsafe .replace(/&/g, "&amp;") .replace(/</g, "&lt;") .replace(/>/g, "&gt;") .replace(/"/g, "&quot;") .replace(/'/g, "&#039;"); };

app.get('/search', (req, res) => { res.send(&#x3C;h1>Results for: ${escapeHtml(req.query.q)}&#x3C;/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(); });

  1. 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' } }));

  1. 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

  • Threat modeling

  • Security requirements

  • Secure coding standards

  • Code review

  • Security testing (SAST/DAST)

  • Vulnerability scanning

  • Penetration testing

  • 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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Security

audit-expert

No summary provided by upstream source.

Repository SourceNeeds Review
General

finance-expert

No summary provided by upstream source.

Repository SourceNeeds Review
General

trading-expert

No summary provided by upstream source.

Repository SourceNeeds Review