agentleFS
mahdy-gribkovmahdy-gribkov/arcana/skills/security-review/SKILL.md

security-review

Skill1 starsChanged 9 months agoApache-2.0View on GitHub

Reviews code for security flaws and hardens applications, covering OWASP Top 10 risks, injection (SQL, XSS, command), authentication and authorization, secrets handling and rotation, dependency scanning, input validation, CORS, CSRF, rate limiting and security headers, with examples in JavaScript, Python and Go. Use when reviewing a diff or file for vulnerabilities, hardening login or session code, setting up headers, CORS or rate limits, or auditing how secrets and dependencies are handled.

  • Reads credentials

193 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use linkerd-patterns, dependency-cve-sweep and 10 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.
  • xfstudioSkill

    linkerd-patterns

    Implement Linkerd service mesh patterns for lightweight, security-focused service mesh deployments. Use when setting up Linkerd, configuring traffic policies, or implementing zero-trust networking with minimal overhead.

    Also in 5 projects that use this

  • berntpoppSkill

    dependency-cve-sweep

    Use when handling Dependabot PRs, CodeQL/Trivy alerts, or doing a routine dependency/CVE sweep for this repo.

    Also in 12 projects that use this

  • berntpoppSkill

    fleet-standard-adoption

    Use when bringing this repo into compliance with a GeneFoundry fleet standard (tool-naming, response-envelope, container-hardening, versioning, MCP transport, or logging) or closing its tracking issue.

    Also in 12 projects that use this

  • HABUBUSSSkill

    project-guidelines-example

    Also in 16 projects that use this

  • niloykumarbarmanSkill

    agent-sort

    Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

    Also in 13 projects that use this

  • royaluniondesign-sysSkill

    configure-ecc

    Interactive installer for Everything Claude Code — guides users through selecting and installing skills and rules to user-level or project-level directories, verifies paths, and optionally optimizes installed files.

    Also in 11 projects that use this

  • +2 more from berntpopp

The file

We split this skill into the rules it gives an agent

8 rules. 0 are common, 7 are distinctive, and 1 is very specific to this project.

  • 0

    Common

    Most of the 167 edited versions we read keep these.

  • 7

    Distinctive

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Never store JWTs in localStorage (XSS-accessible).

    line 123
  2. Distinctive:

    Never put sensitive data in JWT payload (it's base64, not encrypted).

    line 124
  3. Very specific:

    Always validate the alg header server-side.

    line 125
  4. Distinctive:

    IDOR prevention: Always check that the authenticated user owns or has permission to access the resource identified by the URL parameter.

    line 162
  5. Distinctive:

    Runtime secrets: Use environment variables or a secrets manager (AWS SSM, Vault, Doppler).

    line 183
  6. Distinctive:

    Add new secret to secrets manager

    line 196
  7. Distinctive:

    Update clients to use new secret

    line 198
  8. Distinctive:

    Remove old secret after grace period (24-48 hours)

    line 199

“Common” is measured: kept by at least 6 in 10 of the 167 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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

You are a security engineer specializing in application security, code review, and vulnerability prevention across web, API, and systems code.

Use this skill when

  • Reviewing code for security vulnerabilities
  • Hardening authentication, authorization, or input handling
  • Setting up security headers, CORS, CSRF protection
  • Configuring dependency scanning or secrets management
  • Designing rate limiting or abuse prevention

OWASP Top 10 Quick Reference (2021 edition)

The 2025 edition reorders this list: it adds software supply chain failures and mishandling of exceptional conditions, and folds SSRF into broken access control. The defenses below still apply. Check owasp.org/Top10 for the current numbering before you cite an A-number in a report.

#VulnerabilityPrimary Defense
A01Broken Access ControlDeny by default, server-side checks on every request
A02Cryptographic FailuresTLS everywhere, bcrypt/argon2 for passwords, no secrets in code
A03InjectionParameterized queries, context-aware output encoding
A04Insecure DesignThreat modeling, secure defaults, least privilege
A05Security MisconfigurationMinimal installs, disable defaults, automate config
A06Vulnerable ComponentsSCA scanning (dependabot/snyk), pin versions, update regularly
A07Auth FailuresMFA, credential stuffing protection, secure session management
A08Data Integrity FailuresVerify signatures, use SRI, secure CI/CD
A09Logging FailuresLog security events, don't log secrets, monitor alerts
A10SSRFAllowlist outbound hosts, validate/sanitize URLs

Injection Prevention

SQL Injection

# BAD: string concatenation
cursor.execute(f"SELECT * FROM users WHERE id = {user_id}")

# GOOD: parameterized query
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))

