Skip to content

Threat model

The security layers nestrs ships cover a specific slice. They catch the bug classes that ship most often in business apps — forgotten filters, leaked fields, missing auth — and they catch them structurally, not by review. They do not cover everything a real production deployment must think about. This page lists what each layer covers, what it does not, and where the responsibility falls back on you.

LayerCatchesDoes not catch
AuthnGuard + JwtStrategyMissing, malformed, expired, unsigned-incorrectly tokensStolen-but-unexpired tokens, replay before expiry, key compromise
OAuth2ClientCSRF (state cookie), code-interception (PKCE), SSRF on token exchange (no redirects)Phishing the provider, malicious provider, callback URL takeover
hash_password + burn_verifyTiming-based account enumeration via loginSlow GPU farms cracking dumped hashes, weak passwords, password reuse
AbilityGuardMissing or malformed Ability (500), unauthenticated GraphQL/WS (403)Logic bugs in your AbilityFactory itself
Repo::scopedScope leakage on every read — handler cannot forget the filterCustom queries that ignore Scope<E, A>
Bind<S, A>Cross-tenant by-id reads (403) and missing rows (404)Handlers that fetch by id outside Bind and skip access
Authorize shaperLeak of unexposed columns, field-restricted grantsA handler returning a hand-built serde_json::Value shape that does not match Model (500)
ThrottlerGuardBrute-force login, naïve enumeration scrapesDDoS at network or HTTP layer (use a CDN / edge throttler)

These are out of scope on purpose. They matter, they belong in your deployment, and they are not part of the framework’s surface.

A stolen JWT remains valid until exp. The framework’s mitigation is short access tokens (default 1 hour, configurable via JwtOptions::expires_in) paired with a refresh-token endpoint your app exposes. Verification is stateless by design — no shared revocation store on the hot path, so a resource server needs nothing but the public key. Server-side revocation is a revoked_tokens table your Strategy consults on verify, read through the same Repo as everything else.

The framework’s posture is bearer tokens, which are immune to CSRF by construction (the browser does not auto-attach an Authorization header). Cookie-based sessions are not supported as a first-party credential. If you bridge to one (an SSR layer that forwards cookies), add CSRF protection at the bridge — it is not the framework’s job.

EdDSA splits the issuer’s private key from every verifier’s public key — see Issuer and resource server. Rotation, HSM integration, and secret-store wiring are not the framework’s job. Treat NESTRS_AUTHN__PRIVATE_KEY as you would treat any production secret: load it from a real secret store, never commit it, rotate on a schedule.

TLS terminates either at your edge — the common production shape: CDN, ingress, load balancer — or at the app itself, via HttpConfig. The transport serves whichever you configure and does not assume an edge it cannot see, so pick one and pin it in your deployment checklist.

The throttler is a per-handler guard, not a network defense. A real deployment fronts the app with a CDN, WAF, or rate-limiter that sheds load before the request reaches the process.

The framework wraps jsonwebtoken, argon2, and oauth2. The cryptographic correctness comes from those crates. The framework’s job is to wire them correctly — call hash_password instead of storing plaintext, verify_password instead of comparing strings, JwtService::verify instead of decoding manually.

For a production app:

  1. Bearer tokens with short TTL + refresh.
  2. EdDSA split between issuer and resource servers.
  3. Authz guards bound on every controller (negative-path tests catch the missing one).
  4. Exposure is opt-in — no #[expose] on server-only columns. A column crosses the wire only when its field carries #[expose], so a forgotten or newly-migrated column is hidden, not leaked. The masker is the second line for columns you do expose but restrict, covering them only when the field rule is .fields([...]).
  5. CDN / WAF in front for DDoS and bot defense.
  6. Real secret store for JWT_PRIVATE_KEY, OAuth secrets, DB credentials.
  7. TLS terminated at the edge or at the app (HttpConfig).
  8. Logging at the right level. nest_rs::authn and nest_rs::orm emit warn+ on denials and security events — pipe them to your SIEM, do not silence them.

Built by YV17labs