Skip to main content

production-security

Production security hardening — secrets management, CORS policy, authentication patterns, authorization models, rate limiting, security headers, dependency scanning, and OWASP Top 10 awareness. Use this skill when the user works on authentication, authorization, secrets, CORS, rate limiting, security headers, or any security-adjacent code. Also trigger when user says /production security.

Jump to install

Source facts

Repository
vstorm-co/production-stack-skills
Last source activity
April 16, 2026 at 17:54
Detected SKILL.md language
English
Stars
25
Forks
7

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
5 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
production-security
description
Production security hardening — secrets management, CORS policy, authentication patterns, authorization models, rate limiting, security headers, dependency scanning, and OWASP Top 10 awareness. Use this skill when the user works on authentication, authorization, secrets, CORS, rate limiting, security headers, or any security-adjacent code. Also trigger when user says /production security.
# Production Security This skill encodes the security patterns that stop your application from becoming a headline. Every recommendation here comes from real breaches, real CVE exploits, and real incident reports — not theoretical threat models. The patterns are opinionated because security is not a place for "it depends." If you ship with `allow_origins=["*"]`, hardcoded API keys, or MD5 password hashes, you are not making a tradeoff — you are making a mistake. --- ## 1. Secrets Management **The #1 rule: secrets never touch code, ever.** Not in variables, not in comments, not in "temporary" config files, not in Docker build args. If `git log -p` or `docker history --no-trunc` can reveal a secret, you have a breach waiting to happen. ### Environment Variables for Local Dev ```python # settings.py — Pydantic settings, fails fast on missing secrets from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): model_config = SettingsConfigDict( env_file=".env", env_file_encoding="utf-8", case_sensitive=False, ) database_url: str # REQUIRED — no default, crash on startup if missing jwt_secret_key: str # REQUIRED stripe_api_key: str # REQUIRED redis_url: str = "redis://localhost:6379/0" environment: str = "development" ``` ```bash # .env — MUST be in .gitignore DATABASE_URL=postgresql://user:pass@localhost:5432/myapp JWT_SECRET_KEY=your-256-bit-secret-here STRIPE_API_KEY=sk_test_... ``` ### Secret Managers for Production Never use `.env` files in production. Use a proper secret manager: ```python # GCP Secret Manager from google.cloud import secretmanager def get_secret(project_id: str, secret_id: str, version: str = "latest") -> str: client = secretmanager.SecretManagerServiceClient() name = f"projects/{project_id}/secrets/{secret_id}/versions/{version}" response = client.access_secret_version(request={"name": name}) return response.payload.data.decode("UTF-8") # AWS Secrets Manager import boto3 def get_secret(secret_name: str, region: str = "us-east-1") -> str: client = boto3.client("secretsmanager", region_name=region) response = client.get_secret_value(SecretId=secret_name) return response["SecretString"] ``` Other production-grade options: HashiCorp Vault, Doppler, 1Password Secrets Automation. ### Docker: BuildKit Secrets Only ```dockerfile # syntax=docker/dockerfile:1 # GOOD — secret never appears in image layers RUN --mount=type=secret,id=pip_index_url \ PIP_INDEX_URL=$(cat /run/secrets/pip_index_url) \ pip install --no-cache-dir -r requirements.txt ``` ```bash DOCKER_BUILDKIT=1 docker build --secret id=pip_index_url,src=.pip_credentials . ``` **What NEVER to do:** ```dockerfile # ALL OF THESE LEAK SECRETS INTO IMAGE LAYERS: ARG DATABASE_URL=postgresql://... # visible in docker history ENV API_KEY=sk_live_... # visible in docker inspect COPY .env /app/.env # baked into a layer forever COPY id_rsa /root/.ssh/ # private key in the image ``` For complete container secret patterns, see **production-docker** section 4. ### Secret Rotation Design for rotation from day one. Secrets will be compromised — the question is how fast you can rotate. ```python # Restart-based rotation (minimum viable) # Change the secret in your secret manager, then rolling-restart the service. # Works if your deploy pipeline is fast (< 5 minutes). # Zero-downtime rotation (preferred for databases, API keys) # Accept both old and new secret during a transition window. def verify_jwt(token: str) -> dict: """Try current key first, fall back to previous key.""" for key in [settings.jwt_secret_key, settings.jwt_secret_key_previous]: if key is None: continue try: return jwt.decode(token, key, algorithms=["HS256"]) except jwt.InvalidSignatureError: continue raise HTTPException(status_code=401, detail="Invalid token") ``` ### Pre-Commit Hooks: Catch Secrets Before They Ship ```yaml # .pre-commit-config.yaml repos: - repo: https://github.com/Yelp/detect-secrets rev: v1.5.0 hooks: - id: detect-secrets args: ['--baseline', '.secrets.baseline'] - repo: https://github.com/gitleaks/gitleaks rev: v8.18.4 hooks: - id: gitleaks ``` ```bash # Generate baseline (marks existing false positives) detect-secrets scan > .secrets.baseline # One-time scan for hardcoded secrets gitleaks detect --source . --verbose trufflehog filesystem . --only-verified ``` ### Detection: Grep for Hardcoded Secrets ```bash # Run these on any codebase to find secrets that should not be there rg '(password|secret|api_key|token|private_key)\s*=\s*"[^"]{8,}"' -i --type py --type js --type ts rg '(PASSWORD|SECRET|API_KEY|TOKEN)\s*=\s*"[^"]{8,}"' rg 'sk_(live|test)_[a-zA-Z0-9]{20,}' # Stripe keys rg 'AKIA[0-9A-Z]{16}' # AWS access keys rg 'ghp_[a-zA-Z0-9]{36}' # GitHub personal access tokens rg 'xox[bpas]-[a-zA-Z0-9-]+' # Slack tokens rg '-----BEGIN (RSA |EC |DSA )?PRIVATE KEY-----' # Private keys ``` --- ## 2. CORS Configuration CORS (Cross-Origin Resource Sharing) controls which origins can make requests to your API from a browser. Get it wrong and you either block your own frontend or open your API to every site on the internet. ### What CORS Protects Against (And What It Does Not) **CORS prevents:** A malicious site (`evil.com`) from making authenticated requests to your API using a victim's browser cookies. Without CORS, any site could read your API responses if the user is logged in. **CORS does NOT prevent:** Server-to-server attacks, API key theft from client-side code, or attacks from non-browser clients (curl, Postman, bots). CORS is a browser-only enforcement mechanism. ### Python FastAPI ```python from fastapi.middleware.cors import CORSMiddleware # GOOD — specific origins, explicit methods and headers 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"], max_age=600, # preflight cache: 10 minutes ) ``` ```python # DANGEROUS — never do this in production app.add_middleware( CORSMiddleware, allow_origins=["*"], # Any site can make requests allow_credentials=True, # Combined with *, this is a security hole allow_methods=["*"], # Exposes every HTTP method allow_headers=["*"], # Accepts any header ) ``` ### Node.js Express ```javascript const cors = require('cors'); // GOOD — explicit whitelist const allowedOrigins = [ 'https://app.example.com', 'https://staging.example.com', ]; app.use(cors({ origin: (origin, callback) => { if (!origin || allowedOrigins.includes(origin)) { callback(null, true); } else { callback(new Error('Not allowed by CORS')); } }, credentials: true, methods: ['GET', 'POST', 'PUT', 'DELETE'], allowedHeaders: ['Authorization', 'Content-Type'], maxAge: 600, })); ``` ### CORS Rules - Never `allow_origins=["*"]` when `allow_credentials=True` — browsers actually reject this combination, but it signals you have not thought about your CORS policy - Whitelist specific origins — your frontend domain(s), your staging domain, nothing else - `allow_methods` — only the methods your API actually uses. If you do not support PATCH, do not allow PATCH - `allow_headers` — only `Authorization` and `Content-Type` for most APIs - `max_age` — set to 600 (10 min) to reduce preflight requests. Do not set to 86400 in dev (causes stale CORS debugging) --- ## 3. Authentication Patterns ### JWT: Short-Lived Access, Long-Lived Refresh ``` Access token: 15 minutes (carried on every request) Refresh token: 7 days (used only to get new access tokens) ``` Access tokens are short-lived so that if stolen, the damage window is small. Refresh tokens are long-lived but can be revoked server-side. ### JWT Storage: HttpOnly Cookies, Not localStorage ```python # Python FastAPI — set tokens as HttpOnly cookies from fastapi.responses import JSONResponse @app.post("/auth/login") async def login(credentials: LoginRequest, response: Response): user = authenticate(credentials.email, credentials.password) if not user: raise HTTPException(status_code=401, detail="Invalid credentials") access_token = create_access_token(user.id, expires_minutes=15) refresh_token = create_refresh_token(user.id, expires_days=7) response = JSONResponse(content={"message": "Logged in"}) response.set_cookie( key="access_token", value=access_token, httponly=True, # JavaScript cannot read this cookie secure=True, # HTTPS only samesite="lax", # CSRF protection max_age=900, # 15 minutes path="/", ) response.set_cookie( key="refresh_token", value=refresh_token, httponly=True, secure=True, samesite="lax", max_age=604800, # 7 days path="/auth/refresh", # only sent to refresh endpoint ) return response ``` **Why not localStorage?** Any XSS vulnerability gives the attacker full access to tokens stored in localStorage. HttpOnly cookies are invisible to JavaScript — XSS cannot read them. ### JWT Validation ```python # Python — PyJWT import jwt from datetime import datetime, timezone def validate_access_token(token: str) -> dict: try: payload = jwt.decode( token, settings.jwt_secret_key, algorithms=["HS256"], # ALWAYS specify algorithms explicitly options={ "require": ["exp", "sub", "iat"], "verify_exp": True, }, ) return payload except jwt.ExpiredSignatureError: raise HTTPException(status_code=401, detail="Token expired") except jwt.InvalidTokenError: raise HTTPException(status_code=401, detail="Invalid token") ``` ```javascript // Node.js — jsonwebtoken const jwt = require('jsonwebtoken'); function validateAccessToken(token) { try { return jwt.verify(token, process.env.JWT_SECRET_KEY, { algorithms: ['HS256'], // ALWAYS specify — prevents 'none' algorithm attack complete: false, }); } catch (err) { if (err.name === 'TokenExpiredError') { throw new HttpError(401, 'Token expired'); } throw new HttpError(401, 'Invalid token'); } } ``` ### Password Hashing: bcrypt or argon2id ```python # Python — passlib with bcrypt from passlib.context import CryptContext pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") def hash_password(password: str) -> str: return pwd_context.hash(password) def verify_password(plain: str, hashed: str) -> bool: return pwd_context.verify(plain, hashed) ``` ```python # Python — argon2id (preferred for new projects) from argon2 import PasswordHasher ph = PasswordHasher( time_cost=3, # iterations memory_cost=65536, # 64MB parallelism=4, ) def hash_password(password: str) -> str: return ph.hash(password) def verify_password(plain: str, hashed: str) -> bool: try: return ph.verify(hashed, plain) except Exception: return False ``` **Never use:** MD5, SHA1, SHA256 (unsalted), or any general-purpose hash for passwords. These are fast by design — attackers can brute-force billions of hashes per second. bcrypt and argon2id are intentionally slow. ### OAuth2 Flows - **Authorization Code** — for server-rendered web apps. Server exchanges code for tokens, tokens never touch the browser. - **Authorization Code + PKCE** — for SPAs and mobile apps. Same flow, but with a code verifier to prevent interception. - **Client Credentials** — for service-to-service. No user involved. - **Never use Implicit flow** — deprecated, tokens exposed in URL fragments. ### API Keys for Service-to-Service ```python # Hash API keys before storing (same as passwords) import hashlib import secrets def generate_api_key() -> tuple[str, str]: """Returns (raw_key_for_client, hashed_key_for_storage).""" raw_key = f"sk_{secrets.token_urlsafe(32)}" hashed = hashlib.sha256(raw_key.encode()).hexdigest() return raw_key, hashed def verify_api_key(raw_key: str, stored_hash: str) -> bool: return hashlib.sha256(raw_key.encode()).hexdigest() == stored_hash ``` **Never roll your own crypto.** Use established libraries: PyJWT, python-jose, passlib, argon2-cffi, jsonwebtoken (Node.js). If you are implementing a cryptographic primitive, you are doing it wrong. --- ## 4. Authorization Models Authentication answers "who are you?" Authorization answers "what can you do?" ### RBAC (Role-Based Access Control) Best for most applications. Users have roles, roles have permissions. ```python # FastAPI — role-based authorization with Depends from enum import Enum from fastapi import Depends, HTTPException class Role(str, Enum): ADMIN = "admin"
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub