Skip to content

Architecture Overview

┌─────────────────────────────────────────────┐
│ 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 │
└─────────────────────────────────────────────┘

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 HandlerRegistry holding all handlers
  • Each middleware instance owns its own registry
  • Tests create fresh instances per test case
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.

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.

1. Adapter receives native request
2. Wraps it in GuardRequest
3. BypassHandler.handlePassthrough() → check excludePaths
4. RouteConfigResolver.getRouteConfig() → resolve per-route config
5. BypassHandler.handleSecurityBypass() → check bypassed checks
6. SecurityCheckPipeline.execute() → run 17 checks
7. If blocked: send GuardResponse via adapter
8. If passed: call next() / route handler
9. Capture response for behavioral processing + security headers

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,
) {}
}

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.