|$ curl https://forge-ai.dev/api/markdown?path=docs/security/hardening
$cat docs/security-hardening.md
updated Recently·35 min read·published

Security Hardening

SecurityHardeningIntermediate to Advanced
Introduction

Security hardening is the process of configuring systems and applications to reduce the attack surface. It involves implementing security headers, input validation, rate limiting, dependency auditing, and following OWASP guidelines.

Security is not a one-time task — it is an ongoing practice of monitoring, patching, and improving. This guide covers the most impactful security measures you can implement for a web application.

OWASP Top 10 (2021)

The OWASP Top 10 represents the most critical security risks to web applications. Understanding these is the foundation of application security.

RankRiskDescription
A01Broken Access ControlUsers can access resources beyond their permissions
A02Cryptographic FailuresWeak encryption, exposed sensitive data
A03InjectionSQL, NoSQL, OS command injection attacks
A04Insecure DesignArchitecture-level security gaps
A05Security MisconfigurationDefault credentials, unpatched systems
A06Vulnerable & Outdated ComponentsKnown CVEs in dependencies
A07Identification & Auth FailuresWeak passwords, session management flaws
A08Software & Data Integrity FailuresSupply chain attacks, unsigned updates
A09Security Logging & MonitoringInsufficient detection of breaches
A10Server-Side Request Forgery (SSRF)Server makes requests to internal resources

best practice

Run an OWASP Top 10 scan as part of your CI/CD pipeline. Tools like OWASP ZAP, Burp Suite, and Snyk can automate vulnerability scanning. Fix critical and high-severity findings before deployment.
Content Security Policy (CSP)

CSP is a browser security mechanism that mitigates XSS (cross-site scripting) and data injection attacks by specifying which sources of content are allowed to load.

csp-config.js
TypeScript
1// CSP Headers — Next.js middleware or next.config.js
2// next.config.js
3const cspHeader = `
4 default-src 'self';
5 script-src 'self' 'unsafe-eval' 'unsafe-inline' https://www.googletagmanager.com;
6 style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
7 img-src 'self' blob: data: https://*.example.com https://www.google-analytics.com;
8 font-src 'self' https://fonts.gstatic.com;
9 connect-src 'self' https://api.example.com https://*.sentry.io wss://*.pusher.com;
10 frame-src 'self' https://www.youtube.com;
11 object-src 'none';
12 base-uri 'self';
13 form-action 'self';
14 frame-ancestors 'none';
15 block-all-mixed-content;
16 upgrade-insecure-requests;
17`;
18
19module.exports = {
20 async headers() {
21 return [
22 {
23 source: '/(.*)',
24 headers: [
25 {
26 key: 'Content-Security-Policy',
27 value: cspHeader.replace(/\s{2,}/g, ' ').trim(),
28 },
29 ],
30 },
31 ];
32 },
33};
34
35// Strict CSP for static pages
36const strictCsp = {
37 'default-src': "'self'",
38 'script-src': "'self'",
39 'style-src': "'self' 'unsafe-inline'",
40 'img-src': "'self' data:",
41 'font-src': "'self'",
42 'connect-src': "'self'",
43 'frame-ancestors': "'none'",
44 'base-uri': "'self'",
45 'form-action': "'self'",
46};
47
48// Reporting-only mode (monitor before enforcing)
49// Content-Security-Policy-Report-Only:
50// This logs violations without blocking them

warning

Start with Content-Security-Policy-Report-Only before enforcing a full CSP. Many third-party scripts (analytics, ads, chatbots) require specific CSP directives, and blocking them can break functionality. Use a reporting endpoint to collect violations, adjust the policy, then enforce it.
Rate Limiting

Rate limiting prevents brute-force attacks, credential stuffing, and API abuse by restricting the number of requests a client can make in a given time window.

