|$ curl https://forge-ai.dev/api/markdown?path=docs/security
$cat docs/security-—-overview-&-defense-in-depth.md
updated Recently·18 min read·published

Security — Overview & Defense in Depth

SecurityIntermediate🎯Free Tools
Introduction

Web application security is the practice of protecting websites and APIs from attacks that could compromise data integrity, availability, or confidentiality. No single mechanism makes an application secure — security is achieved through layers of overlapping defenses.

This guide covers the foundational principles every developer needs: defense in depth, threat modeling, the CIA triad, attack surface reduction, and how to build security into your development lifecycle from day one.

The CIA Triad

Confidentiality, Integrity, and Availability form the foundation of information security. Every security decision maps back to protecting one or more of these properties.

PropertyDefinitionThreatsControls
ConfidentialityData is accessible only to authorized usersXSS, data leakage, insecure storageEncryption, access control, least privilege
IntegrityData is accurate and unalteredSQL injection, CSRF, tamperingHashing, signatures, validation, CSRF tokens
AvailabilitySystems are accessible when neededDDoS, resource exhaustion, deadlocksRate limiting, redundancy, autoscaling

info

When designing a feature, ask: "What happens if confidentiality/integrity/availability is violated here?" This simple exercise surfaces most security requirements before code is written.
Defense in Depth

Defense in depth means layering multiple independent security controls so that if one fails, another catches the threat. No single layer is sufficient on its own — each adds a barrier that an attacker must overcome.

defense-in-depth.txt
TEXT
1Layer 1: Network
2├── Firewall rules (block unnecessary ports)
3├── DDoS protection (Cloudflare, AWS Shield)
4├── WAF (Web Application Firewall)
5└── VPN / private networking for internal services
6
7Layer 2: Transport
8├── TLS 1.2+ everywhere (HSTS, certificate pinning)
9├── Mutual TLS for service-to-service
10└── Certificate transparency monitoring
11
12Layer 3: Application
13├── Input validation (allowlists, Zod schemas)
14├── Output encoding (prevent XSS)
15├── Authentication & session management
16├── Authorization (RBAC / ABAC)
17├── CSRF protection (tokens, SameSite cookies)
18└── Security headers (CSP, X-Frame-Options, etc.)
19
20Layer 4: Data
21├── Encryption at rest (AES-256-GCM)
22├── Encryption in transit (TLS)
23├── Database access controls (least privilege)
24├── Backup encryption
25└── Key management (Vault, AWS KMS)
26
27Layer 5: Monitoring & Response
28├── Security event logging (SIEM)
29├── Intrusion detection (IDS)
30├── Alerting on anomalies
31├── Incident response plan
32└── Forensics & audit trails

best practice

A WAF is not a substitute for input validation. Relying on a single layer creates a single point of failure. Every layer should be independently effective, and the failure of any one layer should not compromise the whole system.
Principle of Least Privilege

Every user, process, and service should have only the minimum permissions necessary to perform its function. This limits the blast radius of any compromise.

least-privilege.ts
TypeScript
1// Database — least privilege in practice
2// BAD: application uses root/admin database user
3const db = createConnection({
4 host: 'db.example.com',
5 user: 'root', // Can do anything
6 password: DB_PASS,
7});
8
9// GOOD: application uses a scoped user
10// GRANT SELECT, INSERT, UPDATE ON app_db.* TO 'app_user'@'%';
11// REVOKE DELETE, DROP, ALTER, CREATE ON app_db.* FROM 'app_user'@'%';
12const db = createConnection({
13 host: 'db.example.com',
14 user: 'app_user', // Only necessary operations
15 password: DB_PASS,
16 database: 'app_db',
17});
18
19// Service accounts — each service gets its own credentials
20const paymentService = createConnection({
21 user: 'payment_svc', // Only payment tables
22 // Cannot access user data, logs, or admin tables
23});
24
25const analyticsService = createConnection({
26 user: 'analytics_svc', // Read-only access to events
27 // Cannot write or modify any data
28});
29
30// IAM policy — AWS example (deny by default)
31const policy = {
32 Version: '2012-10-17',
33 Statement: [
34 {
35 Effect: 'Allow',
36 Action: ['s3:GetObject'],
37 Resource: 'arn:aws:s3:::my-bucket/public/*',
38 },
39 // Everything else is implicitly denied
40 ],
41};
Secure by Default

