Skip to main content

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.

EndpointLimit
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.

Security — UserDispatch Docs