When to use
This skill is always active. It applies to every project where AI agents generate code. 45% of AI-generated code has security flaws. This skill catches them before they reach production.
Critical Rules
1. Never Hardcode Secrets
Wrong:
const API_KEY = "sk-1234567890abcdef";
const dbUrl = "postgres://user:password123@localhost/db";
Correct:
const API_KEY = process.env.API_KEY;
const dbUrl = process.env.DATABASE_URL;
2. Always Validate Input
Wrong:
app.post("/api/users", (req, res) => {
db.insert(req.body);
});
Correct:
const userSchema = z.object({ email: z.string().email(), name: z.string().min(1) });
app.post("/api/users", (req, res) => {
const parsed = userSchema.safeParse(req.body);
if (!parsed.success) return res.status(400).json(parsed.error.flatten());
db.insert(parsed.data);
});
3. Enforce Authentication on Every Route
Wrong:
app.get('/api/users', (req, res) => {
const users = await db.select('*').from('users');
res.json(users);
});
Correct:
app.get("/api/users", authMiddleware, async (req, res) => {
const users = await db.select("*").from("users").where("tenant_id", req.user.tenantId);
res.json(users);
});
4. Use Row-Level Security (Supabase/Postgres)
Wrong:
const { data } = await supabase.from("profiles").select("*");
Correct:
CREATE POLICY "Users can only read own profile" ON profiles
FOR SELECT USING (auth.uid() = id);
const { data } = await supabase.from("profiles").select("*");
5. Sanitize Database Queries
Wrong:
db.query(`SELECT * FROM users WHERE id = ${userId}`);
Correct:
db.query("SELECT * FROM users WHERE id = $1", [userId]);
6. Set CORS Properly
Wrong:
app.use(cors({ origin: "*" }));
Correct:
app.use(cors({ origin: ["https://yourdomain.com", "https://app.yourdomain.com"] }));
7. Add Rate Limiting
Wrong:
app.post("/login", loginHandler);
app.post("/signup", signupHandler);
Correct:
const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 5 });
app.post("/login", authLimiter, loginHandler);
app.post("/signup", authLimiter, signupHandler);
8. Use HTTPS Only
Wrong:
const apiUrl = "http://api.example.com";
Correct:
const apiUrl =
process.env.NODE_ENV === "production" ? "https://api.example.com" : "http://localhost:3000";
app.use(helmet.hsts({ maxAge: 31536000, includeSubDomains: true }));
9. Hash Passwords Properly
Wrong:
const hash = crypto.createHash("md5").update(password).digest("hex");
// or: const hash = password; (plain text)
Correct:
const hash = await bcrypt.hash(password, 12);
10. Set Security Headers
Wrong:
app.listen(3000);
Correct:
app.use(helmet());
app.use((req, res, next) => {
res.setHeader("X-Content-Type-Options", "nosniff");
res.setHeader("X-Frame-Options", "DENY");
res.setHeader("Content-Security-Policy", "default-src 'self'");
next();
});
11. Handle Errors Without Leaking Info
Wrong:
catch (err) {
res.status(500).json({ error: err.stack, message: err.message });
}
Correct:
catch (err) {
if (process.env.NODE_ENV === 'production') {
res.status(500).json({ error: 'Internal server error' });
} else {
res.status(500).json({ error: err.message });
}
}
12. Validate File Uploads
Wrong:
app.post("/upload", upload.single("file"), (req, res) => {
fs.writeFileSync(`./uploads/${req.file.originalname}`, req.file.buffer);
});
Correct:
const ALLOWED_TYPES = ["image/jpeg", "image/png"];
const MAX_SIZE = 5 * 1024 * 1024;
app.post("/upload", upload.single("file"), (req, res) => {
if (!ALLOWED_TYPES.includes(req.file.mimetype) || req.file.size > MAX_SIZE) {
return res.status(400).json({ error: "Invalid file" });
}
const ext = path.extname(req.file.originalname);
const safeName = `${uuid()}${ext}`;
fs.writeFileSync(`./uploads/${safeName}`, req.file.buffer);
});
13. Use Environment-Specific Config
Wrong:
const config = { dbUrl: "localhost", debug: true };
Correct:
const config = {
dbUrl: process.env.DATABASE_URL,
debug: process.env.NODE_ENV !== "production",
};
14. Add Logging (But Not Secrets)
Wrong:
console.log("User logged in:", user);
Correct:
const { password, ...safeUser } = user;
logger.info("User logged in", { userId: safeUser.id, email: safeUser.email });
15. Set Proper Cookie Flags
Wrong:
res.cookie("session", token);
document.cookie = "session=" + token;
Correct:
res.cookie("session", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
path: "/",
maxAge: 3600000,
});
16. Implement Proper Session Management
Wrong:
const token = jwt.sign({ userId }, secret, { expiresIn: "365d" });
Correct:
const accessToken = jwt.sign({ userId }, secret, { expiresIn: "15m" });
const refreshToken = jwt.sign({ userId }, refreshSecret, { expiresIn: "7d" });
17. Protect Against CSRF
Wrong:
app.post("/api/transfer", (req, res) => {
transferMoney(req.body);
});
Correct:
app.use(csrf({ cookie: true }));
app.post("/api/transfer", (req, res) => {
if (req.headers["x-csrf-token"] !== req.csrfToken()) {
return res.status(403).json({ error: "Invalid CSRF token" });
}
transferMoney(req.body);
});
18. Use Content Security Policy
Wrong:
// No CSP header - inline scripts allowed
Correct:
app.use((req, res, next) => {
res.setHeader(
"Content-Security-Policy",
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'",
);
next();
});
19. Validate Redirects
Wrong:
res.redirect(req.query.next || "/");
Correct:
const ALLOWED_REDIRECTS = ["/dashboard", "/profile", "/settings"];
const next = req.query.next;
const target = ALLOWED_REDIRECTS.includes(next) ? next : "/";
res.redirect(target);
20. Check Dependencies for Vulnerabilities
Wrong:
// Never running npm audit
Correct:
npm audit
npm audit fix
# In CI: .github/workflows/ci.yml
- run: npm audit --audit-level=high