From security-hardening
Use when hardening application security -- preventing OWASP top 10 vulnerabilities, configuring security headers, implementing rate limiting, sanitizing input, managing CORS, or reviewing code for security issues. Covers web security for any backend framework.
npx claudepluginhub sagargupta16/claude-skills --plugin security-hardeningThis skill uses the workspace's default tool permissions.
| Threat | Prevention |
Acquire memory dumps from live systems/VMs and analyze with Volatility 3 for processes, networks, DLLs, injections in incident response or malware hunts.
Provides x86-64/ARM disassembly patterns, calling conventions, control flow recognition for static analysis of executables and compiled binaries.
Identifies anti-debugging checks like IsDebuggerPresent, NtQueryInformationProcess in Windows binaries; suggests bypasses via patches/hooks/scripts for malware analysis, CTFs, authorized RE.
| Threat | Prevention |
|---|---|
| SQL/NoSQL injection | Parameterized queries, ORMs |
| XSS | Output encoding, CSP headers |
| CSRF | SameSite cookies, CSRF tokens |
| Broken auth | Short-lived tokens, bcrypt, rate limiting |
| Sensitive data exposure | HTTPS, encryption at rest, no secrets in code |
| Security misconfiguration | Security headers, least privilege, no defaults |
| SSRF | Allowlist URLs, block private ranges |
| Path traversal | Validate and normalize paths |
# BAD: string concatenation
query = f"SELECT * FROM users WHERE id = '{user_id}'"
# GOOD: parameterized query
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
# GOOD: ORM
user = await User.get(id=user_id)
# BAD: MongoDB injection
db.users.find({"username": request.json["username"]}) # attacker sends {"$ne": ""}
# GOOD: validate type
username = request.json.get("username")
if not isinstance(username, str):
raise ValueError("Username must be a string")
db.users.find({"username": username})
# BAD: command injection
os.system(f"convert {filename} output.png")
# GOOD: use subprocess with list args (no shell)
subprocess.run(["convert", filename, "output.png"], check=True)
# BAD: rendering raw HTML from user input
return f"<div>{user_input}</div>"
# GOOD: use template engine with auto-escaping (Jinja2, React)
# React auto-escapes by default. Only dangerouslySetInnerHTML bypasses it.
Prevent with Content Security Policy:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'
| Rule | Implementation |
|---|---|
| Hash passwords | bcrypt or argon2, never MD5/SHA |
| Short-lived access tokens | 15 minutes for JWT access tokens |
| Refresh token rotation | New refresh token on each use, revoke old |
| Rate limit login | Max 5 attempts per minute per IP |
| MFA for sensitive operations | TOTP or WebAuthn |
from passlib.hash import bcrypt
# Hash
hashed = bcrypt.hash(password)
# Verify
if not bcrypt.verify(password, hashed):
raise HTTPException(401, "Invalid credentials")
Secure, HttpOnly, SameSite on cookies# FastAPI middleware
@app.middleware("http")
async def add_security_headers(request, call_next):
response = await call_next(request)
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
response.headers["X-XSS-Protection"] = "0" # deprecated, use CSP instead
response.headers["Strict-Transport-Security"] = "max-age=63072000; includeSubDomains"
response.headers["Content-Security-Policy"] = "default-src 'self'"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
response.headers["Permissions-Policy"] = "camera=(), microphone=(), geolocation=()"
return response
// Express with helmet
const helmet = require("helmet");
app.use(helmet());
| Header | Purpose |
|---|---|
Strict-Transport-Security | Force HTTPS |
Content-Security-Policy | Control resource loading |
X-Content-Type-Options: nosniff | Prevent MIME sniffing |
X-Frame-Options: DENY | Prevent clickjacking |
Referrer-Policy | Control referrer information |
Permissions-Policy | Disable browser features |
# FastAPI with slowapi
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
@app.post("/login")
@limiter.limit("5/minute")
async def login(request: Request):
...
// Express with express-rate-limit
const rateLimit = require("express-rate-limit");
app.use("/api/", rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 100, // 100 requests per window
standardHeaders: true,
}));
| Endpoint Type | Suggested Limit |
|---|---|
| Login/auth | 5-10 per minute |
| API (authenticated) | 100-1000 per minute |
| API (public) | 30-60 per minute |
| Password reset | 3 per hour |
| File upload | 10 per hour |
# GOOD: explicit origins from env
cors_origins = os.getenv("CORS_ORIGINS", "").split(",")
app.add_middleware(CORSMiddleware,
allow_origins=cors_origins,
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE"],
allow_headers=["Authorization", "Content-Type"],
)
# BAD: wildcard with credentials
app.add_middleware(CORSMiddleware,
allow_origins=["*"], # anyone can call your API
allow_credentials=True, # this combination is rejected by browsers anyway
)
Validate all input at system boundaries:
from pydantic import BaseModel, EmailStr, Field
class CreateUser(BaseModel):
name: str = Field(min_length=1, max_length=255)
email: EmailStr
age: int = Field(ge=0, le=150)
| Input Type | Validation |
|---|---|
| Strings | Max length, allowlist characters for usernames |
| Numbers | Min/max range, integer vs float |
| Emails | Format validation (use library, not regex) |
| URLs | Allowlist domains for SSRF prevention |
| File uploads | Max size, allowlist MIME types, scan for malware |
| Paths | Normalize, reject .., resolve to allowed directory |
| Don't | Do Instead |
|---|---|
allow_origins=["*"] in production | Explicit origin allowlist from env vars |
| Store passwords as MD5/SHA | Use bcrypt or argon2 |
| Log full request bodies | Redact sensitive fields (passwords, tokens, PII) |
| Disable HTTPS for development convenience | Use HTTPS everywhere, use self-signed certs for dev |
| Trust client-side validation only | Always validate server-side too |
Use eval() or exec() with user input | Never execute user-provided code |
| Expose stack traces in production errors | Generic error messages, detailed logs server-side |
| Hardcode secrets in code | Use environment variables or secret managers |