security-checklist/SKILL.md
Pre-deployment security audit for web applications. Use when reviewing code before shipping, auditing an existing application, or when users mention "security review," "ready to deploy," "going to production," or express concern about vulnerabilities. Covers authentication, input validation, secrets management, database security, and compliance basics.
npx skillsauth add jamditis/claude-skills-journalism security-checklistInstall 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.
Minimum viable security before shipping any web application. This is not exhaustive—it's the baseline that prevents obvious disasters.
Run through this checklist before any production deployment. If you can't check an item, fix it first.
crypto.randomBytes or equivalent)* in production)// BAD: SQL injection
db.query(`SELECT * FROM users WHERE id = ${req.params.id}`);
// GOOD: Parameterized query
db.query('SELECT * FROM users WHERE id = $1', [req.params.id]);
// BAD: XSS vulnerability
res.send(`<h1>Hello ${req.query.name}</h1>`);
// GOOD: Use template engine with auto-escaping
res.render('greeting', { name: req.query.name });
// BAD: Secrets in code
const API_KEY = 'sk_live_abc123';
// GOOD: Environment variables
const API_KEY = process.env.API_KEY;
# BAD: SQL injection
cursor.execute(f"SELECT * FROM users WHERE id = {user_id}")
# GOOD: Parameterized query
cursor.execute("SELECT * FROM users WHERE id = %s", [user_id])
# BAD: Pickle deserialization (RCE vulnerability)
data = pickle.loads(user_input)
# GOOD: Use JSON for untrusted data
data = json.loads(user_input)
# BAD: Debug mode in production
app.run(debug=True)
# GOOD: Debug off, proper error handling
app.run(debug=False)
// BAD: XSS via dangerouslySetInnerHTML
<div dangerouslySetInnerHTML={{ __html: userContent }} />
// GOOD: Let React escape content
<div>{userContent}</div>
// BAD: Storing tokens in localStorage (XSS accessible)
localStorage.setItem('token', authToken);
// BETTER: HttpOnly cookies (not accessible via JS)
// Set via server response header
// BAD: Exposing API keys in frontend code
const API_KEY = 'sk_live_abc123';
// GOOD: Proxy through your backend
const response = await fetch('/api/proxy/external-service');
const bcrypt = require('bcrypt');
// Hashing (on registration)
const saltRounds = 12;
const hashedPassword = await bcrypt.hash(plainPassword, saltRounds);
// Verification (on login)
const isValid = await bcrypt.compare(plainPassword, hashedPassword);
import bcrypt
# Hashing
hashed = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt(rounds=12))
# Verification
is_valid = bcrypt.checkpw(password.encode('utf-8'), hashed)
# .env
DATABASE_URL=postgresql://user:pass@localhost:5432/myapp
JWT_SECRET=your-256-bit-secret-here
API_KEY=sk_test_xxxxx
.env
.env.local
.env.*.local
*.pem
*.key
credentials.json
secrets/
// Node.js with dotenv
require('dotenv').config();
const dbUrl = process.env.DATABASE_URL;
// Fail fast if missing
if (!process.env.JWT_SECRET) {
throw new Error('JWT_SECRET environment variable is required');
}
-- Enable RLS on table
ALTER TABLE documents ENABLE ROW LEVEL SECURITY;
-- Users can only read their own documents
CREATE POLICY "Users can read own documents" ON documents
FOR SELECT USING (auth.uid() = user_id);
-- Users can only insert documents as themselves
CREATE POLICY "Users can insert own documents" ON documents
FOR INSERT WITH CHECK (auth.uid() = user_id);
-- Users can only update their own documents
CREATE POLICY "Users can update own documents" ON documents
FOR UPDATE USING (auth.uid() = user_id);
-- Users can only delete their own documents
CREATE POLICY "Users can delete own documents" ON documents
FOR DELETE USING (auth.uid() = user_id);
const cors = require('cors');
// BAD: Allow all origins
app.use(cors());
// GOOD: Specific origins
app.use(cors({
origin: ['https://myapp.com', 'https://www.myapp.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true
}));
const helmet = require('helmet');
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
}
}));
// Essential security headers
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-XSS-Protection', '1; mode=block');
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
res.setHeader('Content-Security-Policy', "default-src 'self'");
// NEVER log these:
// - Passwords (plain or hashed)
// - Session tokens / JWTs
// - API keys
// - Credit card numbers
// - Social security numbers
// - Full request bodies with user data
// BAD
console.log('Login attempt:', { email, password });
console.log('Request:', req.body);
// GOOD
console.log('Login attempt:', { email, success: false, reason: 'invalid_password' });
console.log('Request:', { endpoint: req.path, method: req.method, userId: req.user?.id });
git filter-branch or BFG Repo-Cleaner to remove from historyconsole.log, logger., print(testing
Configure install-time cooldowns for npm/bun (minimum release age) and run a sandboxed pre-install scan when the cooldown has to be bypassed. Use when the user asks about supply-chain attacks, npm/bun security, "minimum release age", a "cooldown" for installs, hardening against Shai-Hulud-class worms, or how to safely install a package that was just published. Also use after any recent supply-chain incident in the npm ecosystem.
tools
Generate CLAUDE.md project memory files that transfer institutional knowledge, not obvious information. Use when setting up new journalism projects, onboarding collaborators, or documenting project-specific quirks. Includes templates for editorial tools, event websites, publications, research projects, content pipelines, and digital archives.
development
Use when suggesting APIs for a project, looking for free data sources, building weekend projects that need external data, or when the user needs weather, news, finance, sports, ML, or entertainment data without paid subscriptions
development
Choose the correct CLAUDE.md or LESSONS.md template for journalism projects. Use when starting a new project, setting up documentation, or unsure which template category fits best. Provides decision trees and selection guidance for 6 journalism-focused template types.