Payload Security
This topic explains why Framework M encrypts request payloads at the application layer and how the different modes trade off security, performance, and developer experience.
Why Encrypt at the Application Layer?
HTTPS protects data from passive network eavesdropping, but the request body is still visible to many components inside the request path:
- Reverse proxies and load balancers that log bodies for debugging.
- Web Application Firewalls (WAFs) that inspect content.
- APM and observability tools that capture request traces.
- Ingress controllers and sidecars.
- Any compromised component between TLS termination and the application.
Application-Layer Payload Encryption (ALPE) ensures that only the client and the application can read the payload. Even if an intermediate component captures the request, it sees an opaque JWE envelope rather than cleartext passwords, emails, or PII.
The Four Modes
Framework M does not force a single policy. It offers a spectrum so the same codebase can run in local development, standard SaaS, and high-security production environments.
disabled
No encryption. The frontend sends Content-Type: application/json and the middleware passes requests through unchanged. This is the default for local development and CI because it keeps debugging simple and avoids extra key-fetching network calls.
selective
Encrypts a configured list of routes. By default this covers authentication endpoints where passwords and OTP codes are exchanged. It provides a pragmatic balance: sensitive authentication payloads are protected without encrypting every API call.
metadata
Encrypts fields flagged in DocType schemas. This is useful when only a subset of fields in a form are sensitive. For example, a patient record might encrypt ssn and email while leaving diagnosis_code in cleartext. The Desk form controller inspects the DocType schema and decides whether to encrypt the submission based on the flagged fields.
strict
Encrypts every mutating request (POST, PUT, PATCH). This is the highest-security mode, appropriate for fintech or zero-trust deployments where any write could contain sensitive data.
Key Exchange and Zero-Latency Design
Framework M avoids per-request handshakes, which would double latency. Instead:
- On app boot, the frontend fetches
/api/v1/security/configonce. - If encryption is enabled, it lazily fetches
/api/v1/security/jwksonce and caches the public encryption key. - Every subsequent eligible request uses
window.crypto.subtleto generate an ephemeral AES key, encrypt the payload, wrap the AES key with the cached RSA public key, and send a JWE envelope.
The extra network cost is zero after the initial key fetch, and the cryptographic overhead is typically under one millisecond on modern browsers.
Middleware Placement
PayloadDecryptionMiddleware runs at priority 15, after rate limiting and before CSRF, idempotency, and authentication. This placement is intentional:
- Rate limiting still sees request metadata (path, headers) and can drop abuse early.
- CSRF and idempotency run on the decrypted body, so they work with standard JSON.
AuthMiddlewarereceives a normalLoginRequestDTO, so authentication handlers do not need decryption logic.
The OpenAPI Constraint
Field-level flags use standard Pydantic json_schema_extra, but Litestar's OpenAPI generator validates every extra key against its internal Schema object. Unknown keys cause OpenAPI generation to fail. Therefore:
- DocType models can safely use
json_schema_extra={"encrypted": True, "pii": True}because their schemas are produced by Pydantic and served by the metadata router, not by Litestar's OpenAPI generator. - Litestar request/response DTOs must not carry these flags. Auth routes rely on
selectivemode route configuration instead.
This separation keeps the feature simple and avoids custom Pydantic subclasses while respecting Litestar's OpenAPI constraints.
When to Use Each Mode
- Local development / CI:
disabled - Standard SaaS with password login:
selective - Apps with scattered PII fields in Desk forms:
metadata - Fintech, healthcare, zero-trust:
strict