DocsGuidesSecurity

Security

A short reference covering the security knobs you need to know about for a TopGun deployment: TLS termination, secret-at-rest encryption on the client, and credential hygiene. The server also enforces role-based access control — see the RBAC guide for policy syntax and the roadmap for what is still planned.

TLS / wss:// — terminate at the reverse proxy

The TopGun Rust server speaks plain WebSocket (ws://) on port 8080 by default. Do not expose this port to the internet. Terminate TLS at your edge — nginx, Caddy, Cloudflare, an AWS ALB, Fly.io’s built-in TLS, etc. — and forward to the server over loopback or a private network.

A minimal nginx snippet:

server {
  listen 443 ssl http2;
  server_name api.example.com;

  ssl_certificate     /etc/letsencrypt/live/api.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;

  location /ws {
    proxy_pass http://127.0.0.1:8080;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
    proxy_set_header Host $host;
    proxy_read_timeout 86400s;
  }
}

Then connect clients to wss://api.example.com/ws.

The cluster transport is plain TCP today (cluster mTLS is on the roadmap); restrict the cluster network with a VPC / private subnet until that lands.

Authentication — JWT_SECRET must be set

The server refuses to start when JWT_SECRET is unset and TOPGUN_NO_AUTH is not 1. For production:

export JWT_SECRET="$(openssl rand -base64 32)"
pnpm start:server

For dev / demo with no auth:

export TOPGUN_NO_AUTH=1
pnpm start:server

Token-validation details — Clerk, Better Auth, Firebase, custom JWTs — live in the Authentication guide.

Encrypting data at rest on the client

By default IDBAdapter stores records in IndexedDB unencrypted. For laptops where the OS does not encrypt the disk (or for shared devices), wrap your adapter in EncryptedStorageAdapter:

import { TopGunClient } from '@topgunbuild/client';
import { IDBAdapter } from '@topgunbuild/adapters';
import { EncryptedStorageAdapter } from '@topgunbuild/client';

const key = await crypto.subtle.importKey(
  'raw',
  await deriveKeyMaterial(userPassword),
  { name: 'AES-GCM' },
  false,
  ['encrypt', 'decrypt'],
);

const client = new TopGunClient({
  serverUrl: 'wss://api.example.com/ws',
  storage: new EncryptedStorageAdapter(new IDBAdapter(), key),
});

Key management is the integrator’s responsibility — derive from a password, fetch from a vault, or pin to an OS keychain depending on your threat model.

Credential hygiene

  • Never commit .env files. The repo’s .gitignore excludes .env, .env.local, and .env.*.local. Use .env.example placeholders.
  • Rotate JWT_SECRET if it ever leaks. Active sessions become invalid; clients reconnect and re-authenticate.
  • Cluster admin credentials (TOPGUN_ADMIN_USERNAME, TOPGUN_ADMIN_PASSWORD) ship with changeme-style defaults in .env.example; override them before any non-localhost deployment.
  • Report suspected vulnerabilities via the disclosure channel in SECURITY.md.

Authorization (RBAC)

Per-map, per-operation RBAC is enforced server-side today — not planned. A Tower middleware layer checks every client read, write, and remove against the policy store before it reaches the domain services, using allow/deny rules with deny-wins semantics and optional predicate conditions. You do not need to re-implement authorization in your application code; server-side enforcement is the authoritative gate. App-layer claim checks are fine as optional defense-in-depth, but they are not a substitute and should not replace it.

See the RBAC guide for the policy model, the admin API used to define policies, role conditions, and the current persistence caveat (policies are in-memory and must be re-applied after a restart).

Planned

Cluster mTLS, mutual auth for the cluster transport, durable (restart-surviving) policy storage, and field-level redaction are on the roadmap. Until they ship, rely on edge TLS for transport security and re-apply RBAC policies after a server restart.