trappsec.dev GitHub

Supported Frameworks

All frameworks supported across Python, Node.js, and Go.

Language Framework Minimum version
Python
3.11+
Flask 3.0
FastAPI 0.115.3
Starlette 0.40
Litestar 2.13
Django 5.2 (LTS)
Sanic 24.6
Tornado 6.0
Node.js
22+
Express 4.17 (and 5.x)
NestJS 11
Fastify 5
Hapi 21
Koa 3.0 with @koa/router 13
Hono 4.2 (any runtime; the default client IP is read from @hono/node-server)
Go
1.26+
net/http standard library
Gin 1.12
Echo 5.2
chi, gorilla/mux and other http.Handler routers any; tested with chi 5.4 and gorilla/mux 1.8.1

Minimum versions are the oldest releases the integration tests pass on. CI checks them on every change: each Python framework on Python 3.11, every Node framework on Node 22. Go adapters require these versions in their go.mod, so Go’s minimum version selection enforces them; the handler adapter has no router dependency and is tested against current chi and gorilla/mux releases. Older releases may work but are not tested; Litestar before 2.13 and Sanic before 24.6 are known to miss honey fields.

Framework differences

The API is the same in every framework; these are the places where the framework shows through. “Request object” is what trappsec passes to identify_user, override_source_ip, callable defaults and dynamic bodies, and what you pass to trigger and is_trap.

Framework Request object Body parsing for body watches CSRF middleware and traps Known limits
Flask flask.request by trappsec Flask-WTF CSRFProtect: exempt automatically  
FastAPI / Starlette starlette.requests.Request by trappsec starlette-csrf: route traps around it with ts.is_trap (example)  
Litestar litestar.Request by trappsec CSRFConfig: exempt automatically bodies over 10 MiB pass uninspected; 2.13+
Django django.http.HttpRequest by trappsec CsrfViewMiddleware: exempt automatically WSGI and ASGI
Sanic sanic.Request by trappsec no built-in CSRF; skip third-party middleware with ts.is_trap 24.6+
Tornado tornado.httputil.HTTPServerRequest (self.request) by trappsec xsrf_cookies: exempt automatically  
Express req your parser (express.json(), multer, …), app- or route-level csurf / csrf-csrf: skip with ts.is_trap(req), registered at app level sub-routers (also under parameterised mounts) are covered; mounted sub-apps (app.use('/x', express())) are not, and a warning names them: install trappsec on the sub-app
Fastify request Fastify content-type parsers and @fastify/multipart @fastify/csrf-protection: skip with ts.is_trap(req) in an onRequest hook  
Koa ctx your parser (koa-bodyparser, …), app- or route-level koa-csrf: skip with ts.is_trap(ctx) register router.routes() directly: routers wrapped in koa-compose or koa-mount are not seen (a warning says so)
Hapi request Hapi crumb: skip with ts.is_trap(request)  
Hono c (the Hono Context) by trappsec (JSON, urlencoded, multipart; up to 10 MiB): c.req.json(), parseBody(), formData() and c.req.raw all see the stripped request hono/csrf: skip with app.use('*', (c, next) => ts.is_trap(c) ? next() : protect(c, next)) install Sentry before serve() / export default app: it hooks app.fetch. Middleware (app.use(), or app.all() with a (c, next) handler) runs before traps; app.all() with a (c) handler is a route. Outside @hono/node-server, set override_source_ip (e.g. with Hono’s getConnInfo), or every event records 0.0.0.0
NestJS the platform’s request (Express req or Fastify request) the platform; multipart via FileInterceptor / AnyFilesInterceptor as for the underlying platform a handler reading req.body through @Req() with no @Body() parameter gets the event, but the multipart honey field is not stripped
net/http *http.Request by trappsec (up to SetMaxBodyBytes, 1 MiB) gorilla/csrf, CrossOriginProtection: skip with app.IsTrap(r) serve through app; watches inside a mounted sub-mux are logged as unreachable
Gin *gin.Context by trappsec (up to SetMaxBodyBytes, 1 MiB) skip with app.IsTrap(c) in your CSRF middleware serve through app (app.Run*, or app.Handler(), which honours UseH2C)
chi, gorilla/mux, any http.Handler (handler package) *http.Request by trappsec (up to SetMaxBodyBytes, 1 MiB) wrap your CSRF middleware around the router, or skip with app.IsTrap(r) if it wraps the App serve through app; pass app.MatchRoutes(...) so real routes beat look-alike traps, duplicates fail at bootstrap, and watches follow the matched route (example); traps and watches run before the router’s middleware, so IdentifyUser sees headers and cookies but not values that middleware adds; traps are not router routes, so a trap path requested with a method the trap does not declare gets the router’s 404 where a real route would answer 405: declare every method a real route at that path would plausibly accept; with MatchRoutes, a trap does not answer its path with a trailing slash (chi and gorilla/mux answer those 404)
Echo *echo.Context by trappsec (up to SetMaxBodyBytes, 1 MiB) middleware.CSRF: Skipper: app.IsTrap serve through app (app.Start, echo.StartConfig{...}.Start(ctx, app), or app as the handler); requests rejected by earlier middleware are reported but not stripped; Echo v4 is not supported, and Echo before 5.2 has a route-protection bypass (GO-2026-6293)

In Go, every adapter’s App.IsTrap takes that framework’s request type, as in the table; app.Sentry.IsTrap(req any) accepts any of them. Every adapter has InstallSentry(router, service, environment) and Use(sentry, router) (to wire a Sentry you created with trappsec.NewSentry).

Missing your favorite framework? Raise a request here.