Systems should be secure out of the box. Every default configuration should be the safest option — users and developers should have to explicitly opt into less secure behavior, not opt out of it.

ComponentInsecure DefaultSecure Default
CORSAllow all origins (*)Deny all, explicit allowlist
SessionsNo SameSite, no Secure flagSameSite=Lax, Secure, HttpOnly
Error handlingFull stack traces to clientGeneric error message, log server-side
Admin panelAccessible in productionDisabled or IP-restricted
Debug endpointsExposed in all environmentsGated behind NODE_ENV=development
Threat Modeling with STRIDE

Threat modeling is the process of identifying what can go wrong before it does. STRIDE is a mnemonic that categorizes threats into six types, each with a corresponding security property.

stride-threat-model.txt
TEXT
1STRIDE Threat Model for a Web Application
2───────────────────────────────────────────
3
4S — Spoofing (Authentication)
5 Threat: Attacker pretends to be another user
6 Example: Stolen session cookie, phishing login page
7 Control: Strong auth, MFA, session management, CSRF tokens
8
9T — Tampering (Integrity)
10 Threat: Data is modified in transit or at rest
11 Example: SQL injection, modifying form data in transit
12 Control: Input validation, HMAC signatures, parameterized queries
13
14R — Repudiation (Non-repudiation)
15 Threat: User denies performing an action
16 Example: "I never transferred that money"
17 Control: Audit logging, digital signatures, immutable logs
18
19I — Information Disclosure (Confidentiality)
20 Threat: Data exposed to unauthorized parties
21 Example: XSS stealing tokens, verbose error messages
22 Control: Encryption, CSP, error sanitization, least privilege
23
24D — Denial of Service (Availability)
25 Threat: System made unavailable to legitimate users
26 Example: DDoS, resource exhaustion, regex DoS (ReDoS)
27 Control: Rate limiting, WAF, autoscaling, input length limits
28
29E — Elevation of Privilege (Authorization)
30 Threat: User gains higher access than intended
31 Example: IDOR, admin functions accessible without auth check
32 Control: RBAC, authorization middleware, server-side enforcement

info

Perform threat modeling early — during design, not after implementation. Even a 30-minute whiteboard session using STRIDE on your most critical feature will catch issues that would be expensive to fix later. Revisit the model when architecture changes.
Attack Surface Reduction

Every feature, endpoint, and integration is a potential entry point. Reducing the attack surface means removing unnecessary components, closing unused ports, and minimizing the amount of code and configuration that could be exploited.

attack-surface.ts
TypeScript
1// Attack surface reduction checklist
2
3// 1. Remove unused routes and endpoints
4// BAD: debug endpoint left in production
5app.get('/debug', (req, res) => {
6 res.json({ env: process.env, db: db.config }); // Exposed secrets!
7});
8
9// GOOD: gate behind environment check, or remove entirely
10if (process.env.NODE_ENV === 'development') {
11 app.get('/debug', (req, res) => {
12 res.json({ status: 'ok' });
13 });
14}
15
16// 2. Disable unnecessary HTTP methods
17app.use((req, res, next) => {
18 const allowed = ['GET', 'POST', 'OPTIONS'];
19 if (!allowed.includes(req.method)) {
20 return res.status(405).json({ error: 'Method not allowed' });
21 }
22 next();
23});
24
25// 3. Remove server identification headers
26app.disable('x-powered-by');
27app.disable('x-aspnet-version');
28app.disable('x-runtime');
29
30// 4. Limit request body size
31app.use(express.json({ limit: '100kb' }));
32app.use(express.urlencoded({ limit: '100kb', extended: false }));
33
34// 5. Set timeouts on incoming requests
35app.use((req, res, next) => {
36 req.setTimeout(30000); // 30 second timeout
37 res.setTimeout(30000);
38 next();
39});
40
41// 6. Remove directory listing and file serving defaults
42// Do not serve static files from the application root
Secure Software Development Lifecycle

Security should be integrated into every phase of development — not bolted on at the end. A Secure SDLC (S-SDLC) embeds security activities into requirements, design, implementation, testing, and deployment.

