Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/dallay/corvus/llms.txt

Use this file to discover all available pages before exploring further.

Gateway Pairing Mechanism

The gateway requires pairing before accepting webhook requests. This prevents unauthorized access when the gateway is accidentally exposed.

How Pairing Works

  1. Startup — Gateway generates a 6-digit one-time pairing code
  2. Display — Code is printed to the terminal (only visible to operator)
  3. Exchange — Client sends code to POST /pair via X-Pairing-Code header
  4. Token Generation — Server responds with a bearer token (256-bit entropy)
  5. Persistence — Token hash is saved to config.toml for restarts

Configuration

[gateway]
require_pairing = true  # default: true
allow_public_bind = false  # default: false
paired_tokens = []  # auto-populated on first pair

Pairing Code Generation

The 6-digit code is generated using cryptographically secure randomness:
  • Source: UUID v4 (backed by OS CSPRNG)
    • Linux: /dev/urandom
    • Windows: BCryptGenRandom
    • macOS: SecRandomCopyBytes
  • Entropy: 256 bits from UUID
  • Range: 000000 to 999999 (1 million possibilities)
  • Bias Elimination: Rejection sampling for uniform distribution
  • One-Time: Consumed after first successful pair

Bearer Token Format

Generation:
zc_<64-char-hex>
  • Prefix: zc_ (Corvus token identifier)
  • Payload: 32 random bytes (256-bit entropy) hex-encoded
  • Total Length: 67 characters
  • Source: rand::rng() OS CSPRNG
Storage:
  • Tokens are hashed with SHA-256 before storage
  • Config file stores 64-character hex hashes, not plaintext
  • Prevents token exposure via config file access

Brute Force Protection

Attempt Limiting:
  • Maximum 5 failed pairing attempts
  • 5-minute lockout after max attempts
  • Lockout timer returns remaining seconds
# After 5 failed attempts:
curl -H "X-Pairing-Code: wrong" http://127.0.0.1:8080/pair
# {"error": "Too many failed attempts. Try again in 298s.", "retry_after": 298}
Rate Limiting:
  • Per-client rate limiting on /pair endpoint
  • Client identified by IP + headers
  • Prevents distributed brute force attacks
Constant-Time Comparison:
  • Token and code comparison uses subtle::ConstantTimeEq
  • Prevents timing attacks
  • No early return on mismatch

Bearer Token Authentication

All authenticated endpoints require a bearer token in the Authorization header.

Protected Endpoints

POST /webhook
curl -X POST http://127.0.0.1:8080/webhook \
  -H "Authorization: Bearer zc_<your-token>" \
  -H "Content-Type: application/json" \
  -d '{"message": "Hello, Corvus!"}'
Admin endpoints (if enabled):
  • GET /admin/config
  • GET /admin/identity
  • PATCH /admin/identity

Public Endpoints

GET /health — Always public, no auth required
curl http://127.0.0.1:8080/health
# {"status": "ok", "uptime_seconds": 123}
WhatsApp webhook verification — Meta webhook challenge
# Meta sends verification request
GET /whatsapp?hub.mode=subscribe&hub.verify_token=<your-token>&hub.challenge=<challenge>

Token Validation

When a request arrives:
  1. Extract token from Authorization: Bearer <token> header
  2. Hash token with SHA-256
  3. Compare hash against stored hashes (constant-time)
  4. Allow or deny request based on match

Token Lifecycle

Creation:
  • Generated on first successful pairing
  • Displayed once to client
  • Never shown again (hash stored)
Storage:
  • Saved to config.toml as SHA-256 hash
  • Survives gateway restarts
  • Multiple tokens supported
Revocation:
  • Edit config.toml and remove token hash
  • Restart gateway to apply changes
  • Future: corvus gateway unpair command

Public Bind Restrictions

The gateway refuses to bind to public addresses without explicit configuration or an active tunnel.

Default Behavior

# Safe: binds to localhost only
corvus gateway
# Listening on 127.0.0.1:8080
Blocked by default:
  • 0.0.0.0 (all interfaces)
  • Public IP addresses
  • Non-localhost hostnames
Allowed by default:
  • 127.0.0.1 (IPv4 localhost)
  • localhost (hostname)
  • ::1 (IPv6 localhost)
  • [::1] (IPv6 localhost with brackets)

