- Add comprehensive database migrations (001-024) for schema evolution - Enhance API schema with expanded type definitions and resolvers - Add new middleware: audit logging, rate limiting, MFA enforcement, security, tenant auth - Implement new services: AI optimization, billing, blockchain, compliance, marketplace - Add adapter layer for cloud integrations (Cloudflare, Kubernetes, Proxmox, storage) - Update Crossplane provider with enhanced VM management capabilities - Add comprehensive test suite for API endpoints and services - Update frontend components with improved GraphQL subscriptions and real-time updates - Enhance security configurations and headers (CSP, CORS, etc.) - Update documentation and configuration files - Add new CI/CD workflows and validation scripts - Implement design system improvements and UI enhancements
142 lines
4.5 KiB
TypeScript
142 lines
4.5 KiB
TypeScript
/**
|
|
* Security Middleware
|
|
* Implements security headers and protections per DoD/MilSpec standards
|
|
*
|
|
* Complies with:
|
|
* - DISA STIG: Web Server Security
|
|
* - NIST SP 800-53: SI-4 (Information System Monitoring)
|
|
* - NIST SP 800-171: 3.13.1 (Cryptographic Protection in Transit)
|
|
*/
|
|
|
|
import { FastifyRequest, FastifyReply } from 'fastify'
|
|
import { randomBytes } from 'crypto'
|
|
|
|
/**
|
|
* Add security headers to responses per DoD/MilSpec requirements
|
|
*
|
|
* Implements comprehensive security headers as required by:
|
|
* - DISA STIG for Web Servers
|
|
* - OWASP Secure Headers Project
|
|
* - DoD Security Technical Implementation Guides
|
|
*/
|
|
export async function securityHeadersMiddleware(
|
|
request: FastifyRequest,
|
|
reply: FastifyReply
|
|
): Promise<void> {
|
|
// Prevent MIME type sniffing (DISA STIG requirement)
|
|
reply.header('X-Content-Type-Options', 'nosniff')
|
|
|
|
// Prevent clickjacking attacks (DISA STIG requirement)
|
|
reply.header('X-Frame-Options', 'DENY')
|
|
|
|
// Legacy XSS protection (deprecated but still recommended for older browsers)
|
|
reply.header('X-XSS-Protection', '1; mode=block')
|
|
|
|
// HTTP Strict Transport Security (HSTS) with preload
|
|
// max-age: 1 year (31536000 seconds)
|
|
// includeSubDomains: Apply to all subdomains
|
|
// preload: Allow inclusion in HSTS preload lists
|
|
const hstsMaxAge = 31536000 // 1 year
|
|
reply.header('Strict-Transport-Security', `max-age=${hstsMaxAge}; includeSubDomains; preload`)
|
|
|
|
// Content Security Policy (CSP) per STIG requirements
|
|
// Strict CSP to prevent XSS and injection attacks
|
|
// Generate nonce for inline scripts/styles to avoid unsafe-inline
|
|
const nonce = randomBytes(16).toString('base64')
|
|
// Store nonce in request for use in templates
|
|
;(request as any).cspNonce = nonce
|
|
|
|
const csp = [
|
|
"default-src 'self'",
|
|
`script-src 'self' 'nonce-${nonce}'`, // Use nonce instead of unsafe-inline
|
|
`style-src 'self' 'nonce-${nonce}'`, // Use nonce instead of unsafe-inline
|
|
"img-src 'self' data: https:",
|
|
"font-src 'self' data:",
|
|
"connect-src 'self'",
|
|
"frame-ancestors 'none'",
|
|
"base-uri 'self'",
|
|
"form-action 'self'",
|
|
"upgrade-insecure-requests",
|
|
].join('; ')
|
|
reply.header('Content-Security-Policy', csp)
|
|
|
|
// Referrer Policy - control referrer information leakage
|
|
reply.header('Referrer-Policy', 'strict-origin-when-cross-origin')
|
|
|
|
// Permissions Policy (formerly Feature Policy) - disable unnecessary features
|
|
reply.header('Permissions-Policy', [
|
|
'geolocation=()',
|
|
'microphone=()',
|
|
'camera=()',
|
|
'payment=()',
|
|
'usb=()',
|
|
'magnetometer=()',
|
|
'gyroscope=()',
|
|
'accelerometer=()',
|
|
].join(', '))
|
|
|
|
// X-Permitted-Cross-Domain-Policies - restrict cross-domain policies
|
|
reply.header('X-Permitted-Cross-Domain-Policies', 'none')
|
|
|
|
// Expect-CT - Certificate Transparency (deprecated but still used)
|
|
// Note: This header is deprecated but may still be required for some compliance
|
|
// reply.header('Expect-CT', 'max-age=86400, enforce')
|
|
|
|
// Cross-Origin-Embedder-Policy - prevent cross-origin data leakage
|
|
reply.header('Cross-Origin-Embedder-Policy', 'require-corp')
|
|
|
|
// Cross-Origin-Opener-Policy - isolate browsing context
|
|
reply.header('Cross-Origin-Opener-Policy', 'same-origin')
|
|
|
|
// Cross-Origin-Resource-Policy - control resource loading
|
|
reply.header('Cross-Origin-Resource-Policy', 'same-origin')
|
|
|
|
// Remove server information disclosure
|
|
// Note: Fastify doesn't expose server header by default, but we ensure it's not set
|
|
reply.removeHeader('Server')
|
|
reply.removeHeader('X-Powered-By')
|
|
}
|
|
|
|
/**
|
|
* Input sanitization helper
|
|
*/
|
|
export function sanitizeInput(input: unknown): unknown {
|
|
if (typeof input === 'string') {
|
|
// Remove potentially dangerous characters
|
|
return input
|
|
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
|
|
.replace(/javascript:/gi, '')
|
|
.replace(/on\w+\s*=/gi, '')
|
|
.trim()
|
|
}
|
|
|
|
if (Array.isArray(input)) {
|
|
return input.map(sanitizeInput)
|
|
}
|
|
|
|
if (input && typeof input === 'object' && !Array.isArray(input)) {
|
|
const sanitized: Record<string, unknown> = {}
|
|
for (const key in input) {
|
|
if (Object.prototype.hasOwnProperty.call(input, key)) {
|
|
sanitized[key] = sanitizeInput((input as Record<string, unknown>)[key])
|
|
}
|
|
}
|
|
return sanitized
|
|
}
|
|
|
|
return input
|
|
}
|
|
|
|
/**
|
|
* Validate and sanitize request body
|
|
*/
|
|
export async function sanitizeBodyMiddleware(
|
|
request: FastifyRequest,
|
|
reply: FastifyReply
|
|
): Promise<void> {
|
|
if (request.body) {
|
|
request.body = sanitizeInput(request.body)
|
|
}
|
|
}
|
|
|