secure-sdlc.txt
TEXT
1Secure Development Lifecycle Phases
2────────────────────────────────────
3
41. Requirements
5 ├── Define security requirements (CIA for each feature)
6 ├── Identify compliance needs (GDPR, HIPAA, PCI-DSS)
7 └── Establish security acceptance criteria
8
92. Design
10 ├── Threat modeling (STRIDE on critical paths)
11 ├── Architecture security review
12 ├── Define trust boundaries and data flows
13 └── Select secure patterns and libraries
14
153. Implementation
16 ├── Secure coding guidelines (OWASP Cheat Sheets)
17 ├── Peer code review with security focus
18 ├── Static Application Security Testing (SAST)
19 └── Dependency vulnerability scanning
20
214. Testing
22 ├── Dynamic Application Security Testing (DAST)
23 ├── Penetration testing (manual + automated)
24 ├── Fuzz testing for input handling
25 └── Security regression tests
26
275. Deployment
28 ├── Infrastructure hardening (CIS benchmarks)
29 ├── Container image scanning
30 ├── Secrets management (no hardcoded credentials)
31 └── Security headers and CSP configuration
32
336. Operations
34 ├── Security event monitoring (SIEM)
35 ├── Vulnerability management and patching
36 ├── Incident response procedures
37 └── Regular security audits and re-assessment
Essential Security Headers

HTTP response headers are your first line of defense at the transport layer. They tell the browser how to behave — what to load, what to block, and what policies to enforce.

security-headers.ts
TypeScript
1// Helmet.js — sets 15+ security headers in one line
2import helmet from 'helmet';
3
4app.use(helmet());
5
6// What Helmet sets (and why each matters):
7
8// Content-Security-Policy (CSP)
9// Prevents XSS by whitelisting allowed sources for scripts, styles, etc.
10// Default: script-src 'self'
11
12// Strict-Transport-Security (HSTS)
13// Forces HTTPS for all future requests (including subdomains)
14Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
15
16// X-Content-Type-Options
17// Prevents MIME-type sniffing (loading CSS as HTML, etc.)
18X-Content-Type-Options: nosniff
19
20// X-Frame-Options
21// Prevents clickjacking by blocking iframe embedding
22X-Frame-Options: DENY
23
24// X-XSS-Protection
25// Legacy XSS filter (modern browsers rely on CSP instead)
26X-XSS-Protection: 0
27
28// Referrer-Policy
29// Controls how much referrer information is sent
30Referrer-Policy: strict-origin-when-cross-origin
31
32// Permissions-Policy
33// Controls browser features (camera, microphone, geolocation)
34Permissions-Policy: camera=(), microphone=(), geolocation=()
35
36// Custom configuration with Helmet
37app.use(helmet({
38 contentSecurityPolicy: {
39 directives: {
40 defaultSrc: ["'self'"],
41 scriptSrc: ["'self'", "'strict-dynamic'"],
42 styleSrc: ["'self'", "'unsafe-inline'"],
43 imgSrc: ["'self'", 'data:', 'https:'],
44 connectSrc: ["'self'", 'https://api.example.com'],
45 frameSrc: ["'none'"],
46 objectSrc: ["'none'"],
47 upgradeInsecureRequests: [],
48 },
49 },
50 hsts: {
51 maxAge: 63072000,
52 includeSubDomains: true,
53 preload: true,
54 },
55 referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
56}));

warning

CSP is the most powerful security header, but a misconfigured CSP can break your application. Start with Content-Security-Policy-Report-Only to test your policy without enforcing it. Collect violations, refine the policy, then switch to enforcement mode.
Input Validation & Output Encoding

The two most fundamental security practices: validate all input on the server (never trust the client), and encode all output before rendering (never embed raw data in HTML, JS, or SQL).

