Architecture Overview
Layer Diagram
Section titled “Layer Diagram”┌─────────────────────────────────────────────┐│ Framework Adapter ││ (Express / Fastify / NestJS / Hono) ││ Implements GuardRequest, GuardResponse │├─────────────────────────────────────────────┤│ initializeSecurityMiddleware() ││ Creates all components, returns registry │├─────────────────────────────────────────────┤│ SecurityCheckPipeline ││ 17 checks in Chain of Responsibility │├─────────────────────────────────────────────┤│ Core Modules ││ Events │ Routing │ Validation │ Bypass ││ Behavioral │ Responses │ Initialization │├─────────────────────────────────────────────┤│ HandlerRegistry ││ Redis │ IPBan │ RateLimit │ Cloud │ SusP ││ SecurityHeaders │ Behavior │ DynamicRules │├─────────────────────────────────────────────┤│ Protocols Layer ││ GuardRequest │ GuardResponse │ GeoIPHandler││ AgentHandler │ RedisHandler │ Middleware │└─────────────────────────────────────────────┘No Singletons
Section titled “No Singletons”The Python guard-core uses __new__ singletons for handlers. The TypeScript port eliminates all global state:
HandlerInitializer.initialize(config)creates fresh handler instances- Returns a
HandlerRegistryholding all handlers - Each middleware instance owns its own registry
- Tests create fresh instances per test case
HandlerRegistry Pattern
Section titled “HandlerRegistry Pattern”interface HandlerRegistry { redisHandler: RedisManager | null; ipBanHandler: IPBanManager; rateLimitHandler: RateLimitManager; cloudHandler: CloudHandler; susPatternsHandler: SusPatternsManager; securityHeadersHandler: SecurityHeadersManager; behaviorTracker: BehaviorTracker; dynamicRuleHandler: DynamicRuleManager; geoIpHandler: GeoIPHandler | null;}The registry is created by HandlerInitializer and flows through SecurityMiddlewareComponents to all checks and processors.
SecurityMiddlewareComponents
Section titled “SecurityMiddlewareComponents”The initializeSecurityMiddleware() function is the central entry point. It creates all components and returns:
interface SecurityMiddlewareComponents { registry: HandlerRegistry; pipeline: SecurityCheckPipeline; eventBus: SecurityEventBus; metricsCollector: MetricsCollector; validator: RequestValidator; routeResolver: RouteConfigResolver; bypassHandler: BypassHandler; errorResponseFactory: ErrorResponseFactory; behavioralProcessor: BehavioralProcessor; middlewareProtocol: GuardMiddlewareProtocol;}Adapters store this object and use it to process every request.
Request Flow
Section titled “Request Flow”1. Adapter receives native request2. Wraps it in GuardRequest3. BypassHandler.handlePassthrough() → check excludePaths4. RouteConfigResolver.getRouteConfig() → resolve per-route config5. BypassHandler.handleSecurityBypass() → check bypassed checks6. SecurityCheckPipeline.execute() → run 17 checks7. If blocked: send GuardResponse via adapter8. If passed: call next() / route handler9. Capture response for behavioral processing + security headersDependency Injection
Section titled “Dependency Injection”All core modules receive their dependencies through constructor parameters or method arguments. There is no service locator or container.
Checks receive the GuardMiddlewareProtocol:
abstract class SecurityCheck { constructor(protected readonly middleware: GuardMiddlewareProtocol) {}}Through the middleware protocol, checks access config, logger, handlers, and the response factory.
Core modules (routing, validation, bypass, etc.) receive specific dependencies:
class BypassHandler { constructor( config: ResolvedSecurityConfig, eventBus: SecurityEventBus, routeResolver: RouteConfigResolver, errorResponseFactory: ErrorResponseFactory, validator: RequestValidator, ) {}}Edge Safety
Section titled “Edge Safety”The protocol layer uses Uint8Array instead of Node.js Buffer for binary data:
interface GuardResponse { readonly body: Uint8Array | null;}
interface GuardRequest { body(): Promise<Uint8Array>;}This allows the core engine to run in Cloudflare Workers, Deno, and other edge runtimes that lack Node.js built-ins.
Redis and maxmind features gracefully degrade when unavailable — rate limiting falls back to in-memory, GeoIP returns null, and the engine continues operating.