# GOOD: ORM (SQLAlchemy)
user = session.query(User).filter(User.id == user_id).first()
// BAD: template literal in query
db.query(`SELECT * FROM users WHERE email = '${email}'`);

// GOOD: parameterized
db.query("SELECT * FROM users WHERE email = $1", [email]);
// BAD: fmt.Sprintf in query
db.Query(fmt.Sprintf("SELECT * FROM users WHERE id = %d", id))

// GOOD: parameterized
db.Query("SELECT * FROM users WHERE id = $1", id)

XSS Prevention

// BAD: innerHTML with user data
element.innerHTML = userInput;

// GOOD: textContent (no HTML parsing)
element.textContent = userInput;

// React: NEVER use dangerouslySetInnerHTML with user data
// If you must render HTML, sanitize with DOMPurify:
import DOMPurify from "dompurify";
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(html) }} />

// CSP header to block inline scripts:
// Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'

Command Injection

# BAD: shell=True with user input
subprocess.run(f"convert {filename} output.png", shell=True)

# GOOD: pass args as list, no shell
subprocess.run(["convert", filename, "output.png"], shell=False)

# GOOD: validate input against allowlist
ALLOWED = re.compile(r'^[a-zA-Z0-9_\-]+\.png$')
if not ALLOWED.match(filename):
    raise ValueError("Invalid filename")

Authentication Patterns

Password Hashing

# Use argon2 (preferred) or bcrypt. NEVER MD5/SHA for passwords.
from argon2 import PasswordHasher
ph = PasswordHasher(time_cost=3, memory_cost=65536, parallelism=4)
hash = ph.hash(password)
ph.verify(hash, password)  # raises on mismatch

JWT Best Practices

// Sign with RS256 (asymmetric) for services, HS256 for simple apps
const token = jwt.sign(
  { sub: user.id, role: user.role },
  privateKey,
  { algorithm: "RS256", expiresIn: "15m" } // short-lived access tokens
);

// ALWAYS validate: algorithm, expiration, issuer, audience
const payload = jwt.verify(token, publicKey, {
  algorithms: ["RS256"],     // prevent algorithm confusion attack
  issuer: "myapp",
  audience: "myapp-api",
});

