Overview
All API inputs are validated using Zod schemas before processing. Error messages are sanitized before reaching the client to prevent information leakage. URLs supplied by users or services are validated against SSRF blocklists with DNS rebinding protection. Together, these layers ensure that malformed, malicious, or probing requests never reach business logic.Zod schema validation
Every API endpoint validates its request body through thevalidateBody() helper:
Parse JSON
Attempts
request.json(). If the body is not valid JSON, returns a 400 response with "error": "Invalid JSON body".Validate against schema
Runs
schema.safeParse(raw). If validation fails, returns a 400 response with an issues array containing the path and message for each violation.Validation error format
Schema constraints
Selected schemas and their key constraints:| Schema | Notable constraints |
|---|---|
createPasswordSchemaV3 | name max 200, url max 2000, ciphertext max 1 MB, requires wrapped_item_key, encrypted_data with v: 3 |
createShareSchema | accountId must be UUID, permission is read or write, expiresAt is ISO datetime |
notificationCreateSchema | title max 500, body max 5000, action_url must be valid URL, metadata is a record |
serviceRegistryOverrideSchema | domain validated as hostname, category is enum, selectors checked for script injection |
deviceRegistrationSchema | fingerprint required, deviceName max 100, deviceType max 50 |
bulkCreatePasswordsSchema | Array of 1-200 entries, each validated individually |
The
.strict() modifier is applied to schemas where extra fields should be rejected (e.g., password updates, passkey operations). This prevents unintended field injection.URL validation
The URL validator prevents server-side request forgery (SSRF) by blocking requests to internal infrastructure, cloud metadata endpoints, and private IP ranges.Basic validation
The synchronousvalidateNavigationUrl() runs these checks:
Protocol check
Only
http: and https: protocols are allowed. All others (file:, ftp:, data:, etc.) are blocked.Blocked hosts
| Host | Reason |
|---|---|
169.254.169.254 | AWS EC2 instance metadata |
100.100.100.200 | Alibaba Cloud metadata |
metadata.google.internal | GCP metadata |
127.0.0.1 / localhost | Loopback |
0.0.0.0 | Unspecified |
::1 / [::1] | IPv6 loopback |
Blocked IP ranges
| CIDR | Description |
|---|---|
10.0.0.0/8 | Private network (Class A) |
172.16.0.0/12 | Private network (Class B) |
192.168.0.0/16 | Private network (Class C) |
169.254.0.0/16 | Link-local |
127.0.0.0/8 | Loopback |
0.0.0.0/8 | Unspecified |
fc00::/7 | IPv6 unique local address |
fe80::/10 | IPv6 link-local |
DNS rebinding protection
The asyncvalidateNavigationUrlWithDns() extends basic validation with DNS resolution to catch hostnames that resolve to internal IPs:
Run basic checks
Executes all synchronous validation first. If the basic check fails, returns immediately.
Resolve A records
Calls
dns.resolve4(hostname) to get IPv4 addresses. If DNS resolution fails, the URL is rejected (fail-closed).Resolve AAAA records
Calls
dns.resolve6(hostname) to get IPv6 addresses. Missing AAAA records are acceptable.CSS selector validation
Service registry selectors are validated to block script injection:Error sanitization
Error messages returned to the client pass through a sanitization layer that blocks internal details.Internal patterns blocked
The sanitizer matches against 48 patterns covering:Database errors
- Duplicate key / constraint violations
- Missing tables, columns, relations
- Permission denied
- Syntax errors
- Deadlocks, timeouts
Network / infra errors
ECONNREFUSED,ENOTFOUND,ETIMEDOUT- Socket hang up, connection reset
- DNS resolution failures (
getaddrinfo) - SSL / certificate errors
Auth / system errors
- JWT errors
- Row-level security
- Role / database missing
- Out of memory
- Stack depth limit
- Too many connections
Safe messages
These messages pass through the sanitizer without modification:- Not found
- Unauthorized
- Forbidden
- Bad request
- Method not allowed
- Conflict
- Too many requests
- Service unavailable
Sanitization rules
| Condition | Result |
|---|---|
| Message matches an internal pattern | Replaced with generic fallback (“An error occurred”) |
| Message exceeds 200 characters | Treated as a stack trace, replaced with fallback |
| Message is a known safe message | Passed through unchanged |
| Message is short and contains no internal patterns | Passed through unchanged |
Database error sanitization
ThesanitizeDbError() function always returns a generic fallback message regardless of the input. Database errors never reach the client.
The unconditional fallback for database errors is intentional. Even seemingly benign Supabase/PostgreSQL error messages can reveal table names, constraint names, or query structure that aid attackers in fingerprinting the stack.