Security
UserDispatch is hardened at every layer — widget, API, MCP server, CLI, and database. Enterprise-grade protection is included on every plan, not locked behind a paywall.
Authentication & sessions
Admin sign-in uses Google OAuth via NextAuth v5. Sessions are JWT-based with an 8-hour expiry and are revalidated against the database on every request.
CLI authentication issues ud_-prefixed tokens with a 90-day default expiry. Non-interactive environments (Claude Code, Codex, CI) use an RFC 8628-inspired device authorization flow with 8-character user codes (~39 bits of entropy), 20-minute expiry, and a 5-attempt lockout.
Submission API keys start with pk_ and are scoped per-app. They never reach the browser — the feedback form resolves keys server-side through a proxy endpoint.
Authorization
All MCP tools enforce org ownership and role checks. Destructive operations — delete app, remove member, rotate key — require the owner role. Non-owners can only update their own member record.
Sensitive fields (notification_email, enable_log_upload) are restricted to owners. Attachment access is scoped to the requesting admin's org through an authenticated proxy — raw blob URLs are never exposed.
Data protection
Every response includes a nonce-based Content Security Policy with strict-dynamic. HSTS is enabled with the preload directive, and a restrictive Permissions-Policy disables unnecessary browser APIs.
All API inputs are validated through Zod schemas. Detailed field errors are only returned in development — production responses use generic messages with no stack traces or internal paths.
Token security
API tokens (ud_) are SHA-256 hashed before storage. The raw token value is displayed exactly once at creation and cannot be retrieved afterward.
Token comparison uses a constant-time XOR-based algorithm padded to the maximum length, preventing timing side-channels. Tokens default to a 90-day expiry. Deleting an admin cascades to revoke all their tokens.
The CLI writes tokens to .env (auto-added to .gitignore) and never logs or transmits them beyond the initial auth exchange.
File upload protection
Uploaded files are validated by reading magic bytes — the actual file header — not just the MIME type or extension. A blocklist rejects text/html, image/svg+xml, and application/xhtml+xml to prevent stored XSS through disguised uploads.
Attachments are served through an authenticated proxy scoped to the requesting admin's org. Images use Content-Disposition: inline; all other file types force a download. Raw blob URLs are never exposed to the browser.
Injection prevention
SQL
Every database query uses bound $N parameters through tagged templates. Zero string interpolation in SQL throughout the codebase.
XSS
Email bodies and webhook content are HTML-escaped before storage or rendering. The widget uses dedicated escapeHtml and escapeAttr functions with null guards and single-quote coverage, rendered inside a Shadow DOM.
Prompt injection
MCP prompts wrap user-submitted content in <user_submission> XML tags, separating untrusted input from system instructions.
Command injection
The CLI uses spawn() with an args array for all child processes — no shell string interpolation.
Rate limiting
A PostgreSQL-backed fixed-window rate limiter uses atomic upserts, ensuring consistent enforcement across all serverless instances.
| Endpoint | Limit |
|---|---|
| Submissions (per IP per key) | 20/min |
| Submissions (global per key) | 120/min |
| Admin API (per org) | 200/min |
| Onboarding (per email) | 3/hr |
| CLI auth (per token) | 5/hr |
| Device auth create (per IP) | 10/hr |
| Device auth verify (per email) | 5/hr |
Webhook replay protection deduplicates events using svix-id (Resend) and MessageSid (Twilio) over a 24-hour window.
CSRF & origin validation
All admin POST/PATCH/DELETE routes require Content-Type: application/json or multipart/form-data. This prevents cross-site form submissions that rely on cookies.
Apps can configure allowed_origins to restrict which domains can submit feedback. When set, the Origin header must match. Dynamic CORS headers are returned with Vary: Origin.
Privacy practices
- ✓Tokens stored locally — CLI tokens written to
.env, auto-added to.gitignore, never committed - ✓Minimal file changes — the CLI modifies one layout file, MCP config files, and
.gitignore - ✓Fully auditable — the CLI source code is in the
cli/directory. See exactly what it does - ✓Error messages sanitized — production errors never leak stack traces, SQL, or internal paths
Reporting vulnerabilities
If you discover a security vulnerability, please email security@userdispatch.com. We take all reports seriously and will respond within 48 hours.