security

Sensitive Param Filtering for Logs

If you ever need to hand logs to support, you don’t want secrets in them. Filter params at the framework level; then add custom filters for app-specific fields (API keys, tokens).

CORS configuration that’s explicit (no *)

CORS configs have a habit of getting more permissive over time until you’re basically allowing any origin. I keep an explicit allowlist and handle credentials carefully. If you allow cookies, you can’t use * as the origin. I also keep preflight respon

Safer HTML Sanitization Pipeline

User content needs defense in depth: markdown rendering + sanitization + link attribute hygiene. Keep the allowed tags list explicit and test it. Don’t trust upstream renderers to be safe by default.

Pre-signed S3 upload from the browser

Large file uploads don’t belong on app servers. My default is: the server issues a short-lived pre-signed URL, the browser uploads directly to S3, then the server stores the object key. That keeps latency low and costs predictable. Before signing, I v

Turbo Streams: partial page auth failure handling

When a session expires, Turbo requests can start returning 401/302 and the UI gets confusing. Handle unauthorized turbo requests explicitly: return a stream that updates a “session expired” banner or triggers a redirect.

Password hashing with Argon2

Bcrypt is fine, but Argon2 is the modern default with better resistance to GPU attacks. I store the full hash string (it includes parameters + salt) and keep verification in one utility so the rest of the app doesn’t grow its own auth helpers. The imp

Rate limiting by IP + user (Express)

A single abusive client can ruin your latency budget for everyone else, so I rate limit early rather than trying to ‘detect abuse’ after the outage starts. I combine an IP bucket with a user bucket: IP protects unauthenticated endpoints, user protects

Avoid caching sensitive pages in Turbo Drive

Turbo’s page cache is great until you have sensitive screens (account settings, billing) where the browser back button might show stale content. I handle this by setting cache-control headers and, for specific actions, disabling Turbo caching via turb

Next.js middleware for auth gating

Protecting routes at the middleware layer prevents a whole class of ‘oops, we forgot to check auth on one page’ bugs. middleware.ts runs before rendering, so unauthenticated users get redirected early and you don’t waste work. I keep the logic simple:

Robust Webhook Verification (HMAC + Timestamp)

Webhooks are a security boundary. Verify signatures with constant-time compare, include a timestamp window to prevent replay, and store processed event IDs to make handlers idempotent.

Safer File Attachments: Content Type + Size Validation

Active Storage makes uploads easy; production makes them dangerous. Validate content type and size at the model layer, and keep the error messages user-friendly. This prevents large or unexpected uploads from blowing up costs and processing queues.

Rate Limiting with Redis + Increment Expiry

A simple fixed-window rate limiter is often enough for endpoints like login, password reset, webhooks, or expensive searches. Use atomic Redis INCR + EXPIRE with a stable key and return remaining quota for UX.