Skip to content

Auth Internals

Snippbot uses API key authentication for all protected endpoints. Keys are stored as SHA-256 hashes and validated on every request via middleware.

There is no user login or session management in the self-hosted tier. Authentication is purely API key based:

  1. On first run (bootstrap mode): no key required — the setup wizard creates the first key
  2. After setup: all API requests must include a valid API key
  3. The UI stores its key in the browser’s localStorage and sends it with every request

Generated keys use the format:

snip_<random_hex>

Example: snip_f7a3c2d1e8b4a6f9c2d1e8b4a6f9c2d1e8b4a6f9c2d1e8b4a6f9c2d1

Keys are never stored in plaintext. The storage layer saves only:

  • SHA-256(key) — for validation
  • Key name and description
  • Creation and last-used timestamps
  • Permissions scope

Include the API key as a Bearer token in the Authorization header:

GET /api/agents HTTP/1.1
Authorization: Bearer snip_f7a3c2d1e8b4a6f9...

The middleware computes SHA-256(provided_key) and compares it to the stored hash using a constant-time comparison to prevent timing attacks.

Requests flow through this middleware chain:

Request
SecurityHeadersMiddleware — X-Content-Type-Options, X-Frame-Options, etc.
CORSMiddleware — Origin validation and preflight handling
AuthMiddleware — API key validation
Route handler

Adds security headers to all responses:

X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin

Default CORS policy:

  • Allowed origins: http://localhost:5173 (UI dev server) + the daemon’s own origin
  • Allowed methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
  • Allowed headers: Authorization, X-API-Key, Content-Type
  • Max age: 3600 seconds

Configure with SNIPPBOT_CORS_ORIGINS to add additional allowed origins.

Validates the API key on every protected request. Unauthenticated requests return:

HTTP/1.1 401 Unauthorized
{"error": "Missing Authorization header"}

Or if a key is supplied but invalid:

HTTP/1.1 401 Unauthorized
{"error": "Invalid or revoked API key"}

Some endpoints are accessible without authentication:

EndpointPurpose
GET /healthHealth check for load balancers
GET /health/liveLiveness probe (Kubernetes)
GET /health/readyReadiness probe (Kubernetes)
GET /api/API index / discovery
GET /api/metrics/prometheusPrometheus metrics scrape
POST /api/auth/loginWeb UI login
POST /api/auth/validateAPI key validation (pass key in header)
GET /api/auth/bootstrapBootstrap state check
POST /api/setup/*Setup wizard (before any key is created)
POST /api/auth/device/pairDevice pairing initiation

On first run, before any API key exists, the daemon enters bootstrap mode:

  • The setup wizard (snippbot setup or UI /setup page) is accessible without auth
  • All other endpoints return 401
  • Bootstrap mode auto-disables after the first API key is created

Bootstrap mode can be detected:

GET /api/health
{"status": "ok", "bootstrap_mode": true}

Via setup wizard (first key) or via the API:

Terminal window
POST /api/auth/keys
Authorization: Bearer existing_key
{"name": "CI/CD Pipeline", "description": "GitHub Actions integration"}

Response:

{
"id": "key_abc123",
"name": "CI/CD Pipeline",
"key": "snip_f7a3c2d1...", Only shown once!
"created_at": "2026-03-02T00:00:00Z"
}
Terminal window
POST /api/auth/keys/{id}/rotate
Authorization: Bearer existing_key

The old key is immediately invalidated. A new key is returned.

Terminal window
DELETE /api/auth/keys/{id}
Authorization: Bearer existing_key

Returns {"revoked": true}.

Device agents (remote machines) use a separate token-based auth system:

  1. Pairing: daemon issues a 6-digit one-time code (valid 5 min)
  2. Device sends code + device metadata → receives a device_token
  3. WebSocket connections: ws://daemon:18781/api/devices/ws?token=DEVICE_TOKEN
  4. Token validation: SHA-256 hash lookup on every WebSocket connect

Device tokens are stored separately in devices.db and are not valid for API endpoints.

Each platform adapter verifies inbound webhooks using platform-specific mechanisms:

PlatformVerification method
DiscordEd25519 signature (X-Signature-Ed25519)
SlackHMAC-SHA256 (X-Slack-Signature)
TelegramSecret token header (X-Telegram-Bot-Api-Secret-Token)
WhatsAppHMAC-SHA256 (X-Hub-Signature-256)
TeamsBot Framework OAuth2 JWT validation
Google ChatJWT signed by Google service account public key

These are separate from the API key system — channel webhook endpoints don’t require an API key.

  • Change the default API key after setup
  • Store keys in environment variables, not in code
  • Set SNIPPBOT_CORS_ORIGINS to your actual domain (remove localhost)
  • Enable TLS via reverse proxy — never run HTTP in production
  • Set SNIPPBOT_API_KEY_REQUIRED=true to disable anonymous access
  • Rotate keys on a regular schedule
  • Monitor key last-used timestamps for unused keys