Security
Diminuendo implements a defense-in-depth security architecture across authentication, authorization, transport, input validation, and error handling. No single layer is relied upon exclusively — each defense operates independently and provides protection even if adjacent layers are compromised or misconfigured.Authentication
Auth0 JWT Verification
In production, every WebSocket connection must authenticate by sending anauthenticate message with a JWT token. The gateway verifies tokens against Auth0’s JWKS endpoint:
jose library handles JWKS rotation automatically — when a key ID in the token doesn’t match the cached keyset, it fetches the latest keys from the endpoint.
JWT Verification Cache
Asymmetric JWT verification (RS256) is computationally expensive. To avoid paying this cost on every message from a previously authenticated connection, the gateway maintains an LRU cache of verified tokens:- Cache key: SHA-256 hash of the raw JWT string (using
Bun.CryptoHasher) - Cache size: 10,000 entries maximum
- TTL: Derived from the token’s
expclaim, capped at 5 minutes - Eviction: FIFO when at capacity; expired entries cleaned up every 60 seconds
Dev Mode Bypass
WhenDEV_MODE=true, authentication is bypassed entirely. All connections are automatically authenticated as developer@example.com with tenant ID dev. This is intended exclusively for local development.
Role-Based Access Control
Roles and Permissions
The RBAC system defines 5 roles with 12 granular permissions:| Role | session:create | session:read | session:write | session:delete | session:archive | session:steer | member:read | member:write | member:delete | billing:read | billing:write | tenant:admin |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| owner | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y |
| admin | Y | Y | Y | Y | Y | Y | Y | Y | - | Y | - | - |
| billing_admin | Y | Y | Y | - | Y | Y | - | - | - | Y | Y | - |
| member | Y | Y | Y | - | Y | Y | - | - | - | - | - | - |
| viewer | - | Y | - | - | - | - | - | - | - | - | - | - |
MembershipService
Role assignments are stored in per-tenant SQLite databases (tenants/{tenantId}/registry.db). The MembershipService provides CRUD operations on the tenant_members table:
Owner Bootstrap
The first user to authenticate against a tenant is automatically bootstrapped asowner. Subsequent users are assigned the member role by default:
Last Owner Protection
The system prevents removing or demoting the last owner of a tenant. Both theremoveMember and set_role operations check the owner count before proceeding:
Permission Enforcement
TherequirePermission function is the authorization checkpoint. It is called before every sensitive operation:
CSRF Protection
Cross-Site Request Forgery protection uses a three-layer defense. Each layer is checked in order; the request passes if any layer succeeds:1
Sec-Fetch-Site Header
If the browser sends
Sec-Fetch-Site: same-origin or Sec-Fetch-Site: none, the request is same-origin and passes immediately. This header cannot be spoofed by JavaScript running in the same browser — it is set by the browser itself.2
Origin Header
If the
Origin header is present, it is checked against the ALLOWED_ORIGINS configuration list. If absent (non-browser clients such as CLIs and SDKs do not send Origin), the request passes — non-browser clients are not CSRF vectors.3
Referer Header Fallback
If the
Origin check fails, the Referer header is parsed and its origin component is checked against the same allowlist. This handles edge cases where some browsers strip the Origin header on certain redirect chains.SSRF Guard
TheassertSafeUrl function validates that outbound HTTP requests do not target internal networks. It inspects the URL’s hostname and blocks:
Private IPv4 Ranges
0.0.0.0/8, 10.0.0.0/8, 127.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16IPv6 Addresses
Loopback (
::1), unique local (fc00::/7), link-local (fe80::/10), IPv4-mapped (::ffff:127.0.0.1)Cloud Metadata
169.254.169.254 (AWS/GCP), metadata.google.internal, metadata.googleObfuscation Techniques
Bare integer IPs (
http://2130706433), octal notation (0177.0.0.1), hex notation (0x7f.0.0.1)http: and https: only, blocking file:, ftp:, gopher:, and other protocol handlers.
IPv4-Mapped IPv6 Detection
A sophisticated attacker might attempt to bypass IPv4 range checks by using IPv4-mapped IPv6 addresses. The guard handles both dotted-quad form (::ffff:127.0.0.1) and hex form (::ffff:7f00:1):
Security Headers
Every HTTP response — including WebSocket upgrade responses, health checks, and 404s — includes a comprehensive set of security headers:| Header | Purpose |
|---|---|
Strict-Transport-Security | Forces HTTPS for 1 year, including subdomains |
Content-Security-Policy | Restricts resource loading to same-origin; allows WebSocket connections |
X-Frame-Options: DENY | Prevents clickjacking via iframes |
X-Content-Type-Options: nosniff | Prevents MIME type sniffing |
Referrer-Policy | Sends origin only on cross-origin requests |
Permissions-Policy | Denies access to camera, microphone, and geolocation APIs |
X-DNS-Prefetch-Control: off | Disables DNS prefetching to prevent information leakage |
X-XSS-Protection: 0 | Disables the legacy XSS Auditor (which has known bypass quirks that can introduce vulnerabilities) |
Error Sanitization
All error messages sent to clients pass throughsanitizeErrorMessage, which applies three transformations:
1
Strip Stack Traces
Removes lines matching the pattern
\n\s*at\s+... — standard V8 stack trace lines that expose internal file paths.2
Redact API Keys
Replaces matches for five secret patterns with
[REDACTED]:3
Truncate
Messages exceeding 500 characters are truncated with an ellipsis. This bounds the size of error responses and prevents verbose internal errors from leaking excessive detail.
Rate Limiting
Per-Connection Rate Limiter
Every WebSocket connection gets its own sliding-window rate limiter:- Limit: 60 messages per 10-second window
- Enforcement: Checked before message parsing. Exceeding the limit returns an
errorevent with codeRATE_LIMITED - Cleanup: The limiter is removed from the tracking map when the WebSocket closes
Authentication Rate Limiter
A separate, IP-based rate limiter protects the authentication endpoint:| Parameter | Value |
|---|---|
| Max attempts | 10 per IP |
| Window | 60 seconds |
| Lockout duration | 5 minutes |
| Max tracked IPs | 10,000 |
Input Validation
Schema Validation
Every incoming WebSocket message is validated against Effect Schema definitions before processing:ClientMessage schema is a union of 21 message types, each with its own field requirements. Messages that do not match any variant are rejected immediately.
Message Size Limit
Raw messages exceeding 1 MB are rejected before JSON parsing, preventing denial-of-service via oversized payloads:Session ID Path Traversal Prevention
Session IDs are used to construct file paths for per-session SQLite databases. TheresolveSessionDir function validates that the resolved path stays within the sessions base directory:
../../etc/passwd would resolve to a path outside sessionsBaseDir and be rejected.
Tenant ID Validation
Tenant IDs extracted from JWT claims are validated against a strict pattern before use:Transport Security
WebSocket Upgrade Validation
The WebSocket upgrade path (/ws) validates the Origin header before upgrading the connection. Non-browser clients (which don’t send Origin) are allowed through, but browser-based connections from unauthorized origins are rejected with a 403 response.
Connection Lifecycle
| Parameter | Value |
|---|---|
| Max payload length | 2 MB (maxPayloadLength) |
| Idle timeout | 120 seconds |
| Per-message deflate | Disabled (avoids CRIME-class compression attacks) |
| Heartbeat interval | 30 seconds |
WAL Mode SQLite
All SQLite databases are opened withPRAGMA journal_mode = WAL. WAL mode prevents database corruption from concurrent access (reader and writer workers accessing the same file) and provides crash recovery — incomplete transactions are rolled back automatically on the next open.
synchronous = NORMAL provides a balance between durability and performance: data is safe after a process crash but may be lost on an OS crash or power failure. For a gateway managing ephemeral agent sessions, this trade-off is appropriate.