rate-limiting.ts
TypeScript
1// Next.js API route rate limiting with Upstash
2import { Ratelimit } from '@upstash/ratelimit';
3import { Redis } from '@upstash/redis';
4
5const ratelimit = new Ratelimit({
6 redis: Redis.fromEnv(),
7 limiter: Ratelimit.slidingWindow(10, '10 s'), // 10 requests per 10 seconds
8 analytics: true,
9});
10
11export default async function handler(req, res) {
12 const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
13 const { success, limit, remaining, reset } = await ratelimit.limit(ip);
14
15 // Set rate limit headers
16 res.setHeader('X-RateLimit-Limit', limit);
17 res.setHeader('X-RateLimit-Remaining', remaining);
18 res.setHeader('X-RateLimit-Reset', new Date(reset).toISOString());
19
20 if (!success) {
21 return res.status(429).json({
22 error: 'Too many requests',
23 retryAfter: Math.ceil((reset - Date.now()) / 1000),
24 });
25 }
26
27 // Process the request
28 res.status(200).json({ message: 'Success' });
29}
30
31// Middleware-based rate limiting (for all routes)
32import { Ratelimit } from '@upstash/ratelimit';
33import { Redis } from '@upstash/redis';
34import type { NextRequest } from 'next/server';
35
36const ratelimit = new Ratelimit({
37 redis: Redis.fromEnv(),
38 limiter: Ratelimit.slidingWindow(100, '60 s'), // 100 requests per minute
39});
40
41export async function middleware(request: NextRequest) {
42 const ip = request.headers.get('x-forwarded-for') || 'unknown';
43 const { success } = await ratelimit.limit(ip);
44
45 if (!success) {
46 return new Response('Too Many Requests', { status: 429 });
47 }
48
49 return NextResponse.next();
50}
51
52// Rate limit tiers:
53// API: 10 req/10s (authenticated), 5 req/10s (unauthenticated)
54// Auth endpoints: 5 req/min (login attempts)
55// Public pages: 100 req/min per IP

info

Use a sliding window algorithm for rate limiting instead of fixed windows (which can allow burst at boundaries). Distribute rate limit state across a shared Redis instance when running multiple server instances. Set different rate limits for different endpoints — login endpoints need stricter limits than public content.
Dependency Auditing

Vulnerable dependencies are a leading cause of security breaches. Regular auditing with npm audit, Snyk, or GitHub Dependabot helps identify and fix known vulnerabilities.

dependency-audit.sh
Bash
1# npm audit — Check for vulnerabilities
2npm audit
3
4# Detailed audit with JSON output
5npm audit --json
6
7# Fix vulnerabilities automatically (when possible)
8npm audit fix
9
10# Install only with force (bypass audit — AVOID)
11# npm install --audit=false
12
13# GitHub Dependabot configuration (.github/dependabot.yml)
14version: 2
15updates:
16 - package-ecosystem: "npm"
17 directory: "/"
18 schedule:
19 interval: "weekly"
20 day: "monday"
21 time: "09:00"
22 timezone: "America/New_York"
23 open-pull-requests-limit: 10
24 labels:
25 - "dependencies"
26 - "automerge"
27 reviewers:
28 - "team-lead"
29 ignore:
30 - dependency-name: "eslint"
31 versions: ["9.x"]
32
33 - package-ecosystem: "docker"
34 directory: "/"
35 schedule:
36 interval: "weekly"
37
38 - package-ecosystem: "github-actions"
39 directory: "/"
40 schedule:
41 interval: "weekly"
42
43# Snyk CLI — More comprehensive than npm audit
44npm install -g snyk
45snyk auth
46snyk test
47snyk monitor # Continuous monitoring
48snyk code test # SAST (Static Application Security Testing)
49
50# Trivy — Container image scanning
51trivy image my-app:latest

danger

npm audit fix may introduce breaking changes by updating major versions. Review the changelog of each updated package, especially for major version bumps. In CI, use npm audit --audit-level=high to fail builds on high-severity vulnerabilities only, avoiding noise from low-severity issues.
Helmet.js & CORS Configuration

Helmet.js is a collection of Express middleware that sets security-related HTTP headers. CORS (Cross-Origin Resource Sharing) controls which domains can access your API.

