Use when implementing authentication, authorization, input validation, or secrets management — or when auditing an existing service against OWASP Top 10 risks, configuring security headers, or running a STRIDE threat model.
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Um comando direto ignora o prompt de revisão. Verifique a origem antes de executá-lo.
Instruções da origem · Visualização somente leitura
name
security
description
Use when implementing authentication, authorization, input validation, or secrets management — or when auditing an existing service against OWASP Top 10 risks, configuring security headers, or running a STRIDE threat model.
Security
A comprehensive reference for securing backend APIs and web applications — covering authentication, authorization, secrets, input validation, security headers, threat modeling, and dependency hygiene across Python, TypeScript, and Go.
When to Activate
Implementing authentication or session management
Reviewing code for security vulnerabilities or injection risks
Handling secrets, API keys, or credentials in code or config
Designing authorization — who can access what
Configuring security headers for an API or web application
Setting up dependency scanning in CI
Running a threat model for a new feature or system
OWASP Top 10 Quick Reference
#
Vulnerability
Description
Primary Mitigation
A01
Broken Access Control
Users act outside intended permissions
Enforce ownership checks server-side on every request
A02
Cryptographic Failures
Sensitive data exposed due to weak/absent encryption
TLS everywhere, strong hashing, encrypted secrets at rest
Never construct queries or commands by concatenating user input.
SQL Injection
# BAD — string concatenation opens SQLi
query = f"SELECT * FROM users WHERE email = '{user_input}'"
cursor.execute(query)
# GOOD — parameterized query
cursor.execute("SELECT * FROM users WHERE email = %s", (user_input,))
// BADconst rows = await db.query(`SELECT * FROM users WHERE email = '${userInput}'`);
// GOODconst rows = await db.query("SELECT * FROM users WHERE email = $1", [userInput]);
// BAD
row := db.QueryRow("SELECT * FROM users WHERE email = '" + userInput + "'")
// GOOD
row := db.QueryRow("SELECT * FROM users WHERE email = ?", userInput)
Command Injection
# BAD — shell=True with user input is always dangerousimport subprocess
subprocess.run(f"convert {filename}", shell=True)
# GOOD — pass args as a list, shell=False (default)
subprocess.run(["convert", filename])
// BADexec(`convert ${filename}`);
// GOOD — use execFile with an array of argumentsimport { execFile } from"child_process";
execFile("convert", [filename]);
// BAD
cmd := exec.Command("sh", "-c", "convert "+filename)
// GOOD
cmd := exec.Command("convert", filename)
Broken Access Control (A01) — Deep Dive
Check ownership at the handler level, not only role membership. A role check alone prevents vertical privilege escalation (a regular user doing admin things) but not horizontal privilege escalation (a user accessing another user's resources — IDOR).
IDOR (Insecure Direct Object Reference)
# BAD — checks auth, but not ownership@app.get("/invoices/{invoice_id}")defget_invoice(invoice_id: int, current_user=Depends(get_current_user)):
return db.query(Invoice).filter(Invoice.id == invoice_id).first()
# GOOD — verifies the resource belongs to the caller@app.get("/invoices/{invoice_id}")defget_invoice(invoice_id: int, current_user=Depends(get_current_user)):
invoice = db.query(Invoice).filter(
Invoice.id == invoice_id,
Invoice.owner_id == current_user.id, # ownership check
).first()
ifnot invoice:
raise HTTPException(status_code=404)
return invoice
Escalation Type
Description
Example
Vertical
Lower-privileged user accesses higher-privileged functions
Regular user calls admin endpoint
Horizontal
User accesses another user's data at the same privilege level
Suppress stack traces in production — return generic error messages, log details server-side.
Audit S3 bucket ACLs: aws s3api get-bucket-acl --bucket <name> — buckets must never be public-read or public-read-write unless serving static public assets intentionally.
CORS must allowlist specific origins; Access-Control-Allow-Origin: * disables same-origin protection for all browsers.
Authentication Patterns
Password Hashing
Never store plaintext passwords or hashes produced by MD5, SHA-1, or unsalted SHA-256. Use a slow, adaptive hashing algorithm: bcrypt (work factor ≥ 12) or argon2id.
<!-- BAD — localStorage is readable by any JavaScript on the page (XSS risk) -->
localStorage.setItem("access_token", token);
<!-- GOOD — httpOnly cookie is inaccessible to JavaScript --><!-- Set by server: Set-Cookie: access_token=<jwt>; HttpOnly; Secure; SameSite=Strict -->
Refresh token rotation: Issue a short-lived access token (15 min) alongside a long-lived refresh token (7 days). On each refresh, invalidate the old refresh token and issue a new one. Detect token reuse — if a refresh token is used twice, revoke the entire family.
App redirects user to IdP authorization URL with code_challenge and code_challenge_method=S256.
User authenticates at IdP; IdP redirects back with authorization_code.
App exchanges authorization_code + code_verifier (not the challenge) at token endpoint.
IdP verifies SHA256(code_verifier) == code_challenge stored from step 3, then returns tokens.
App stores access token in httpOnly cookie; never in localStorage.
Build vs Buy
Use a third-party IdP (Auth0, Clerk, Cognito, Okta) unless you have exceptional requirements. Rolling your own OAuth2/OIDC server is a multi-month project with serious security risk surface: token endpoint, PKCE, refresh rotation, MFA, brute-force protection, RBAC, and audit logging all need to be correct simultaneously.
Scope Design
# BAD — wildcard and coarse scopes leak excessive access
scopes: ["admin", "*", "readwrite"]
# GOOD — narrow, resource-specific scopes
scopes: ["read:invoices", "write:invoices", "read:profile"]
Never grant admin or wildcard scopes to third-party integrations. Use the principle of least privilege — request only the scopes needed for the operation.
Authorization Patterns
RBAC vs ABAC
Dimension
RBAC
ABAC
Definition
Permissions assigned to roles; users assigned to roles
Permissions derived from attributes of user, resource, environment
Check ownership in the handler, not only in middleware. Middleware can verify the token is valid and extract the role — it cannot verify the requested resource belongs to the caller.
// BAD — only checks role, not resource ownership
router.delete("/posts/:id", requireRole("user"), async (req, res) => {
await db.posts.delete({ where: { id: req.params.id } });
res.sendStatus(204);
});
// GOOD — verifies caller owns the resource before deleting
router.delete("/posts/:id", requireRole("user"), async (req, res) => {
const post = await db.posts.findUnique({ where: { id: req.params.id } });
if (!post) return res.sendStatus(404);
if (post.authorId !== req.user.id) return res.sendStatus(403); // ownership checkawait db.posts.delete({ where: { id: req.params.id } });
res.sendStatus(204);
});
Policy Object Pattern
For complex logic, encapsulate authorization decisions in a policy object rather than scattering if checks across handlers.
Checking permissions only in the UI — API endpoints are directly reachable; always enforce server-side.
Enforcing access control on reads but not on writes — verify on every create, update, and delete.
Relying on obscurity — never assume an endpoint is safe because it is undocumented.
Missing re-authorization after role changes — invalidate sessions or tokens when a user's role is downgraded.
Secrets Management
The rule: never commit secrets. A .env file is for local development only — add it to .gitignore and never commit it to version control.
# BAD — hardcoded secret in source code
SECRET_KEY = "sk_live_abc123supersecretkey"
DATABASE_URL = "postgres://admin:password123@prod-db/app"# GOOD — read from environment at runtimeimport os
SECRET_KEY = os.environ["SECRET_KEY"]
DATABASE_URL = os.environ["DATABASE_URL"]
Secrets Progression by Environment
Environment
Secret Storage
Notes
Local Dev
.env file (gitignored)
Never commit; use .env.example with fake values for documentation
Always use parameterized queries or an ORM with parameter binding. No exceptions for "safe" input — the validation layer can be bypassed.
# BAD
cursor.execute("INSERT INTO orders (user_id, item) VALUES ('" + user_id + "', '" + item + "')")
# GOOD — psycopg2 / SQLAlchemy
cursor.execute("INSERT INTO orders (user_id, item) VALUES (%s, %s)", (user_id, item))
// BADawait pool.query(`INSERT INTO orders (user_id, item) VALUES ('${userId}', '${item}')`);
// GOOD — pg / Prisma / Drizzleawait pool.query("INSERT INTO orders (user_id, item) VALUES ($1, $2)", [userId, item]);
// BAD
db.Exec("INSERT INTO orders (user_id, item) VALUES ('" + userID + "', '" + item + "')")
// GOOD
db.Exec("INSERT INTO orders (user_id, item) VALUES (?, ?)", userID, item)
File Upload Validation
import magic # python-magic (libmagic binding)
ALLOWED_TYPES = {"image/jpeg", "image/png", "application/pdf"}
MAX_SIZE_BYTES = 10 * 1024 * 1024# 10 MBdefvalidate_upload(file_bytes: bytes, claimed_extension: str) -> None:
iflen(file_bytes) > MAX_SIZE_BYTES:
raise ValueError("File too large")
detected_mime = magic.from_buffer(file_bytes, mime=True)
if detected_mime notin ALLOWED_TYPES:
raise ValueError(f"File type not allowed: {detected_mime}")
# Store to a path outside the web root — never serve raw uploads directly
Key rules:
Detect MIME type from file content (magic bytes), not from the file extension or Content-Type header.
Enforce a maximum size before reading the full payload into memory.
Store uploaded files outside the web root or in object storage, never in a publicly served directory.
Rename files on storage — never preserve the user-supplied filename.
HTML Output Escaping
// BAD — executes arbitrary JS if userInput contains <script>...</script>
element.innerHTML = userInput;
// GOOD — use textContent for plain text
element.textContent = userInput;
// GOOD — for rich HTML, use a sanitizer libraryimportDOMPurifyfrom"dompurify";
element.innerHTML = DOMPurify.sanitize(userInput);
Use auto-escaping templating engines (Jinja2, Go html/template, React JSX) — they escape by default. Only bypass escaping intentionally with | safe / dangerouslySetInnerHTML and only on content you fully control.
Validation Libraries
Language
Library
Notes
Python
pydantic
Schema-first; validates at parse time; ideal for FastAPI
TypeScript
zod
Runtime schema validation; pairs well with tRPC
Go
go-playground/validator
Struct tag–based validation; widely used with Gin/Echo
Validate at the API boundary — reject invalid input before it reaches business logic or the database.
Security Headers
Set these headers on every HTTP response from your API or web server.
# BAD — allows any origin to make credentialed requests
CORS(app, origins="*", supports_credentials=True)
# GOOD — explicit allowlist; never wildcard for authenticated routes
CORS(app, origins=["https://app.example.com", "https://admin.example.com"])
Access-Control-Allow-Origin: * is acceptable only for fully public, unauthenticated endpoints (e.g., a public API with no user data). Never combine * with Access-Control-Allow-Credentials: true — browsers reject it, and it would be a severe security flaw if they did not.
Authorization checks on every operation, principle of least privilege
Threat Modeling Process
Draw a data flow diagram (DFD). Identify every external entity, process, data store, and data flow in the feature.
Mark trust boundaries. A trust boundary is crossed wherever data moves between different trust levels — browser to API, API to database, service to third-party, internal service to internal service.
Apply STRIDE to each data flow and process. For each element, ask: can it be Spoofed? Tampered? Repudiated? Disclosed? Denied? Escalated?
List mitigations. For each identified threat, record the control that addresses it (or note that it is accepted risk with justification).
Re-review when the design changes. A threat model is not a one-time artifact — revisit it when auth, data flows, or trust boundaries change.
Lightweight threat model template:
Data Flow
STRIDE Threats Identified
Mitigation
Browser → API (login)
Spoofing (credential stuffing), DoS (brute force)
Rate limit login endpoint, MFA, account lockout
API → Database
Injection (A03), Information Disclosure
Parameterized queries, least-privilege DB user
API → Third-party payment service
Tampering (webhook), Repudiation
Verify webhook HMAC signature, log all events
S3 presigned URL → Browser
Information Disclosure
Short-lived URLs (15 min), bucket policy denies public access
Dependency Scanning
Tools by Language
Language
Tool
Purpose
Python
pip-audit
Scans installed packages against OSV/PyPI advisory database
Python
safety
CVE scanning; integrates with CI
Python
Dependabot
Automated PRs for vulnerable package updates
TypeScript
npm audit
Built-in; checks against npm advisory database
TypeScript
socket.dev
Supply chain analysis — detects typosquatting and malicious packages
TypeScript
Snyk
CVE scanning with fix PRs
Go
govulncheck
Official Go vulnerability scanner from the Go team; checks call graph
Fail the CI build on HIGH or CRITICAL severity findings. Treat a new HIGH CVE in a direct dependency the same as a failing test — it blocks the merge.
Automated Dependency Updates
Configure Dependabot (/.github/dependabot.yml) or Renovate to open automated PRs when dependency updates are available. Keep the update cadence short (weekly) to avoid large, risky batch upgrades.
JWT validated on signature only, not expiry or audience — a valid signature on an expired or wrong-audience token still passes; always check exp, nbf, and aud claims
RBAC without object-level ownership check — checking role but not resource ownership lets any admin-role user access other users' data; always verify resource.owner_id == current_user.id
Input validation inside business logic — validate at the request boundary (controller/handler); by the time data reaches domain logic it should already be trusted and clean
STRIDE skipped for "small" features — new auth paths, file uploads, and webhooks introduce entire attack classes; STRIDE at design time costs minutes, post-incident costs days
Dependency scanning only at release — new CVEs are published daily; run dep scanning on every PR and on a nightly schedule against the default branch
Cookie flags missing on session tokens — without HttpOnly, Secure, and SameSite=Strict, session cookies are vulnerable to XSS theft and CSRF; set all three
Auth middleware applied to route groups but not verified per handler — a misconfigured route group silently bypasses middleware; verify auth and ownership explicitly in each sensitive handler
Checklist
Passwords hashed with bcrypt (factor 12+) or argon2id — never MD5/SHA1/plaintext
JWT signed with RS256 (asymmetric) or HS256 with strong secret (32+ random bytes)
Access tokens short-lived (15 min max); refresh tokens rotate on every use
Secrets stored in vault/secrets manager in production — never in env files committed to git