Public Bind Detection

The gateway checks the bind address before starting:
fn is_public_bind(host: &str) -> bool {
    !matches!(
        host,
        "127.0.0.1" | "localhost" | "::1" | "[::1]" | "0:0:0:0:0:0:0:1"
    )
}
If a public bind is detected:
  1. Check if a tunnel is active
  2. Check if allow_public_bind = true
  3. Refuse to start if neither is true

Allowing Public Bind

Security Risk: Only enable public bind if you understand the implications. Use a tunnel instead.
[gateway]
allow_public_bind = true  # NOT RECOMMENDED
This disables the public bind safety check. Use only for:
  • Internal networks behind a firewall
  • Development environments with additional security layers
  • Situations where a tunnel is not feasible

Random Port Mode

For additional security, use a random ephemeral port:
corvus gateway --port 0
# Listening on 127.0.0.1:54321 (random port)
Benefits:
  • Port scanners won’t find a known port
  • Reduces attack surface
  • Suitable for tunnel-only access

Tunnel Requirements

For external access, Corvus requires a tunnel instead of public bind.

Supported Tunnels

[tunnel]
provider = "tailscale"  # "tailscale", "cloudflare", "ngrok", "custom"

Tailscale

# Install Tailscale
curl -fsSL https://tailscale.com/install.sh | sh

# Enable Tailscale Funnel (HTTPS tunnel)
tailscale funnel --bg 8080

# Start gateway
corvus gateway --port 8080
Configuration:
[tunnel]
provider = "tailscale"
Benefits:
  • Zero-config HTTPS
  • Tailnet authentication
  • Built-in access control

Cloudflare Tunnel

# Install cloudflared
wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared-linux-amd64.deb

# Create tunnel
cloudflared tunnel login
cloudflared tunnel create corvus
cloudflared tunnel route dns corvus corvus.example.com

# Start tunnel
cloudflared tunnel --url http://127.0.0.1:8080 run corvus

# Start gateway
corvus gateway --port 8080
Configuration:
[tunnel]
provider = "cloudflare"

ngrok

# Install ngrok
snap install ngrok

# Start tunnel
ngrok http 8080

# Start gateway
corvus gateway --port 8080
Configuration:
[tunnel]
provider = "ngrok"

Custom Tunnel

[tunnel]
provider = "custom"
command = "my-tunnel-binary --port 8080"
Corvus will:
  1. Check if tunnel process is running
  2. Allow public bind if tunnel is active
  3. Refuse if tunnel is not running

Security Checklist

Before exposing your gateway:
  • Pairing enabledrequire_pairing = true
  • Tunnel configured — Use Tailscale, Cloudflare, or ngrok
  • Localhost bind — Avoid allow_public_bind = true
  • Strong tokens — Never manually create weak tokens
  • Channel allowlists — Configure sender allowlists
  • HTTPS only — Tunnels provide TLS encryption
  • Rate limiting — Default rate limits enabled
  • Audit logs — Monitor tracing output

Gateway API Reference

EndpointMethodAuthDescription
/healthGETNoneHealth check
/pairPOSTX-Pairing-CodeExchange code for token
/webhookPOSTBearer <token>Send message to agent
/whatsappGETQuery paramsWhatsApp webhook verification
/whatsappPOSTMeta signatureWhatsApp incoming message

Example: Pairing Flow

# 1. Start gateway
corvus gateway
# 🔐 Gateway pairing code: 123456
# Listening on 127.0.0.1:8080

# 2. Exchange code for token
curl -X POST http://127.0.0.1:8080/pair \
  -H "X-Pairing-Code: 123456"
# {
#   "paired": true,
#   "persisted": true,
#   "token": "zc_a1b2c3d4...",
#   "message": "Save this token - use it as Authorization: Bearer <token>"
# }

# 3. Use token for webhook requests
curl -X POST http://127.0.0.1:8080/webhook \
  -H "Authorization: Bearer zc_a1b2c3d4..." \
  -H "Content-Type: application/json" \
  -d '{"message": "Hello!"}'

Next Steps

Security Overview

Security architecture and principles

Deployment

Production deployment guide

Build docs developers (and LLMs) love