agents/security-reviewer/.opencode/skill/security-express/SKILL.md
Review Express.js security audit patterns for middleware and routes. Use for auditing Helmet.js, CORS, body-parser limits, and auth middleware. Use proactively when reviewing Express.js apps. Examples: - user: "Secure my Express app" → add Helmet.js and disable x-powered-by - user: "Check Express CORS config" → verify origin allowlists and credentials - user: "Review Express auth middleware" → check route order and coverage - user: "Scan for Express path traversal" → verify path normalization and validation - user: "Audit Express session config" → check secure, httpOnly, and sameSite flags
npx skillsauth add igorwarzocha/opencode-workflows security-expressInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Security audit patterns for Express.js applications covering essential security middleware, CORS configuration, auth patterns, and common vulnerabilities.
</overview> <rules>// ❌ Missing security headers
const app = express();
// ✓ Use Helmet
const helmet = require('helmet');
app.use(helmet());
Check if Helmet is installed and used. It sets:
// ❌ Default (header reveals framework)
const app = express();
// ✓ Disable fingerprinting
app.disable('x-powered-by');
// or: app.set('x-powered-by', false);
// ❌ CRITICAL: Allow all origins
app.use(cors());
app.use(cors({ origin: '*' }));
// ❌ HIGH: Reflect origin with credentials
app.use(cors({
origin: true, // Reflects any origin!
credentials: true
}));
// ✓ Explicit allowlist
app.use(cors({
origin: ['https://app.example.com', 'https://admin.example.com'],
credentials: true,
}));
// ✓ Function for dynamic validation
app.use(cors({
origin: (origin, callback) => {
const allowed = ['https://app.example.com'];
if (!origin || allowed.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
}));
// ❌ No limit (DoS risk)
app.use(express.json());
// ✓ Set reasonable limits
app.use(express.json({ limit: '100kb' }));
app.use(express.urlencoded({ extended: true, limit: '100kb' }));
</rules>
<vulnerabilities>
// ❌ No auth on admin routes
app.get('/api/admin/users', async (req, res) => {
res.json(await User.find());
});
// ✓ Auth middleware applied
app.get('/api/admin/users', requireAuth, requireAdmin, async (req, res) => {
res.json(await User.find());
});
// ❌ Wrong order - static files before auth
app.use(express.static('uploads')); // Exposed!
app.use(requireAuth);
// ✓ Auth before protected static files
app.use('/public', express.static('public')); // Intentionally public
app.use(requireAuth);
app.use('/uploads', express.static('uploads')); // Now protected
// Check: Is auth applied to all routes in admin router?
const adminRouter = express.Router();
adminRouter.use(requireAuth); // Applied to all routes below
adminRouter.get('/users', getUsers);
adminRouter.delete('/users/:id', deleteUser);
// ❌ Watch for routes defined BEFORE the middleware
const apiRouter = express.Router();
apiRouter.get('/health', getHealth); // No auth (intentional?)
apiRouter.use(requireAuth);
apiRouter.get('/users', getUsers); // Has auth
// ❌ String interpolation
const user = await db.query(`SELECT * FROM users WHERE id = ${req.params.id}`);
// ✓ Parameterized query
const user = await db.query('SELECT * FROM users WHERE id = $1', [req.params.id]);
// ❌ MongoDB injection
const user = await User.findOne({ email: req.body.email }); // If email is { $gt: "" }
// ✓ Validate input type
if (typeof req.body.email !== 'string') return res.status(400).json({ error: 'Invalid email' });
// ❌ User-controlled path
app.get('/files/:filename', (req, res) => {
res.sendFile(`./uploads/${req.params.filename}`); // ../../etc/passwd
});
// ✓ Validate and normalize
const path = require('path');
app.get('/files/:filename', (req, res) => {
const filename = path.basename(req.params.filename);
const filepath = path.join(__dirname, 'uploads', filename);
if (!filepath.startsWith(path.join(__dirname, 'uploads'))) {
return res.status(400).json({ error: 'Invalid path' });
}
res.sendFile(filepath);
});
// ❌ Stack traces in production
app.use((err, req, res, next) => {
res.status(500).json({ error: err.stack }); // Leaks internals
});
// ✓ Safe error handler
app.use((err, req, res, next) => {
console.error(err); // Log for debugging
res.status(500).json({ error: 'Internal server error' });
});
// ❌ Insecure session config
app.use(session({
secret: 'keyboard cat', // Hardcoded!
cookie: { secure: false }, // No HTTPS requirement
}));
// ✓ Secure config
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: true, // HTTPS only
httpOnly: true, // No JS access
sameSite: 'strict', // CSRF protection
maxAge: 1000 * 60 * 60 * 24, // 24 hours
},
}));
// Check for rate limiting on auth routes
const rateLimit = require('express-rate-limit');
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 attempts
message: 'Too many login attempts',
});
app.post('/api/login', authLimiter, loginHandler);
app.post('/api/register', authLimiter, registerHandler);
app.post('/api/forgot-password', authLimiter, forgotPasswordHandler);
</vulnerabilities>
<commands>
# Check if Helmet is used
rg -n 'helmet\\(' . -g "*.js" -g "*.ts"
# Check if x-powered-by is disabled
rg -n "x-powered-by" . -g "*.js" -g "*.ts"
# Check for helmet
rg "helmet" package.json
rg "require\\(['\"]helmet" .
rg "from ['\"]helmet" .
# Find CORS config
rg "cors\\(" . -g "*.js" -g "*.ts" -A 5
# Find routes without auth middleware
rg "app\\.(get|post|put|delete|patch)\\(" . -A 1 | grep -v "require.*[Aa]uth"
# Find string interpolation in queries
rg "(query|find|findOne|exec).*\\`" . -g "*.js" -g "*.ts"
# Check session config
rg "session\\(" . -A 10
</commands>
<checklist>
development
Handle structured co-authoring of professional documentation. Use for proposals, technical specs, and RFCs. Use proactively when a collaborative drafting process (Gathering -> Refinement -> Testing) is needed. Examples: - user: "Draft a technical RFC for the new API" -> follow Stage 1 context gathering - user: "Refine the introduction of this proposal" -> use iterative surgical edits - user: "Test if this document is clear for readers" -> run reader testing workflow
development
Handle Word document (.docx) creation, editing, and analysis with high-fidelity visual review. Use for professional reports, legal documents, and tracked changes. Use proactively when quality and precise formatting are critical. Examples: - user: "Create a professional report in Word" -> use python-docx with render loops - user: "Draft a legal contract with redlines" -> use ooxml redlining workflow - user: "Extract text from this DOCX while preserving structure" -> use pandoc markdown conversion
testing
Apply professional visual themes to documents and presentations. Use for styling artifacts with consistent color palettes and font pairings. Use proactively to quickly improve the aesthetic quality of deliverables. Examples: - user: "Apply a modern theme to this deck" -> use Modern Minimalist theme - user: "I want a tech aesthetic for this doc" -> apply Tech Innovation theme - user: "Create a custom theme for my project" -> generate new color/font specification
tools
Guide for creating effective opencode skills. Use for creating or updating skills that extend agent capabilities with specialized knowledge, workflows, or tool integrations. Examples: - user: "Create a skill for git workflows" → define SKILL.md with instructions and examples - user: "Add examples to my skill" → follow the user: "query" → action pattern - user: "Update skill description" → use literal block scalar and trigger contexts - user: "Structure a complex skill" → organize with scripts/ and references/ directories - user: "Validate my skill" → check structure, frontmatter, and discovery triggers