Middleware & Request Context
Kirak adds four middlewares automatically, in this order (outermost/first-to-run to innermost/last-to-run before your route):
- Body-size-limit middleware – rejects any request over 10 MB (
Content-Length) with a 413, before anything else runs. SecurityHeadersMiddleware– sets security response headers on every response.RequestContextMiddleware– tracks every request with a correlation ID, making distributed tracing and log correlation straightforward.MonitoringMiddleware– a no-op unless themonitoringmodule is enabled; otherwise records request timing.
RequestContextMiddleware is covered in detail below since it’s the one most application code interacts with directly.
Body-size-limit middleware
Section titled “Body-size-limit middleware”Unnamed internal middleware (_MAX_BODY_BYTES = 10 * 1024 * 1024 in kirak/kirak_instance.py). Always active, not configurable. Returns a JSON 413 error for any request whose Content-Length exceeds 10 MB, before the request reaches routing or parsing.
SecurityHeadersMiddleware
Section titled “SecurityHeadersMiddleware”File: kirak/middleware/security_headers.py
Sets these headers on every response, unconditionally:
| Header | Value |
|---|---|
X-Content-Type-Options |
nosniff |
X-Frame-Options |
DENY |
X-XSS-Protection |
1; mode=block |
Referrer-Policy |
strict-origin-when-cross-origin |
RequestContextMiddleware
Section titled “RequestContextMiddleware”File: kirak/middleware/request_context.py
Added automatically to every Kirak application on startup. No configuration required.
What It Does
Section titled “What It Does”- Extracts or generates a request ID – reads
X-Request-IDfrom the incoming request headers. If not present, generates a UUID4. - Stores the request ID in a
ContextVar– available throughout the entire async call stack (hooks, operations, background tasks spawned during the request). - Adds
X-Request-IDto the response headers – clients can use this for support and debugging. - Cleans up after the request – clears context variables to prevent cross-request leakage.
Headers
Section titled “Headers”| Header | Direction | Description |
|---|---|---|
X-Request-ID |
Request (optional) | Client-supplied correlation ID. Kirak uses it as-is if present. |
X-Request-ID |
Response | The correlation ID used for this request (client or auto-generated). |
Log Format
Section titled “Log Format”Every log line from the runtime includes the request ID and user ID:
2025-08-20 10:30:01 - [550e8400-e29b-41d4-a716-446655440000] - crud - INFO - [user:42] - [FETCH] Model: postsEvery module logs to the same shared kirak.log file, so this ID is enough to trace a single request across every module’s log lines with a simple grep.
WebSocket Passthrough
Section titled “WebSocket Passthrough”The middleware skips its own dispatch for WebSocket connections (scope["type"] == "websocket") and passes them directly to the next handler. Request ID tracking for WebSockets must be set up manually.
Accessing Request Context
Section titled “Accessing Request Context”Context variables are available anywhere in the async call stack – hooks, operations, custom routes, background tasks:
from kirak.middleware.request_context import ( get_request_id, get_user_id, set_user_id, get_current_user, set_current_user,)
# From a hook:@kirak.on("orders").hook("after_create")async def after_order_created(result): request_id = get_request_id() user_id = get_user_id() # Use for audit logging, tracing, etc. return result
# From a custom route:@app.get("/custom")async def custom_route(request: Request): request_id = get_request_id() # already set by middleware ...Available Functions
Section titled “Available Functions”| Function | Description |
|---|---|
get_request_id() |
Get the UUID string for the current request |
set_request_id(id) |
Set request ID (normally done by middleware) |
get_user_id() |
Get the authenticated user’s ID (set after auth dependency runs) |
set_user_id(id) |
Set user ID (set by auth dependency) |
get_current_user() |
Get the full authenticated user dict |
set_current_user(user) |
Set authenticated user dict (for WebSocket and background contexts) |
clear_request_context() |
Clear all context variables (called automatically after each request) |
RequestContextFilter (Logging)
Section titled “RequestContextFilter (Logging)”File: kirak/middleware/logging_filter.py
A Python logging filter that injects request_id and user_id into every log record. Applied automatically to every module’s logger, all of which share one RotatingFileHandler writing to kirak.log.
Custom loggers can use it too:
import loggingfrom kirak.middleware.logging_filter import RequestContextFilter
logger = logging.getLogger("my_custom_module")logger.addFilter(RequestContextFilter())Log records without an active request context get - as the placeholder values.
RedactionFilter
Section titled “RedactionFilter”File: kirak/middleware/logging_filter.py
A separate logging filter, also applied to every module’s logger, that masks emails, passwords, tokens, and API keys out of log messages before they reach any handler – including the shared kirak.log file and monitoring’s captured logs table.
Adding Custom Middleware
Section titled “Adding Custom Middleware”Pass additional ASGI middleware via create_kirak_app():
from starlette.middleware.gzip import GZipMiddlewarefrom starlette.middleware.trustedhost import TrustedHostMiddleware
app = create_kirak_app( models_path="./models/", middlewares=[ (GZipMiddleware, {"minimum_size": 1000}), (TrustedHostMiddleware, {"allowed_hosts": ["api.example.com", "*.example.com"]}), ],)Kirak’s four built-in middlewares are registered first, and your middlewares=[...] list is added afterward – since Starlette’s add_middleware() prepends (the most recently added middleware becomes outermost), everything you pass in middlewares ends up outermost, running before Kirak’s own middleware stack and your routes. Within your own list, middleware is applied in the order provided (the first tuple ends up outermost of your set).
Script / Background Mode
Section titled “Script / Background Mode”When code runs outside an HTTP request context (cron jobs, scheduler tasks, management scripts, seeding), there is no active request and no user, so Kirak operations run as guest. Set an identity first:
from kirak.core.context import set_user_context, reset_user_context
token = set_user_context({"role": "system", "user_id": None, "token": None})try: await kirak.create("audit_logs", {"data": {"action": "seed_complete"}})finally: reset_user_context(token)A context set this way is used as-is, without the HTTP auth dependency. The system role still goes through the model’s access block: the operation must list {"role": "system"} (here, audit_logs.access.create). Only field-level security is bypassed. The scheduler worker sets no identity for the jobs it runs – a job that calls Kirak sets one itself. See Who a direct call runs as.