feat: improved the Claude Kit as a plugin

This commit is contained in:
duthaho
2026-04-19 14:09:14 +07:00
parent 3103a8da1b
commit d1a6d2a2bc
186 changed files with 771 additions and 1691 deletions
+66
View File
@@ -0,0 +1,66 @@
---
name: owasp
description: >
Use when reviewing code for security vulnerabilities, implementing authentication or authorization flows, handling user input validation, or building web endpoints exposed to untrusted data. Trigger on keywords like XSS, SQL injection, CSRF, input sanitization, password hashing, security headers, "security scan", "vulnerability scan", "npm audit", or "pip-audit". Also apply when auditing existing code for OWASP Top 10 compliance, scanning dependencies for known vulnerabilities, detecting hardcoded secrets, or conducting security-focused code reviews.
---
# OWASP Security Patterns
## When to Use
- Reviewing code for OWASP Top 10 vulnerabilities
- Implementing input validation on user-facing endpoints
- Adding security headers (CSP, HSTS, X-Frame-Options)
- Preventing XSS, SQL injection, CSRF, or SSRF
- Auditing authentication or authorization flows
- Building endpoints that handle untrusted data
- Scanning dependencies for known vulnerabilities (`npm audit`, `pip-audit`)
- Detecting hardcoded secrets, API keys, or tokens in code
## When NOT to Use
- Infrastructure security (network, firewall, cloud IAM) — use platform-specific tools
- Cryptographic algorithm selection — consult cryptography experts
- Compliance frameworks (SOC 2, HIPAA) — security patterns help but don't cover audit requirements
---
## Quick Reference
| Topic | Reference | Key content |
|-------|-----------|-------------|
| All security patterns | `references/patterns.md` | Input validation, SQL injection, XSS, CSRF, auth, headers |
| OWASP Top 10 cheatsheet | `references/owasp-top10-cheatsheet.md` | Quick reference for each vulnerability category |
| Security headers | `references/security-headers.md` | CSP, HSTS, X-Frame-Options, Referrer-Policy |
| Security checklist | `references/security-checklist.md` | Pre-deploy security review checklist |
| Security audit script | `references/security-audit.py` | Automated security scanning utility |
---
## Best Practices
1. **Validate all input at the boundary.** Use Pydantic (Python) or Zod (TypeScript) for schema validation. Never trust client-side validation alone.
2. **Use parameterized queries exclusively.** Never concatenate user input into SQL strings. Use ORM query builders or prepared statements.
3. **Encode output based on context.** HTML-encode for HTML, URL-encode for URLs, JSON-encode for JSON. No single encoding fits all contexts.
4. **Set security headers on every response.** CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy.
5. **Use CSRF tokens for state-changing requests.** Every POST/PUT/DELETE from a browser form needs a CSRF token.
6. **Apply rate limiting to all public endpoints.** Especially authentication, registration, and password reset.
7. **Never expose stack traces or internal errors to clients.** Return generic error messages; log details server-side.
8. **Audit dependencies regularly.** Run `npm audit` / `pip-audit` / `safety check` in CI.
## Common Pitfalls
1. **Relying on client-side validation only** — easily bypassed with curl or browser devtools.
2. **Using `dangerouslySetInnerHTML` or `| safe` without sanitization** — XSS vector.
3. **SQL string concatenation** — even "just for this one query" is a SQL injection risk.
4. **Missing CSRF protection on API routes** — if cookies are used for auth, CSRF applies.
5. **Overly permissive CORS**`Access-Control-Allow-Origin: *` with credentials is a security hole.
6. **Logging sensitive data** — passwords, tokens, and PII in logs persist in storage and backups.
---
## Related Skills
- `authentication` — Secure auth implementation patterns
- `error-handling` — Preventing information leakage through errors
- `backend-frameworks` — Framework-specific security middleware
@@ -0,0 +1,193 @@
# OWASP Top 10 (2021) Cheat Sheet
Quick reference for the OWASP Top 10 web application security risks.
---
## A01: Broken Access Control
**Risk**: Users act outside intended permissions (view other users' data, modify access).
**Prevention**: Deny by default. Enforce ownership. Disable directory listing. Log failures.
```python
# Enforce ownership check
def get_order(order_id, current_user):
order = db.query(Order).get(order_id)
if order.user_id != current_user.id:
raise PermissionError("Access denied")
return order
```
## A02: Cryptographic Failures
**Risk**: Exposure of sensitive data due to weak or missing encryption.
**Prevention**: Encrypt data at rest and in transit. Use strong algorithms (AES-256, bcrypt). Never store plaintext passwords.
```python
from passlib.hash import bcrypt
hashed = bcrypt.hash(password)
assert bcrypt.verify(password, hashed)
```
## A03: Injection
**Risk**: Untrusted data sent to an interpreter as part of a command or query.
**Prevention**: Use parameterized queries. Validate and sanitize all input. Use ORMs.
```python
# WRONG: cursor.execute(f"SELECT * FROM users WHERE id = {user_id}")
# RIGHT:
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
```
```typescript
// WRONG: db.query(`SELECT * FROM users WHERE id = ${id}`)
// RIGHT:
db.query("SELECT * FROM users WHERE id = $1", [id]);
```
## A04: Insecure Design
**Risk**: Missing or ineffective security controls due to flawed architecture.
**Prevention**: Use threat modeling. Apply secure design patterns. Establish reference architectures. Write abuse-case tests.
```python
# Rate-limit sensitive operations
from functools import lru_cache
from datetime import datetime, timedelta
LOGIN_ATTEMPTS = {} # Use Redis in production
def check_rate_limit(ip: str, max_attempts=5, window=300):
now = datetime.now().timestamp()
attempts = [t for t in LOGIN_ATTEMPTS.get(ip, []) if now - t < window]
if len(attempts) >= max_attempts:
raise RateLimitExceeded()
attempts.append(now)
LOGIN_ATTEMPTS[ip] = attempts
```
## A05: Security Misconfiguration
**Risk**: Default configs, incomplete setups, open cloud storage, verbose errors.
**Prevention**: Repeatable hardening process. Minimal platform. Remove unused features. Review cloud permissions.
```yaml
# Docker: don't run as root
FROM python:3.12-slim
RUN useradd -m appuser
USER appuser
```
## A06: Vulnerable and Outdated Components
**Risk**: Using components with known vulnerabilities.
**Prevention**: Remove unused dependencies. Monitor CVEs. Use `pip audit`, `npm audit`. Pin versions.
```bash
pip audit # Python
npm audit # Node.js
npx depcheck # Find unused deps
```
## A07: Identification and Authentication Failures
**Risk**: Weak authentication, credential stuffing, session fixation.
**Prevention**: MFA. Strong password policies. Secure session management. Throttle failed logins.
```python
# Secure session config (Flask)
app.config.update(
SESSION_COOKIE_SECURE=True,
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE="Lax",
PERMANENT_SESSION_LIFETIME=timedelta(hours=1),
)
```
## A08: Software and Data Integrity Failures
**Risk**: Code and infrastructure that does not protect against integrity violations (CI/CD, unsigned updates).
**Prevention**: Verify signatures. Use lock files. Review CI/CD pipelines. Use Subresource Integrity.
```html
<!-- Subresource Integrity -->
<script src="https://cdn.example.com/lib.js"
integrity="sha384-abc123..."
crossorigin="anonymous"></script>
```
## A09: Security Logging and Monitoring Failures
**Risk**: Insufficient logging makes breaches undetectable.
**Prevention**: Log auth events, access control failures, input validation failures. Set up alerts.
```python
import logging
logger = logging.getLogger("security")
def login(username, password):
user = authenticate(username, password)
if not user:
logger.warning("Failed login attempt", extra={
"username": username,
"ip": request.remote_addr,
"timestamp": datetime.utcnow().isoformat(),
})
raise AuthenticationError()
logger.info("Successful login", extra={"user_id": user.id})
```
## A10: Server-Side Request Forgery (SSRF)
**Risk**: Application fetches remote resources without validating user-supplied URLs.
**Prevention**: Allowlist URLs/domains. Block private IP ranges. Disable redirects.
```python
from urllib.parse import urlparse
import ipaddress
ALLOWED_HOSTS = {"api.example.com", "cdn.example.com"}
def validate_url(url: str) -> bool:
parsed = urlparse(url)
if parsed.hostname not in ALLOWED_HOSTS:
return False
try:
ip = ipaddress.ip_address(parsed.hostname)
if ip.is_private or ip.is_loopback:
return False
except ValueError:
pass # hostname, not IP — already checked against allowlist
return True
```
---
## Quick Reference Table
| ID | Name | Key Control |
|-----|-------------------------------|--------------------------------|
| A01 | Broken Access Control | Deny by default, enforce ownership |
| A02 | Cryptographic Failures | Encrypt in transit + at rest |
| A03 | Injection | Parameterized queries |
| A04 | Insecure Design | Threat modeling, abuse cases |
| A05 | Security Misconfiguration | Hardened defaults, minimal surface |
| A06 | Vulnerable Components | Audit deps, pin versions |
| A07 | Auth Failures | MFA, session security |
| A08 | Integrity Failures | Verify signatures, lock files |
| A09 | Logging Failures | Log security events, alert |
| A10 | SSRF | Allowlist URLs, block private IPs |
*Source: [OWASP Top 10 (2021)](https://owasp.org/Top10/)*
+553
View File
@@ -0,0 +1,553 @@
# Owasp — Patterns
# OWASP Web Application Security
## When to Use
- Security code reviews
- Implementing authentication or authorization
- Handling user input from untrusted sources
- Building or auditing web API endpoints
- Configuring CORS, CSP, or other security headers
- Managing secrets, tokens, or credentials in code
- Setting up rate limiting or brute force protection
## When NOT to Use
- General code style or formatting reviews with no security implications
- Non-web applications such as CLI tools, batch scripts, or desktop utilities
- Performance optimization tasks where security is not the concern
- Infrastructure-level security (firewall rules, network segmentation)
---
## Core Patterns
### 1. Input Validation & Sanitization
Always validate input at the boundary. Use allowlists over denylists.
**Python (Pydantic)**
```python
# BAD - no validation, accepts anything
@app.post("/users")
async def create_user(request: Request):
data = await request.json()
name = data["name"] # no length check, no type check
email = data["email"] # no format validation
role = data["role"] # user controls their own role
db.execute(f"INSERT INTO users VALUES ('{name}', '{email}', '{role}')")
# GOOD - strict schema validation with Pydantic
from pydantic import BaseModel, EmailStr, Field
from enum import Enum
class UserRole(str, Enum):
viewer = "viewer"
editor = "editor"
class CreateUserRequest(BaseModel):
name: str = Field(min_length=1, max_length=100, pattern=r"^[a-zA-Z\s\-]+$")
email: EmailStr
role: UserRole = UserRole.viewer # default to least privilege
@app.post("/users")
async def create_user(payload: CreateUserRequest):
# Pydantic rejects invalid data before this code runs
db.add(User(name=payload.name, email=payload.email, role=payload.role))
```
**TypeScript (Zod)**
```typescript
// BAD - trusting req.body directly
app.post("/users", (req, res) => {
const { name, email, role } = req.body; // no validation
db.query(`INSERT INTO users VALUES ('${name}', '${email}', '${role}')`);
});
// GOOD - validate with Zod at the boundary
import { z } from "zod";
const CreateUserSchema = z.object({
name: z.string().min(1).max(100).regex(/^[a-zA-Z\s\-]+$/),
email: z.string().email(),
role: z.enum(["viewer", "editor"]).default("viewer"),
});
app.post("/users", (req, res) => {
const result = CreateUserSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ errors: result.error.flatten() });
}
// result.data is typed and validated
await prisma.user.create({ data: result.data });
});
```
**File Upload Validation**
```python
# GOOD - validate MIME type (not just extension), size, and sanitize filename
import magic
ALLOWED_TYPES = {"image/jpeg", "image/png", "application/pdf"}
MAX_SIZE = 5 * 1024 * 1024 # 5 MB
def validate_upload(file_bytes: bytes, filename: str) -> bool:
if len(file_bytes) > MAX_SIZE:
raise ValueError("File too large")
if magic.from_buffer(file_bytes, mime=True) not in ALLOWED_TYPES:
raise ValueError("Disallowed file type")
if ".." in filename or filename.startswith("."):
raise ValueError("Invalid filename")
return True
```
### 2. SQL Injection Prevention
Never concatenate user input into SQL strings. Always use parameterized queries or ORM methods.
**Raw SQL (Python)**
```python
# BAD - string interpolation creates injection vector
def get_user(user_id: str):
query = f"SELECT * FROM users WHERE id = '{user_id}'"
# Input: "'; DROP TABLE users; --" destroys the table
cursor.execute(query)
# GOOD - parameterized query
def get_user(user_id: str):
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
return cursor.fetchone()
```
**SQLAlchemy (Python)**
```python
# BAD - text() with f-string
from sqlalchemy import text
result = session.execute(text(f"SELECT * FROM users WHERE name = '{name}'"))
# GOOD - bound parameters with text()
result = session.execute(text("SELECT * FROM users WHERE name = :name"), {"name": name})
# GOOD - ORM query (automatically parameterized)
user = session.query(User).filter(User.name == name).first()
```
**Prisma (TypeScript)**
```typescript
// BAD - raw query with interpolation
const user = await prisma.$queryRawUnsafe(`SELECT * FROM users WHERE id = '${id}'`);
// GOOD - tagged template (auto-parameterized)
const user = await prisma.$queryRaw`SELECT * FROM users WHERE id = ${id}`;
// GOOD - Prisma client methods (always safe)
const user = await prisma.user.findUnique({ where: { id } });
```
### 3. XSS Prevention
Prevent cross-site scripting by encoding output, setting CSP headers, and sanitizing HTML.
**Output Encoding**
```typescript
// BAD - renders raw user content as HTML
element.innerHTML = userComment;
// GOOD - use textContent for plain text
element.textContent = userComment;
// GOOD - React auto-escapes by default (don't bypass it)
return <div>{userComment}</div>;
// BAD - dangerouslySetInnerHTML defeats React's protection
return <div dangerouslySetInnerHTML={{ __html: userComment }} />;
```
**Sanitizing HTML When You Must Render It**
```typescript
// GOOD - sanitize with DOMPurify when HTML rendering is required
import DOMPurify from "dompurify";
const cleanHtml = DOMPurify.sanitize(userHtml, {
ALLOWED_TAGS: ["b", "i", "em", "strong", "a", "p", "br"],
ALLOWED_ATTR: ["href", "title"],
});
return <div dangerouslySetInnerHTML={{ __html: cleanHtml }} />;
```
### 4. Authentication Patterns
**Password Hashing**
```python
# BAD - plain text or weak hashing
hashed = hashlib.md5(password.encode()).hexdigest() # trivially crackable
# GOOD - use argon2 (preferred) or bcrypt with proper cost
from passlib.hash import argon2
hashed = argon2.hash(password)
is_valid = argon2.verify(password, hashed)
```
```typescript
// GOOD - bcrypt in Node.js
import bcrypt from "bcrypt";
const SALT_ROUNDS = 12;
const hashed = await bcrypt.hash(password, SALT_ROUNDS);
const isValid = await bcrypt.compare(password, hashed);
```
**JWT Best Practices**
```python
# BAD - long-lived token, weak secret
token = jwt.encode({"user_id": 1, "exp": datetime.utcnow() + timedelta(days=365)},
"secret123", algorithm="HS256")
# GOOD - short expiry, strong secret, httpOnly cookie delivery
ACCESS_TOKEN_EXPIRY = timedelta(minutes=15)
def create_access_token(user_id: int) -> str:
return jwt.encode(
{"sub": user_id, "exp": datetime.now(timezone.utc) + ACCESS_TOKEN_EXPIRY},
os.environ["JWT_SECRET_KEY"], algorithm="HS256",
)
def set_token_cookie(response: Response, token: str):
response.set_cookie(
key="access_token", value=token,
httponly=True, secure=True, samesite="lax", # not accessible via JS, HTTPS only
max_age=int(ACCESS_TOKEN_EXPIRY.total_seconds()),
)
```
**Session Management Rules**
- Set session timeouts (30 minutes idle, 8 hours absolute)
- Regenerate session ID after login to prevent session fixation
- Store sessions server-side (Redis, database), not in cookies
- Clear sessions on logout (`request.session.clear()`)
- Use `httponly`, `secure`, and `samesite=lax` on session cookies
### 5. Authorization & Access Control
**RBAC Pattern**
```python
# GOOD - role-based access control with decorator
from enum import Enum
class Role(str, Enum):
admin = "admin"
editor = "editor"
viewer = "viewer"
ROLE_HIERARCHY = {Role.admin: 3, Role.editor: 2, Role.viewer: 1}
def require_role(minimum_role: Role):
def decorator(func):
async def wrapper(request: Request, *args, **kwargs):
user = request.state.user
if ROLE_HIERARCHY.get(user.role, 0) < ROLE_HIERARCHY[minimum_role]:
raise HTTPException(status_code=403)
return await func(request, *args, **kwargs)
return wrapper
return decorator
@app.delete("/posts/{post_id}")
@require_role(Role.editor)
async def delete_post(request: Request, post_id: int): ...
```
**Middleware-Based Authorization (Express)**
```typescript
// GOOD - authorization middleware
function requireRole(...allowedRoles: string[]) {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user || !allowedRoles.includes(req.user.role)) {
return res.status(403).json({ error: "Forbidden" });
}
next();
};
}
app.delete("/posts/:id", requireRole("admin", "editor"), deletePostHandler);
```
**Object-Level Permissions**
```python
# BAD - checks auth but not ownership (any user can edit any document)
@app.put("/documents/{doc_id}")
async def update_document(doc_id: int, payload: UpdateDoc, user=Depends(get_current_user)):
doc = await db.get(Document, doc_id)
doc.content = payload.content
# GOOD - verify ownership or admin role on every mutation
@app.put("/documents/{doc_id}")
async def update_document(doc_id: int, payload: UpdateDoc, user=Depends(get_current_user)):
doc = await db.get(Document, doc_id)
if not doc:
raise HTTPException(status_code=404)
if doc.owner_id != user.id and user.role != Role.admin:
raise HTTPException(status_code=403)
doc.content = payload.content
```
### 6. CORS Configuration
**FastAPI**
```python
# BAD - allows everything
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True,
allow_methods=["*"], allow_headers=["*"])
# GOOD - restrictive CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["https://app.example.com", "https://staging.example.com"],
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["Authorization", "Content-Type"],
)
```
**Express**
```typescript
// BAD
app.use(cors({ origin: true, credentials: true }));
// GOOD - explicit allowlist with callback
const ALLOWED_ORIGINS = ["https://app.example.com"];
app.use(cors({
origin: (origin, cb) => {
if (!origin || ALLOWED_ORIGINS.includes(origin)) cb(null, true);
else cb(new Error("Not allowed by CORS"));
},
credentials: true,
methods: ["GET", "POST", "PUT", "DELETE"],
}));
```
### 7. Security Headers
**Express with Helmet**
```typescript
// GOOD - Helmet sets secure defaults for all critical headers
import helmet from "helmet";
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:"],
frameAncestors: ["'none'"],
},
},
hsts: { maxAge: 31536000, includeSubDomains: true, preload: true },
}));
```
**FastAPI**
```python
# GOOD - security headers middleware
@app.middleware("http")
async def security_headers(request, call_next):
response = await call_next(request)
response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains; preload"
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
response.headers["Permissions-Policy"] = "camera=(), microphone=(), geolocation=()"
response.headers["Content-Security-Policy"] = "default-src 'self'; frame-ancestors 'none';"
return response
```
### 8. Secret Management
```python
# BAD - hardcoded secrets
DATABASE_URL = "postgresql://admin:p@ssw0rd@localhost/mydb"
API_KEY = "sk-1234567890abcdef"
JWT_SECRET = "mysecret"
# GOOD - environment variables with validation
import os
def get_required_env(key: str) -> str:
value = os.environ.get(key)
if not value:
raise RuntimeError(f"Required environment variable {key} is not set")
return value
DATABASE_URL = get_required_env("DATABASE_URL")
API_KEY = get_required_env("API_KEY")
JWT_SECRET = get_required_env("JWT_SECRET")
```
**.env and .gitignore**
```bash
# .env (NEVER commit this file)
DATABASE_URL=postgresql://admin:securepass@localhost/mydb
JWT_SECRET=a-very-long-random-string-from-openssl-rand
API_KEY=sk-prod-xxxxxxxxxxxx
```
```gitignore
# .gitignore - always include these
.env
.env.*
!.env.example
*.pem
*.key
credentials.json
```
Commit a `.env.example` with empty values to document required variables without exposing secrets.
### 9. Rate Limiting
**Python (FastAPI with slowapi)**
```python
# GOOD - rate limiting on sensitive endpoints
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
@app.post("/login")
@limiter.limit("5/minute") # brute force protection
async def login(request: Request, credentials: LoginRequest):
...
@app.post("/api/data")
@limiter.limit("100/minute") # general API rate limit
async def get_data(request: Request):
...
```
**Express (express-rate-limit)**
```typescript
// GOOD - tiered rate limiting
import rateLimit from "express-rate-limit";
const generalLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100 });
const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 5 });
app.use("/api/", generalLimiter);
app.use("/auth/login", authLimiter);
app.use("/auth/register", authLimiter);
```
### 10. Dependency Security
```bash
# Python - audit dependencies
pip install pip-audit
pip-audit # scan for known vulnerabilities
pip-audit --fix # auto-fix where possible
# Node.js - audit dependencies
npm audit # list vulnerabilities
npm audit fix # auto-fix compatible updates
pnpm audit # pnpm equivalent
# Always commit lock files to ensure reproducible builds
# Python: requirements.txt or poetry.lock
# Node.js: package-lock.json, pnpm-lock.yaml, or yarn.lock
```
Run `npm audit --audit-level=high` and `pip-audit --strict` in CI (e.g., GitHub Actions on every PR and weekly schedule). Treat high-severity findings as build failures.
---
## Best Practices
1. **Validate at the boundary, trust nothing inside.** Every piece of user input -- query params, headers, request bodies, file uploads -- must be validated before processing. Use Pydantic or Zod schemas, not manual checks.
2. **Apply the principle of least privilege everywhere.** Default to the most restrictive access. Grant permissions explicitly. Use role-based access control and verify object-level ownership on every mutation.
3. **Never store or log secrets in plain text.** Use environment variables, a secret manager, or encrypted storage. Ensure secrets never appear in logs, error messages, or version control.
4. **Use strong, adaptive password hashing.** Always use argon2 or bcrypt with a sufficient work factor. Never use MD5, SHA-1, or SHA-256 alone for password storage.
5. **Set security headers on every response.** Enable HSTS, CSP, X-Content-Type-Options, X-Frame-Options, and Referrer-Policy. Use Helmet for Express and middleware for FastAPI.
6. **Fail closed, not open.** When authentication or authorization checks encounter errors, deny access by default. Never fall through to an unprotected code path on exception.
7. **Keep dependencies updated and audited.** Run `npm audit` and `pip-audit` in CI pipelines. Pin dependency versions with lock files. Review changelogs before major upgrades.
8. **Enforce rate limiting on all public-facing endpoints.** Apply stricter limits on authentication and password reset endpoints. Use IP-based and account-based limiting together for defense in depth.
---
## Common Pitfalls
1. **Trusting client-side validation alone.** Attackers bypass browser validation trivially. Always re-validate on the server.
2. **Using wildcard CORS with credentials.** `allow_origins=["*"]` with credentials is insecure and browsers reject it. Specify exact origins.
3. **Storing JWTs in localStorage.** Any XSS can steal them. Use httpOnly, secure, sameSite cookies instead.
4. **Returning detailed error messages in production.** Stack traces help attackers. Return generic messages to clients, log details server-side.
5. **Using ORM raw query methods unsafely.** `$queryRawUnsafe` and `text()` with f-strings bypass ORM protections. Audit every raw SQL call.
6. **Checking authentication but not authorization.** "Logged in" does not mean "authorized." Check object-level permissions on every write.
7. **Disabling security in dev and shipping it.** CSP, CORS, HTTPS disabled for convenience can reach production. Use environment-aware config.
8. **Ignoring dependency vulnerabilities.** Known CVEs in transitive deps are a top attack vector. Automate auditing in CI.
---
## Security Review Checklist
- [ ] All user input validated with schema (Pydantic / Zod) before processing
- [ ] No string concatenation or interpolation in SQL queries
- [ ] Passwords hashed with argon2 or bcrypt (never MD5/SHA)
- [ ] JWTs have short expiry, use httpOnly cookies, strong secret from env
- [ ] Authorization checked at object level, not just authentication
- [ ] CORS configured with explicit origin allowlist (no wildcards with credentials)
- [ ] Security headers set: CSP, HSTS, X-Content-Type-Options, X-Frame-Options
- [ ] No secrets hardcoded in source -- all from environment variables
- [ ] .env files listed in .gitignore, .env.example committed
- [ ] Rate limiting applied to login, registration, and password reset endpoints
- [ ] File uploads validated by MIME type, size, and sanitized filename
- [ ] Error responses do not leak stack traces or internal details
- [ ] Dependencies audited with npm audit / pip-audit (no high-severity CVEs)
- [ ] HTTPS enforced in production with HSTS preload
- [ ] No use of eval(), dangerouslySetInnerHTML (without DOMPurify), or innerHTML
---
## Related Skills
- `authentication` - Authentication and authorization implementation patterns
- `error-handling` - Secure error handling that avoids leaking sensitive information
- `docker` — Container security hardening
- `defense-in-depth` — Multi-layer security validation
+217
View File
@@ -0,0 +1,217 @@
# Security Headers Reference
Comprehensive reference for HTTP security headers with recommended values and implementation examples.
---
## Header Reference Table
| Header | Purpose | Recommended Value |
|--------|---------|-------------------|
| `Content-Security-Policy` | Prevent XSS, data injection | See detailed section below |
| `Strict-Transport-Security` | Force HTTPS | `max-age=63072000; includeSubDomains; preload` |
| `X-Frame-Options` | Prevent clickjacking | `DENY` or `SAMEORIGIN` |
| `X-Content-Type-Options` | Prevent MIME sniffing | `nosniff` |
| `Referrer-Policy` | Control referer leakage | `strict-origin-when-cross-origin` |
| `Permissions-Policy` | Restrict browser features | See detailed section below |
---
## Content-Security-Policy (CSP)
Controls which resources the browser is allowed to load.
**Starter policy (strict):**
```
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'
```
**Key directives:**
| Directive | Controls | Example |
|-----------|----------|---------|
| `default-src` | Fallback for all resource types | `'self'` |
| `script-src` | JavaScript sources | `'self' https://cdn.example.com` |
| `style-src` | CSS sources | `'self' 'unsafe-inline'` |
| `img-src` | Image sources | `'self' data: https:` |
| `connect-src` | Fetch, XHR, WebSocket targets | `'self' https://api.example.com` |
| `frame-ancestors` | Who can embed this page | `'none'` |
| `form-action` | Form submission targets | `'self'` |
## Strict-Transport-Security (HSTS)
Forces browsers to use HTTPS for all future requests to this domain.
```
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
```
- `max-age=63072000` — 2 years (minimum for preload list)
- `includeSubDomains` — apply to all subdomains
- `preload` — opt into browser preload lists
## X-Frame-Options
Prevents the page from being embedded in iframes (clickjacking protection).
```
X-Frame-Options: DENY
```
| Value | Behavior |
|-------|----------|
| `DENY` | Never allow framing |
| `SAMEORIGIN` | Allow framing by same origin only |
Note: `frame-ancestors` in CSP is the modern replacement but set both for backward compatibility.
## X-Content-Type-Options
Prevents browsers from MIME-sniffing the response content type.
```
X-Content-Type-Options: nosniff
```
Always pair with correct `Content-Type` headers on responses.
## Referrer-Policy
Controls how much referrer information is sent with requests.
```
Referrer-Policy: strict-origin-when-cross-origin
```
| Value | Cross-Origin Sends | Same-Origin Sends |
|-------|-------------------|-------------------|
| `no-referrer` | Nothing | Nothing |
| `origin` | Origin only | Origin only |
| `strict-origin-when-cross-origin` | Origin (HTTPS only) | Full URL |
| `same-origin` | Nothing | Full URL |
## Permissions-Policy
Restricts which browser features the page can use.
```
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
```
| Feature | Recommended | Description |
|---------|-------------|-------------|
| `camera` | `()` | Disable camera access |
| `microphone` | `()` | Disable microphone |
| `geolocation` | `()` | Disable location |
| `payment` | `()` | Disable Payment API |
| `usb` | `()` | Disable USB access |
| `fullscreen` | `(self)` | Allow fullscreen for same origin |
---
## Implementation: Python (FastAPI)
```python
from fastapi import FastAPI
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import Response
app = FastAPI()
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next) -> Response:
response = await call_next(request)
response.headers["Content-Security-Policy"] = (
"default-src 'self'; script-src 'self'; "
"style-src 'self' 'unsafe-inline'; "
"img-src 'self' data: https:; "
"frame-ancestors 'none'; base-uri 'self'; form-action 'self'"
)
response.headers["Strict-Transport-Security"] = (
"max-age=63072000; includeSubDomains; preload"
)
response.headers["X-Frame-Options"] = "DENY"
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
response.headers["Permissions-Policy"] = (
"camera=(), microphone=(), geolocation=(), payment=()"
)
return response
app.add_middleware(SecurityHeadersMiddleware)
```
## Implementation: Node.js (Express)
```typescript
import helmet from "helmet";
import express from "express";
const app = express();
app.use(
helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
frameAncestors: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"],
},
},
strictTransportSecurity: {
maxAge: 63072000,
includeSubDomains: true,
preload: true,
},
frameguard: { action: "deny" },
referrerPolicy: { policy: "strict-origin-when-cross-origin" },
permissionsPolicy: {
features: {
camera: [],
microphone: [],
geolocation: [],
payment: [],
},
},
})
);
```
## Implementation: Next.js
```typescript
// next.config.ts
const securityHeaders = [
{ key: "Content-Security-Policy", value: "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; frame-ancestors 'none'" },
{ key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains; preload" },
{ key: "X-Frame-Options", value: "DENY" },
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{ key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=(), payment=()" },
];
export default {
async headers() {
return [{ source: "/(.*)", headers: securityHeaders }];
},
};
```
---
## Verification
```bash
# Check headers on a live site
curl -I https://example.com
# Use securityheaders.com for a grade
# https://securityheaders.com/?q=https://example.com
```
*Source: [MDN HTTP Headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers), [OWASP Secure Headers](https://owasp.org/www-project-secure-headers/)*
+200
View File
@@ -0,0 +1,200 @@
#!/usr/bin/env python3
"""Security audit scanner for common vulnerabilities.
Scans source files for hardcoded secrets, eval() usage, SQL string
concatenation, and sensitive data in console output. Outputs JSON.
Usage:
python security-audit.py ./src
python security-audit.py ./src --severity high --format pretty
"""
import argparse
import json
import os
import re
import sys
from dataclasses import asdict, dataclass, field
from pathlib import Path
SCAN_EXTENSIONS = {
".py", ".js", ".ts", ".jsx", ".tsx", ".java", ".go",
".rb", ".php", ".env", ".yaml", ".yml", ".toml", ".json",
}
SKIP_DIRS = {
"node_modules", ".git", "__pycache__", ".venv", "venv",
"dist", "build", ".next", ".nuxt", "vendor",
}
@dataclass
class Finding:
file: str
line: int
rule: str
severity: str
message: str
snippet: str
@dataclass
class AuditReport:
scanned_files: int = 0
findings: list = field(default_factory=list)
summary: dict = field(default_factory=dict)
# --- Detection Rules ---
SECRET_PATTERNS = [
(r'(?i)(api[_-]?key|apikey)\s*[=:]\s*["\'][A-Za-z0-9_\-]{16,}["\']', "Possible API key"),
(r'(?i)(secret|password|passwd|pwd)\s*[=:]\s*["\'][^"\']{8,}["\']', "Possible hardcoded secret"),
(r'(?i)(aws_access_key_id|aws_secret_access_key)\s*[=:]\s*["\'][^"\']+["\']', "AWS credential"),
(r'(?i)bearer\s+[A-Za-z0-9_\-\.]{20,}', "Possible bearer token"),
(r'(?i)(ghp_|gho_|github_pat_)[A-Za-z0-9_]{20,}', "GitHub token"),
(r'(?i)(sk-|pk_live_|pk_test_|sk_live_|sk_test_)[A-Za-z0-9]{20,}', "API secret key"),
(r'-----BEGIN\s+(RSA\s+)?PRIVATE\s+KEY-----', "Private key in source"),
]
EVAL_PATTERNS = [
(r'\beval\s*\(', "eval() usage detected"),
(r'\bexec\s*\(', "exec() usage detected (Python)"),
(r'new\s+Function\s*\(', "new Function() usage (dynamic code)"),
(r'\bchild_process\.exec\s*\(', "child_process.exec (command injection risk)"),
(r'subprocess\.call\s*\([^)]*shell\s*=\s*True', "subprocess with shell=True"),
(r'os\.system\s*\(', "os.system() usage (command injection risk)"),
]
SQL_PATTERNS = [
(r'(?i)(SELECT|INSERT|UPDATE|DELETE|DROP)\s+.*([\+]|\.format\(|f["\']|%\s)', "SQL string concatenation"),
(r'(?i)execute\s*\(\s*f["\']', "SQL f-string in execute()"),
(r'(?i)\.query\s*\(\s*`[^`]*\$\{', "SQL template literal injection"),
(r'(?i)\.raw\s*\(\s*f["\']', "Raw SQL with f-string"),
]
SENSITIVE_LOG_PATTERNS = [
(r'console\.log\s*\(.*(?i)(password|secret|token|key|credential)', "Sensitive data in console.log"),
(r'print\s*\(.*(?i)(password|secret|token|key|credential)', "Sensitive data in print()"),
(r'logger?\.(info|debug|warn)\s*\(.*(?i)(password|secret|token)', "Sensitive data in logger"),
]
RULES = [
("hardcoded-secret", "high", SECRET_PATTERNS),
("dangerous-eval", "high", EVAL_PATTERNS),
("sql-injection", "high", SQL_PATTERNS),
("sensitive-logging", "medium", SENSITIVE_LOG_PATTERNS),
]
def should_scan(path: Path) -> bool:
if path.suffix not in SCAN_EXTENSIONS:
return False
for part in path.parts:
if part in SKIP_DIRS:
return False
return True
def scan_file(filepath: Path) -> list[Finding]:
findings = []
try:
content = filepath.read_text(encoding="utf-8", errors="ignore")
except (OSError, PermissionError):
return findings
lines = content.splitlines()
for line_num, line in enumerate(lines, start=1):
stripped = line.strip()
if stripped.startswith(("#", "//", "*", "/*")):
continue
for rule_name, severity, patterns in RULES:
for pattern, message in patterns:
if re.search(pattern, line):
findings.append(Finding(
file=str(filepath),
line=line_num,
rule=rule_name,
severity=severity,
message=message,
snippet=line.strip()[:120],
))
return findings
def scan_directory(target: Path, severity_filter: str | None = None) -> AuditReport:
report = AuditReport()
severity_order = {"high": 3, "medium": 2, "low": 1}
min_severity = severity_order.get(severity_filter, 0) if severity_filter else 0
for root, dirs, files in os.walk(target):
dirs[:] = [d for d in dirs if d not in SKIP_DIRS]
for fname in files:
fpath = Path(root) / fname
if not should_scan(fpath):
continue
report.scanned_files += 1
for finding in scan_file(fpath):
if severity_order.get(finding.severity, 0) >= min_severity:
report.findings.append(finding)
report.summary = {
"total": len(report.findings),
"high": sum(1 for f in report.findings if f.severity == "high"),
"medium": sum(1 for f in report.findings if f.severity == "medium"),
"low": sum(1 for f in report.findings if f.severity == "low"),
"by_rule": {},
}
for f in report.findings:
report.summary["by_rule"][f.rule] = report.summary["by_rule"].get(f.rule, 0) + 1
return report
def main():
parser = argparse.ArgumentParser(
description="Scan source files for common security issues.",
epilog="Example: python security-audit.py ./src --severity high",
)
parser.add_argument("target", help="Directory or file to scan")
parser.add_argument(
"--severity", choices=["low", "medium", "high"],
help="Minimum severity to report (default: all)",
)
parser.add_argument(
"--format", choices=["json", "pretty"], default="json",
help="Output format (default: json)",
)
args = parser.parse_args()
target = Path(args.target)
if not target.exists():
print(f"Error: {target} does not exist", file=sys.stderr)
sys.exit(1)
report = scan_directory(target, args.severity)
output = {
"scanned_files": report.scanned_files,
"summary": report.summary,
"findings": [asdict(f) for f in report.findings],
}
if args.format == "pretty":
print(f"\nScanned {report.scanned_files} files\n")
print(f"Findings: {report.summary['total']} total "
f"({report.summary['high']} high, {report.summary['medium']} medium)")
print("-" * 60)
for f in report.findings:
print(f"[{f.severity.upper()}] {f.file}:{f.line}")
print(f" Rule: {f.rule}")
print(f" {f.message}")
print(f" > {f.snippet}")
print()
else:
print(json.dumps(output, indent=2))
sys.exit(1 if report.summary.get("high", 0) > 0 else 0)
if __name__ == "__main__":
main()
@@ -0,0 +1,120 @@
# Security Code Review Checklist
**Project**: _______________
**Reviewer**: _______________
**Date**: _______________
**Scope**: _______________
---
## Authentication and Session Management
- [ ] Passwords hashed with bcrypt/argon2 (not MD5/SHA1)
- [ ] Session tokens are cryptographically random
- [ ] Session cookies use `Secure`, `HttpOnly`, `SameSite` flags
- [ ] Session timeout is enforced (idle and absolute)
- [ ] Failed login attempts are rate-limited
- [ ] MFA is available for sensitive accounts
- [ ] Password reset tokens expire and are single-use
## Authorization and Access Control
- [ ] Access denied by default (allowlist approach)
- [ ] Server-side authorization on every request
- [ ] Resource ownership verified before access
- [ ] Role/permission checks cannot be bypassed via direct URL
- [ ] Admin endpoints have separate authentication
- [ ] CORS policy restricts allowed origins
## Input Validation
- [ ] All user input validated server-side
- [ ] Parameterized queries used for all database access
- [ ] No string concatenation in SQL/commands
- [ ] File uploads validated (type, size, content)
- [ ] Path traversal prevented on file operations
- [ ] JSON/XML parsers configured against XXE
## Output Encoding
- [ ] HTML output properly escaped (XSS prevention)
- [ ] Content-Type headers set correctly on all responses
- [ ] API responses do not leak stack traces in production
- [ ] Error messages do not reveal system internals
- [ ] Sensitive data excluded from logs
## Cryptography
- [ ] TLS 1.2+ enforced for all connections
- [ ] Sensitive data encrypted at rest
- [ ] No hardcoded secrets, keys, or passwords in source
- [ ] Secrets loaded from environment variables or vault
- [ ] Strong algorithms used (AES-256, RSA-2048+, SHA-256+)
- [ ] No custom cryptographic implementations
## Security Headers
- [ ] Content-Security-Policy configured
- [ ] Strict-Transport-Security enabled
- [ ] X-Frame-Options set to DENY
- [ ] X-Content-Type-Options set to nosniff
- [ ] Referrer-Policy configured
- [ ] Permissions-Policy restricts unused features
## Dependencies
- [ ] No known vulnerabilities (`npm audit` / `pip audit` clean)
- [ ] Unused dependencies removed
- [ ] Dependencies pinned to specific versions
- [ ] Lock file committed and up to date
## Logging and Monitoring
- [ ] Authentication events logged (success and failure)
- [ ] Authorization failures logged
- [ ] Sensitive data not written to logs
- [ ] Log injection prevented (user input sanitized in logs)
- [ ] Alerts configured for suspicious patterns
## API Security
- [ ] Rate limiting on all public endpoints
- [ ] Request size limits configured
- [ ] API keys/tokens not exposed in URLs
- [ ] Pagination enforced on list endpoints
- [ ] HTTPS required (HTTP redirects or blocks)
## Infrastructure
- [ ] Debug mode disabled in production
- [ ] Default credentials changed
- [ ] Unnecessary ports/services disabled
- [ ] Container runs as non-root user
- [ ] Environment variables not logged at startup
---
## Summary
| Category | Pass | Fail | N/A |
|----------|------|------|-----|
| Authentication | | | |
| Authorization | | | |
| Input Validation | | | |
| Output Encoding | | | |
| Cryptography | | | |
| Security Headers | | | |
| Dependencies | | | |
| Logging | | | |
| API Security | | | |
| Infrastructure | | | |
**Overall Assessment**: [ ] Pass / [ ] Conditional Pass / [ ] Fail
**Notes**:
**Follow-up Actions**: