Deploy your own encrypted relay. Own your infrastructure.
Self-hosted gateway for OAuth, device relay, session sharing, and webhook tunneling. Memory-only runtime — no Redis, no database, no external dependencies.
Agent <── E2EE ──> Gateway (you host this) <── E2EE ──> Browser / Desktop
| Zero dependencies | Single Go binary, in-memory state, no Redis or database required |
| Built-in console | Admin UI at /console — devices, sessions, shares, dashboard |
| Flexible auth | Demo mode for testing, Firebase popup sign-in, or any OIDC provider |
| E2E encrypted relay | X25519 + HKDF + AES-256-GCM, direction-split keys, replay protection |
| Webhook tunneling | Route external webhooks (Slack, GitHub, etc.) to local agents over WebSocket |
| One-click deploy | Railway template with auto-detected public URL |
- Go 1.25+
git clone https://github.com/Commands-com/gateway.git
cd gateway
cp .env.example .envGenerate a signing key and update .env:
openssl rand -base64 48JWT_SIGNING_KEY=<paste-generated-key>
AUTH_MODE=demoRun:
go run ./cmd/serverVerify:
curl http://localhost:8080/healthz
curl http://localhost:8080/readyzOpen the console at http://localhost:8080/console.
For use with the Commands.com Agent Workspace:
export JWT_SIGNING_KEY="$(openssl rand -hex 32)"
PORT=8091 AUTH_MODE=demo \
OAUTH_DEFAULT_CLIENT_ID=commands-desktop-public \
REDIRECT_ALLOWLIST='http://localhost:61696/callback,urn:ietf:wg:oauth:2.0:oob' \
go run ./cmd/server- Create a Railway project from this repo.
- Set environment variables:
| Variable | Value | Notes |
|---|---|---|
JWT_SIGNING_KEY |
openssl rand -base64 48 |
Required, >= 32 bytes |
AUTH_MODE |
demo or oidc |
See Auth Modes |
PUBLIC_BASE_URL is auto-detected from RAILWAY_PUBLIC_DOMAIN. Only set it for custom domains.
- Deploy.
- Visit
https://<your-domain>/console.
For Google/GitHub sign-in via Firebase, add these vars:
| Variable | Value |
|---|---|
AUTH_MODE |
oidc |
OIDC_ISSUER_URL |
https://securetoken.google.com/<project-id> |
OIDC_CLIENT_ID |
<project-id> |
FIREBASE_API_KEY |
Your Firebase web API key (public) |
FIREBASE_PROJECT_ID |
Your Firebase project ID |
Then add your Railway domain to Firebase Console > Authentication > Settings > Authorized domains.
No credentials file needed — OIDC validates tokens via Firebase's public JWKS.
| Mode | Use case | Required vars |
|---|---|---|
demo |
Local dev, testing | None beyond JWT_SIGNING_KEY |
firebase |
Firebase Admin SDK verification | FIREBASE_PROJECT_ID |
oidc |
Any OIDC provider (inc. Firebase) | OIDC_ISSUER_URL, OIDC_CLIENT_ID |
When FIREBASE_API_KEY and FIREBASE_PROJECT_ID are set, the authorize page shows
Google and GitHub popup sign-in buttons. Works with both firebase and oidc modes.
- Handshake: X25519 ephemeral key exchange with Ed25519 identity signatures
- Key derivation: HKDF-SHA256 with transcript hash salt, direction-split keys
- Encryption: AES-256-GCM with deterministic nonces and AAD binding
- Replay protection: Monotonic sequence enforcement, idempotency keys, transport tokens
- Frame validation:
REQUIRE_ENCRYPTED_FRAMES=trueby default
| Config | Default | Description |
|---|---|---|
REQUIRE_ENCRYPTED_FRAMES |
true |
Reject unencrypted session frames |
IDEMPOTENCY_TTL_SECONDS |
300 |
Dedup window for message delivery |
TRANSPORT_TOKEN_SECRET |
derived from JWT_SIGNING_KEY |
WebSocket transport auth |
TRANSPORT_TOKEN_TTL_SECONDS |
3600 |
Transport token lifetime |
The gateway serves an admin console at /console:
- Dashboard — connected devices, active sessions, health status
- Devices — online/offline status, identity keys, quick session launch
- Sessions — E2E encrypted chat with agents, handshake status
- Shares — invite management, grant lifecycle, revoke controls
Authenticates through the same OAuth flow as all other clients.
System
GET /healthz— livenessGET /readyz— readinessGET /console— admin UI
OAuth
GET /.well-known/openid-configurationGET /.well-known/jwks.jsonGET|POST /oauth/authorizePOST /oauth/tokenPOST /oauth/token/revokePOST /register— dynamic client registration
Gateway — Devices & Sessions
GET /gateway/v1/healthPUT /gateway/v1/devices/:device_id/identity-keyGET /gateway/v1/devices/:device_id/identity-keyPOST /gateway/v1/sessions/:session_id/handshake/client-initGET /gateway/v1/sessions/:session_id/handshake/:handshake_idPOST /gateway/v1/sessions/:session_id/handshake/agent-ackPOST /gateway/v1/sessions/:session_id/messagesGET /gateway/v1/sessions/:session_id/events(SSE)GET /gateway/v1/agent/connect(WebSocket)
Gateway — Shares
POST /gateway/v1/shares/invitesPOST /gateway/v1/shares/invites/acceptGET /gateway/v1/shares/devices/:device_id/grantsPOST /gateway/v1/shares/grants/:grant_id/revokePOST /gateway/v1/shares/grants/:grant_id/leave
Integrations & Webhook Tunnel
POST /gateway/v1/integrations/routesPUT /gateway/v1/integrations/routes/:route_idDELETE /gateway/v1/integrations/routes/:route_idGET /gateway/v1/integrations/routesPOST /gateway/v1/integrations/routes/:route_id/rotate-tokenGET /gateway/v1/integrations/tunnel/connect(WebSocket)ALL /integrations/:route_id/:route_token(public ingress)
Public ingress uses in-memory fixed-window rate limiting:
| Config | Default |
|---|---|
INGRESS_RATE_WINDOW_SECONDS |
60 |
INGRESS_GLOBAL_LIMIT_PER_WINDOW |
3000 |
INGRESS_IP_LIMIT_PER_WINDOW |
600 |
INGRESS_ROUTE_LIMIT_PER_WINDOW |
300 |
cmd/server Process entrypoint
internal/app Dependency wiring and route registration
internal/config Environment contract and validation
internal/console Built-in admin UI (embedded HTML)
internal/oauth OAuth/OIDC (authorize, token, views)
internal/gateway Device, share, session, tunnel handlers
internal/jwt Token signing and verification
internal/idtoken Upstream identity token verifiers
internal/health Health/readiness handlers
docs/openapi.yaml Endpoint contract
go test ./...Default ALLOW_ORIGINS=* is suitable for demo mode. For production, restrict to your app's origin:
ALLOW_ORIGINS=https://app.example.com- OpenAPI spec:
docs/openapi.yaml - Release checklist:
docs/release.md