JWT rules:

  • Access tokens: 15 min max. Refresh tokens: days, stored server-side, rotatable.
  • Never store JWTs in localStorage (XSS-accessible). Use httpOnly, Secure, SameSite=Strict cookies.
  • Never put sensitive data in JWT payload (it's base64, not encrypted).
  • Always validate the alg header server-side. Reject none algorithm.

Session Security

// Express session config
app.use(session({
  secret: process.env.SESSION_SECRET, // 32+ random bytes
  name: "__Host-sid",                 // __Host- prefix enforces Secure + no Domain
  cookie: {
    httpOnly: true,
    secure: true,
    sameSite: "strict",
    maxAge: 3600000,                  // 1 hour
  },
  resave: false,
  saveUninitialized: false,
}));

Authorization

Principle: deny by default, check on every request, server-side only.

# BAD: checking permissions client-side only
# BAD: checking only "is authenticated" without role/ownership check

# GOOD: middleware that checks ownership
async def get_document(request: Request, doc_id: int) -> Document:
    doc = await db.get_document(doc_id)
    if doc is None:
        raise HTTPException(404)  # don't reveal existence
    if doc.owner_id != request.user.id and request.user.role != "admin":
        raise HTTPException(404)  # 404 not 403 to prevent enumeration
    return doc

IDOR prevention: Always check that the authenticated user owns or has permission to access the resource identified by the URL parameter. Never trust client-supplied IDs without ownership verification.

Secrets Management

# NEVER commit secrets. Use .gitignore + pre-commit scanning.
# .gitignore
.env
*.pem
*.key
credentials.json

# Pre-commit: gitleaks
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.30.1
    hooks:
      - id: gitleaks

Runtime secrets: Use environment variables or a secrets manager (AWS SSM, Vault, Doppler). Never hardcode. Never log secrets. Rotate regularly.

Secrets Rotation

Rotation schedule:

  • API keys: Every 90 days
  • Database passwords: Every 90 days or on personnel changes
  • JWT signing keys: Every 6-12 months
  • TLS certificates: Automated via Let's Encrypt (90-day expiry)
  • Service account tokens: Every 30-90 days

Rotation process:

  1. Generate new secret (key/password/token)
  2. Add new secret to secrets manager
  3. Deploy code that supports BOTH old and new secrets
  4. Update clients to use new secret
  5. Remove old secret after grace period (24-48 hours)
# Example: rotate a database password with zero downtime.
# Keep two roles with identical grants (myapp_a, myapp_b). Only one is live.
# Changing the live role's password would break running instances at once.

# 1. Generate the new password and set it on the idle role (myapp_b).
#    Pipe the SQL through stdin so the password never shows in the process list.
NEW_PASS=$(openssl rand -base64 32)
printf "ALTER ROLE myapp_b PASSWORD '%s';\n" "$NEW_PASS" | psql "$ADMIN_DATABASE_URL"

# 2. Store it encrypted (SecureString), not as a plain String parameter
aws ssm put-parameter --name /prod/db/password --type SecureString --value "$NEW_PASS" --overwrite
unset NEW_PASS

# 3. Rolling restart so instances pick up the new role and password
kubectl rollout restart deployment/myapp
kubectl rollout status deployment/myapp

# 4. After every instance is on myapp_b and the grace period has passed,
#    disable login for the old role (myapp_a). Next rotation swaps the roles.
# GOOD: secrets from environment with validation
import os

def get_required_env(key: str) -> str:
    value = os.environ.get(key)
    if not value:
        raise RuntimeError(f"Required env var {key} is not set")
    return value

DATABASE_URL = get_required_env("DATABASE_URL")

Security Headers

# Nginx or reverse proxy
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://api.example.com" always;

CORS Configuration

// BAD: allow everything
app.use(cors({ origin: "*", credentials: true })); // credentials + wildcard = browser rejects

// GOOD: explicit allowlist
app.use(cors({
  origin: ["https://myapp.com", "https://staging.myapp.com"],
  methods: ["GET", "POST", "PUT", "DELETE"],
  allowedHeaders: ["Content-Type", "Authorization"],
  credentials: true,
  maxAge: 86400,
}));

CSRF Protection

For cookie-based auth (sessions), use the synchronizer token pattern or SameSite cookies:

// Option 1: SameSite=Strict cookies (sufficient for most apps)
// Option 2: CSRF token (needed if SameSite=Lax or cross-subdomain)

// Modern approach: csrf-csrf (csurf is deprecated)
import { doubleCsrf } from "csrf-csrf";

const { generateToken, doubleCsrfProtection } = doubleCsrf({
  getSecret: () => process.env.CSRF_SECRET,
  cookieName: "__Host-csrf",
  cookieOptions: { httpOnly: true, secure: true, sameSite: "strict" },
  getTokenFromRequest: (req) => req.headers["x-csrf-token"],
});

app.use(doubleCsrfProtection);

// Alternative: Double-submit cookie pattern (manual)
app.post("/api/action", (req, res) => {
  const tokenFromCookie = req.cookies.csrfToken;
  const tokenFromHeader = req.headers["x-csrf-token"];
  if (tokenFromCookie !== tokenFromHeader) return res.status(403).send("CSRF validation failed");
  // Process request
});

Token-based auth (Bearer JWT in Authorization header) is inherently CSRF-safe because browsers don't auto-attach it.

Rate Limiting

// Express rate limiting
import rateLimit from "express-rate-limit";

// General API rate limit
app.use("/api/", rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  limit: 100,
  standardHeaders: true,
  legacyHeaders: false,
  keyGenerator: (req) => req.ip, // or req.user?.id for authenticated
}));

// Strict limit on auth endpoints
app.use("/api/auth/", rateLimit({
  windowMs: 15 * 60 * 1000,
  limit: 5,                     // 5 login attempts per 15 min
  skipSuccessfulRequests: true, // don't count successful logins
}));

Dependency Scanning

# JavaScript: npm audit + Snyk
npm audit --omit=dev
npx snyk test

# Python: pip-audit
pip-audit

# Go: govulncheck (official)
govulncheck ./...

# CI: GitHub Dependabot + CodeQL
# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 10

Input Validation Checklist

  1. Validate on the server. Client-side validation is UX, not security.
  2. Allowlist over denylist. Define what IS valid, not what isn't.
  3. Validate type, length, range, and format for every input field.
  4. Reject unexpected fields (strict schema parsing with Pydantic/Zod).
  5. Sanitize for output context (HTML encode for HTML, SQL parameterize for SQL).
  6. File uploads: validate MIME type by content (not extension), limit size, store outside webroot, randomize filenames.
  7. URLs: validate scheme (https only), resolve and check against SSRF allowlist.

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.