helmet-cors.ts
TypeScript
1// Express/Node.js — Helmet.js security headers
2import helmet from 'helmet';
3import cors from 'cors';
4import express from 'express';
5
6const app = express();
7
8// Apply all Helmet middleware defaults
9app.use(helmet());
10
11// Or customize individual directives
12app.use(
13 helmet({
14 contentSecurityPolicy: false, // Set CSP separately (see previous section)
15 crossOriginEmbedderPolicy: false,
16 crossOriginResourcePolicy: { policy: 'cross-origin' },
17 hsts: {
18 maxAge: 31536000,
19 includeSubDomains: true,
20 preload: true,
21 },
22 referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
23 frameguard: { action: 'deny' },
24 })
25);
26
27// CORS configuration
28const corsOptions = {
29 origin: process.env.NODE_ENV === 'production'
30 ? ['https://example.com', 'https://www.example.com']
31 : '*',
32 methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
33 allowedHeaders: ['Content-Type', 'Authorization', 'X-CSRF-Token'],
34 exposedHeaders: ['X-RateLimit-Remaining'],
35 credentials: true,
36 maxAge: 86400, // Preflight cache 24h
37};
38
39app.use(cors(corsOptions));
40
41// In Next.js API routes, set CORS headers manually:
42export default function handler(req, res) {
43 res.setHeader('Access-Control-Allow-Origin', 'https://example.com');
44 res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT');
45 res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
46
47 if (req.method === 'OPTIONS') {
48 res.status(200).end(); // Handle preflight
49 return;
50 }
51
52 // ... handle the request
53}
54
55// Security headers set by Helmet (default):
56// Content-Security-Policy (can be customized)
57// Cross-Origin-Embedder-Policy
58// Cross-Origin-Opener-Policy
59// Cross-Origin-Resource-Policy
60// Expect-CT
61// Origin-Agent-Cluster
62// Referrer-Policy
63// Strict-Transport-Security (HSTS)
64// X-Content-Type-Options
65// X-DNS-Prefetch-Control
66// X-Download-Options
67// X-Frame-Options (anti-clickjacking)
68// X-Permitted-Cross-Domain-Policies
69// X-Powered-By (removed)
70// X-XSS-Protection
Input Sanitization & Validation

Never trust user input. All input must be validated (type, format, length) and sanitized (remove malicious content) before processing or storage.

input-sanitization.ts
TypeScript
1// Input validation with Zod
2import { z } from 'zod';
3
4// API request validation schema
5const CreateUserSchema = z.object({
6 email: z.string().email('Invalid email format'),
7 name: z.string().min(2, 'Name too short').max(100, 'Name too long'),
8 age: z.number().int().positive().optional(),
9 role: z.enum(['user', 'admin', 'moderator']).default('user'),
10});
11
12// Sanitize HTML content
13import DOMPurify from 'isomorphic-dompurify';
14
15function sanitizeHtml(input: string): string {
16 return DOMPurify.sanitize(input, {
17 ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'code', 'pre'],
18 ALLOWED_ATTR: ['href', 'target', 'rel'],
19 });
20}
21
22// SQL injection prevention — Always use parameterized queries
23// BAD (vulnerable):
24const query = `SELECT * FROM users WHERE id = '${req.query.id}'`;
25
26// GOOD (safe):
27const result = await db.query('SELECT * FROM users WHERE id = $1', [req.query.id]);
28
29// NoSQL injection prevention (MongoDB)
30// BAD:
31const user = await User.find({ username: req.body.username, password: req.body.password });
32
33// GOOD (sanitize and validate first, then query with typed schema):
34const credentials = LoginSchema.parse(req.body);
35const user = await User.findOne({
36 username: credentials.username,
37 password: hash(credentials.password),
38});
39
40// XSS prevention — Always encode output
41// React does this automatically with JSX
42// dangerouslySetInnerHTML should be an absolute last resort

best practice

Use Zod (or similar) for runtime type checking and validation at every API boundary. Validate on both client and server — client validation improves UX, server validation is mandatory for security. Use parameterized queries for all database operations to prevent injection.
HTTPS Enforcement

HTTPS encrypts all data between the browser and the server. HSTS (HTTP Strict Transport Security) tells browsers to always use HTTPS, preventing downgrade attacks.

https-enforcement.ts
TypeScript
1// Next.js — Force HTTPS (in next.config.js)
2module.exports = {
3 async headers() {
4 return [
5 {
6 source: '/(.*)',
7 headers: [
8 {
9 key: 'Strict-Transport-Security',
10 value: 'max-age=63072000; includeSubDomains; preload',
11 },
12 ],
13 },
14 ];
15 },
16};
17
18// Nginx — Redirect HTTP to HTTPS
19server {
20 listen 80;
21 server_name example.com www.example.com;
22 return 301 https://$server_name$request_uri;
23}
24
25// Add your site to HSTS preload list:
26// https://hstspreload.org/
27// Requirements:
28// - Valid HTTPS certificate
29// - Redirect all HTTP to HTTPS
30// - HSTS header with max-age >= 31536000 and includeSubDomains
31// - HSTS header on the main domain
32
33// Check HTTPS configuration:
34// https://www.ssllabs.com/ssltest/
35// https://securityheaders.com/
$Blueprint — Engineering Documentation·Section ID: SEC-HARDENING-01·Revision: 1.0