security-toolkit/skills/security-checklist/SKILL.md
Pre-deployment security audit for web applications, organized by OWASP Top 10:2025 categories. 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 access control, supply chain, cryptography, injection, auth, integrity, logging, and exception handling.
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.
Pre-deployment security audit organized around the OWASP Top 10:2025 categories (released late 2025, succeeding the 2021 edition). This is the baseline that prevents obvious disasters — not a substitute for a real penetration test or threat model. For verification depth beyond this checklist, see OWASP ASVS 5.0 (https://owasp.org/www-project-application-security-verification-standard/). For API-specific scope, see OWASP API Security Top 10:2023 (https://owasp.org/API-Security/editions/2023/en/0x00-header/).
Security knowledge ages on a 6-12 month half-life. The recipes below were last verified on 2026-05-08; they may be stale by the time you read this. Before applying any pattern in this skill, fan out research scoped to the OWASP Top 10:2025 categories being audited so the recipes are interpreted against current authoritative sources, not against this file's snapshot.
Run the 4-angle research below by default. Skip ONLY when ALL of these hold:
Research skipped because <reason> note in your response."I think I know" / "moving fast" / "user wants this done quickly" / "already familiar" are NOT valid skip reasons. The whole point of this preamble is that future-you should not trust this skill body's defaults until current state is checked.
Each subagent returns ≤300 words of bullets with citations. Dispatch all 4 in a single message so they run concurrently.
Angle 1 — Authoritative standards. Have NIST / OWASP / IETF (RFCs and Internet-Drafts) / W3C / CISA published anything new about the OWASP Top 10:2025 categories being audited in the last 6-12 months? Look for: spec finalizations, deprecations, replacement specs, RFC publications, draft revisions, NIST SP updates, OWASP project version bumps. Cite by document number + publication date.
Angle 2 — Active exploitation. What's actively being exploited that targets the OWASP Top 10:2025 categories being audited? Pull from: CISA Known Exploited Vulnerabilities (KEV) catalog (filter to last 6-12 months), recent CVE / GHSA entries with high CVSS or in-the-wild exploitation, breach postmortems and incident reports (CSRB, vendor RCAs, security-vendor research). Surface CWE patterns dominating recent KEV adds. Cite by CVE number + advisory URL.
Angle 3 — Tooling and library state. Are the libraries this skill recommends still current? What are the latest major versions in the relevant package registry (npm / PyPI / RubyGems / crates.io)? Have any been deprecated, replaced, or merged into another project? Have any flipped a secure default? Look up current versions in: registry.npmjs.org, pypi.org, rubygems.org, crates.io, pkg.go.dev. Cite by package + version + release date.
Angle 4 — Practitioner discourse. What are practitioners and security teams talking about in the last 6 months? Pull from: OWASP Cheat Sheet Series (last-modified date matters), GitHub Security Lab posts, vendor security blogs (Cloudflare, Fastly, Snyk, Datadog, Wiz, GitGuardian), conference talks (Black Hat, DEF CON, OWASP Global AppSec, USENIX Security), SANS ISC, Krebs, recent OWASP project re-releases. Surface the patterns being adopted and the anti-patterns being called out. Cite by post URL + author + date.
After the 4 returns land, write a 1-paragraph "current state for the OWASP Top 10:2025 categories being audited, as of <today's date>" that names:
If the synthesis flags drift in this skill body's recipes (e.g., a spec finalized after 2026-05-08, a library now deprecated, a default flipped), call that out explicitly in your response and override the skill body where they conflict. The synthesis wins. The skill body is scaffolding, not scripture.
If subagents are not available in your runtime, the same shape applies in-line: do 4 sequential targeted searches (web search for standards, KEV catalog lookup, package registry version checks, recent cheat-sheet diff). Land the same 1-paragraph synthesis. Cost goes up; the protection does not change.
Walk all 10 categories before any production deployment. For each category: read the framing paragraph, run through the must-do items, and check the code-pattern references where they apply. After the walk, file findings as one issue per category with gaps. The flat 25-item Yes/No gate at the end is the pre-deploy summary, not the audit itself.
If you can't check an item, don't ship — fix it first.
Authorization failures are the most-exploited class on the web. The 2025 edition folds SSRF (Server-Side Request Forgery) into A01 because the underlying failure is the same: the server acts on a request it should have rejected. Active exemplars in 2024-2025 include broken object-level authorization in API endpoints (still the dominant API risk per OWASP API Top 10:2023 API1) and SSRF used as a pivot to cloud metadata endpoints.
localhost, 127.0.0.0/8, link-local (169.254.0.0/16), or cloud metadata IP (169.254.169.254) fetches; DNS rebinding protection on resolvers.-- 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);
Misconfiguration moved up the rankings (was A05 in 2021) because default-insecure framework settings keep shipping to production. The 2024 Snowflake / UNC5537 campaign is the canonical lesson: MFA was opt-in per tenant by default, and the campaign harvested credentials at scale before Snowflake flipped the default in 2024.
Access-Control-Allow-Origin: * flagged in production).debug=False, Django DEBUG=False, Express NODE_ENV=production, Rails RAILS_ENV=production.# .env (development only, never commit)
# Replace each <placeholder> with a real value generated locally.
# Generate JWT_SECRET with `openssl rand -hex 32` (256 bits).
DATABASE_URL=<your-database-connection-string>
JWT_SECRET=<32-byte-hex-secret>
API_KEY=<api-key-from-your-provider>
# .gitignore (mandatory)
.env
.env.local
.env.*.local
*.pem
*.key
credentials.json
secrets/
// Reading environment variables (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');
}
const cors = require('cors');
// SAFE: specific origins
app.use(cors({
origin: ['https://myapp.com', 'https://www.myapp.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true
}));
The unsafe pattern — cors() with no options, which sends Access-Control-Allow-Origin: * — is fine for dev but never for production with credentialed requests.
New category in 2025 that absorbs the old 2021 A06 "Vulnerable and Outdated Components." Broader than just patching: covers SBOM, build-system integrity, package provenance, and dependency-source trust. The xz-utils CVE-2024-3094 backdoor (March 2024) is the canonical "social-engineering of an open-source maintainer" lesson; Polyfill.io (June 2024) is the canonical "trusted CDN turned hostile" lesson.
latest in container images or ^x.y.z for security-critical libs).<script> and <link rel="stylesheet"> (Polyfill.io lesson — see A08 for the SRI checklist item).Compliance pointer: OMB M-26-05 (issued 2026-01-23) rescinded the federal-wide SBOM self-attestation mandate from M-22-18 + M-23-16. The EU Cyber Resilience Act (Reg 2024/2847) reporting obligations apply from 2026-09-11 and full obligations from 2027-12-11 — don't treat the US rescission as global rescission.
Use vetted high-level libraries. Don't roll crypto. Don't compose AES-CBC + HMAC by hand — use libsodium, AWS Encryption SDK, or Tink. The 2026 normative ceiling is TLS 1.3 by default, TLS 1.2 minimum, TLS 1.0/1.1 disabled (RFC 8996 / BCP 195).
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload (2 years).Secure, HttpOnly, SameSite=Lax or Strict).secure-auth skill in this bundle — link from here.const helmet = require('helmet');
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
frameAncestors: ["'none'"]
},
},
hsts: {
maxAge: 63072000,
includeSubDomains: true,
preload: true
}
}));
// Manual headers — modern set
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('Strict-Transport-Security', 'max-age=63072000; includeSubDomains; preload');
res.setHeader('Content-Security-Policy', "default-src 'self'; frame-ancestors 'none'");
// Note: X-Frame-Options is replaced by CSP frame-ancestors. The legacy XSS-protection header is deprecated — modern browsers ignore it.
CWE-78 OS command injection dominates CISA KEV in 2024-2025 (14 entries in 2024, 18 in 2025) — eclipsing classic SQL injection by volume. CWE-22 path traversal also climbed (9 in 2024, 13 in 2025). MOVEit CVE-2023-34362 remains the canonical SQLi exemplar.
The unsafe pattern — interpolating user input into a SQL template string and calling db.query on the result — is CWE-89. Always parameterize.
// SAFE: parameterized query, user input bound as $1
db.query('SELECT * FROM users WHERE id = $1', [req.params.id]);
# SAFE: parameterized query, user input bound as %s placeholder
cursor.execute("SELECT * FROM users WHERE id = %s", [user_id])
The unsafe pattern — building the SQL string with an f-string that interpolates user input — is CWE-89.
The Node child_process module exposes a shell-execution primitive (the one that runs a command string through /bin/sh) and an argv-list primitive (the spawn family with shell: false). For any input that touches user data, use the argv-list primitive.
const { spawn } = require('child_process');
// SAFE: argv-list invocation, shell disabled
// userInput is treated as a single argument, never re-parsed by a shell
spawn('convert', [userInput, 'output.png'], { shell: false });
The unsafe pattern — passing a template string with interpolated user input to the shell-execution primitive — is CWE-78. Don't do it. If you absolutely need shell features, allowlist-validate every component of the command first.
Python exposes shell-execution primitives (the OS shell-out function and subprocess.run(..., shell=True)) and an argv-list primitive (subprocess.run([...], shell=False)). For any input that touches user data, use the argv-list primitive.
import subprocess
# SAFE: argv-list invocation, shell disabled
# user_input is treated as a single argument, never re-parsed by a shell
subprocess.run(["convert", user_input, "output.png"], shell=False, check=True)
The unsafe patterns — passing an f-string with interpolated user input to the OS shell-out function or to a subprocess call with shell=True — are CWE-78. Don't do them. Allowlist-validate any path or filename component before it reaches a shell-out boundary.
React auto-escapes JSX child content. The framework also exposes dangerouslySetInnerHTML, a prop whose name explicitly warns you what happens if you pass user data through it. Default to rendering as a child; reach for the dangerous prop only with a vetted sanitizer in front.
// DO NOT USE: dangerouslySetInnerHTML on user content with no sanitizer
<div dangerouslySetInnerHTML={{ __html: userContent }} />
// SAFE: React escapes content automatically when rendered as a child
<div>{userContent}</div>
// SAFE: when raw HTML is genuinely required, sanitize through DOMPurify first
import DOMPurify from 'dompurify';
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userContent) }} />
For server-rendered HTML, use a template engine with auto-escaping (EJS <%= %>, Nunjucks {{ }} with autoescape on, etc.) rather than building strings by hand.
// DO NOT USE: template-string interpolation of user input into HTML
res.send(`<h1>Hello ${req.query.name}</h1>`);
// SAFE: template engine with auto-escaping
res.render('greeting', { name: req.query.name });
Threat-modeling and secure defaults belong in the design phase, not retrofitted post-incident. Snowflake's MFA-opt-in default (until 2024) is the canonical "secure default" lesson — features that ship insecure by default and require opt-in to be safe are misuse-prone.
Renamed from "Identification and Authentication Failures" in 2021. Defaults live in the secure-auth skill in this bundle — checklist items here reference, don't duplicate. NIST SP 800-63B-4 went final 2025-07-31 (https://csrc.nist.gov/pubs/sp/800/63/b/4/final).
crypto.randomBytes or equivalent).Anchor lessons: Change Healthcare Feb 2024 (no MFA on the Citrix remote-access portal — https://www.unitedhealthgroup.com/newsroom/2024/2024-04-22-uhg-update-on-change-healthcare-cyberattack.html), Snowflake / UNC5537 2024 (default-on MFA was opt-in per tenant), Storm-0558 2023 (full token validation; CSRB review at https://www.cisa.gov/resources-tools/resources/CSRB-Review-Summer-2023-MEO-Intrusion), 23andMe 2023 (graph-traversal blast radius across linked accounts).
Note: "Software or Data" — the OR is load-bearing. Covers CI/CD pipeline integrity, signed update channels, and deserialization safety. CWE-502 (deserialization) dominates KEV in 2024-2025 (11 in 2024, 14 in 2025).
<script> and <link rel="stylesheet"> (Polyfill.io lesson — https://sansec.io/research/polyfill-supply-chain-attack).Python's native binary-deserialization primitive executes arbitrary code on untrusted input — loads runs object constructors, including any __reduce__ payload an attacker has crafted. JSON does not. Across any trust boundary, use JSON. Same hazard for YAML: the bare loader runs Python code; use safe_load.
import json
import yaml
# DO NOT USE: pickle.loads runs arbitrary code on untrusted input —
# any object's __reduce__ method executes at parse time. CWE-502 sink.
# import pickle
# obj = pickle.loads(request.body)
# DO NOT USE: yaml.load with no Loader argument is equivalent to yaml.Loader and runs code:
# config = yaml.load(request.body)
# SAFE: JSON parsing returns plain data (dict / list / str / number / bool / None)
data = json.loads(request.body)
# SAFE: when YAML is genuinely required, use safe_load
config = yaml.safe_load(request.body)
CWE-502 (deserialization of untrusted data) dominated the CISA KEV catalog in 2024-2025 (11 in 2024, 14 in 2025). The same warning applies in Java (ObjectInputStream.readObject), .NET (BinaryFormatter, NetDataContractSerializer, LosFormatter, SoapFormatter), and any Node package that "unserializes" arbitrary objects. JSON is the only safe choice for cross-trust-boundary input.
<!-- SAFE: SRI hash pinned -->
<script src="https://cdn.example.com/lib.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous"></script>
The unsafe pattern — referencing an external script with no integrity attribute — means any change at the CDN runs in your origin's context. Polyfill.io 2024 was exactly this.
Note: "Alerting", not "Monitoring" — the 2025 edition reframes around active response, not just collection. The Okta HAR incident (2023) is the anchor lesson: customer-uploaded debug artifacts contained live session tokens because sanitization at log boundaries was inadequate.
Anchor lesson: Okta HAR file incident — https://sec.okta.com/articles/harfiles/
// SAFE: structured logging with field allowlist
console.log('Login attempt:', { email, success: false, reason: 'invalid_password' });
console.log('Request:', { endpoint: req.path, method: req.method, userId: req.user?.id });
The unsafe pattern — logging the full request body or a credentials object — leaks every secret a user supplies. Never log password fields, token fields, full request bodies, or session identifiers.
New category in 2025. CWE catalog assigned CWE-1445 to this category, covering CWE-209/234/274/476/636 among 24 CWEs. The CrowdStrike Channel File 291 incident (July 2024) is the anchor lesson: production deployment of an unvalidated config file caused 8.5M Windows hosts to BSOD.
Exception blocks don't swallow security-relevant failures silently.Anchor lesson: CrowdStrike Falcon Channel File 291 RCA — https://www.crowdstrike.com/en-us/blog/falcon-content-update-preliminary-post-incident-report/
Run this at the end of the audit. If any answer is N, don't ship.
max-age=63072000; includeSubDomains; preload? Y/Nframe-ancestors? Y/N* in prod)? Y/Ndebug=False, NODE_ENV=production, etc.)? Y/Nsecure-auth skill).git filter-repo (preferred over git filter-branch) or BFG Repo-Cleaner to remove from history.console.log, logger., print(.You likely need to care about:
Minimum for any user data:
development
Use this skill when creating new files that represent architectural decisions — data models, infrastructure configs, auth boundaries, API contracts, CI/CD pipelines, or event systems. Flags irreversible decisions and forces a discussion about trade-offs before committing.
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