input-output-validation.ts
TypeScript
1// Validate input with Zod (server-side only)
2import { z } from 'zod';
3
4const CreateUserSchema = z.object({
5 name: z.string().min(1).max(100).regex(/^[a-zA-Z\s'-]+$/),
6 email: z.string().email().max(255),
7 age: z.number().int().min(13).max(150),
8 role: z.enum(['user', 'editor', 'admin']).default('user'),
9});
10
11// Always parse on the server — never trust client validation alone
12app.post('/api/users', (req, res) => {
13 const result = CreateUserSchema.safeParse(req.body);
14 if (!result.success) {
15 return res.status(400).json({
16 error: 'Validation failed',
17 details: result.error.issues,
18 });
19 }
20 // result.data is fully validated and typed
21 createUser(result.data);
22});
23
24// Output encoding — context-dependent
25// HTML context: escape <, >, &, ", '
26import escapeHtml from 'escape-html';
27const safe = escapeHtml(userInput); // <script> → &lt;script&gt;
28
29// JavaScript context: JSON.stringify or template literal escaping
30const safe = JSON.stringify(userInput); // Safe for embedding in JS
31
32// URL context: encode special characters
33const safe = encodeURIComponent(userInput);
34
35// SQL context: ALWAYS use parameterized queries
36await db.query('SELECT * FROM users WHERE name = $1', [userInput]);
Secure Error Handling

Error messages can leak sensitive information — stack traces reveal internal paths, database errors reveal schema, and version numbers reveal known vulnerabilities. Always sanitize errors before sending them to clients.

error-handling.ts
TypeScript
1// Secure error handling middleware (Express)
2class AppError extends Error {
3 constructor(public statusCode: number, message: string) {
4 super(message);
5 }
6}
7
8// Global error handler — last middleware in the chain
9app.use((err: Error, req, res, next) => {
10 // Log full error server-side (for debugging and SIEM)
11 console.error('[ERROR]', {
12 timestamp: new Date().toISOString(),
13 method: req.method,
14 path: req.path,
15 error: err.message,
16 stack: err.stack,
17 userId: req.session?.userId,
18 ip: req.ip,
19 });
20
21 // Determine if this is a known/expected error
22 if (err instanceof AppError) {
23 return res.status(err.statusCode).json({
24 error: err.message,
25 });
26 }
27
28 // Unknown errors — generic message, no details
29 res.status(500).json({
30 error: 'Internal server error',
31 // Never include: stack, database errors, file paths, version numbers
32 });
33});
34
35// Database errors — sanitize before leaking
36app.post('/api/users', async (req, res) => {
37 try {
38 await createUser(req.body);
39 res.json({ success: true });
40 } catch (err) {
41 // Database unique constraint violation
42 if (err.code === '23505') {
43 return res.status(409).json({ error: 'Email already exists' });
44 }
45 // All other DB errors — generic message
46 console.error('[DB]', err);
47 res.status(500).json({ error: 'Internal server error' });
48 }
49});
50
51// Custom error pages
52app.get('/404', (req, res) => {
53 res.status(404).json({ error: 'Not found' });
54});

danger

Never return stack traces, SQL errors, or internal paths to the client. These reveal your technology stack, file structure, and potential vulnerabilities. Log them server-side and return only generic error messages.
Production Security Checklist
CategoryItemPriority
TransportHTTPS everywhere, HSTS with preloadCritical
AuthRate limiting on login, MFA for adminsCritical
InputServer-side validation on all endpointsCritical
SessionsHttpOnly, Secure, SameSite cookiesCritical
HeadersCSP, X-Frame-Options, X-Content-Type-OptionsHigh
CSRFCSRF tokens on all state-changing requestsHigh
Dependenciesnpm audit in CI, Dependabot enabledHigh
SecretsNo hardcoded secrets, use env vars / vaultHigh
ErrorsGeneric error messages to clientsHigh
LoggingSecurity events logged, no secrets in logsMedium
MonitoringAlerting on 5xx spikes and auth failuresMedium
Best Practices
  • Never trust the client — validate and sanitize all input on the server.
  • Use deny-by-default for CORS, access control, and feature flags.
  • Encrypt sensitive data in transit (TLS) and at rest (AES-GCM).
  • Apply least privilege at every level: database users, API keys, IAM roles.
  • Automate security scanning in CI/CD: SAST, DAST, dependency audit, secret scanning.
  • Log security-relevant events and centralize logs for investigation.
  • Keep dependencies updated and audit for known CVEs regularly.
  • Use managed services for auth, secrets, and encryption when possible — don't roll your own.
  • Conduct threat modeling before implementation, not after a breach.
  • Train every developer on OWASP Top 10 and secure coding at least annually.

best practice

Security is a continuous process, not a one-time task. Start with the most critical controls (HTTPS, auth, input validation, CSRF) and progressively add layers. A partially-secure application is always better than waiting for perfection.
$Blueprint — Engineering Documentation·Section ID: SEC-OVERVIEW·Revision: 1.0

Community

Get help on Slack, Discord or VIP

Stuck on a guide? Join the community and ask.