Security

The Cordy Gateway trust model — self-hosting, key hashing, credential encryption, per-key limits, audit logging, and an honest statement on compliance.

This page states plainly what Cordy Gateway protects, how, and what it does not claim. It is a self-hosted, single-tenant product: the security posture is largely determined by how you deploy and operate it.

Trust model

Cordy Gateway runs entirely on your infrastructure:

  • Your keys. Upstream provider credentials live in your database, encrypted at rest. You issue and revoke the wnx_ keys your clients use.
  • Your data store. Prompts pass through the gateway to the upstream provider you configured; they are not sent to a Cordy-operated service. The selected upstream receives them under its own data terms. Configuration and usage records live in your own PostgreSQL.
  • No Cordy control service in the request path. There is no phone-home to Cordy for inference. Licensing is verified offline (see below).

Because it is self-hosted, the boundary of trust is your own network, host, database, and secrets management.

No payload capture by default

By default, the gateway does not capture request or response payloads (your prompts and completions). Usage records store metadata only — model, token counts, cost, status, and timing — which is what powers metering and analytics. Content-capturing evaluation features are opt-in and off by default. If you later enable any content-capturing feature, that is your explicit choice and your data stays in your store.

API keys

  • Client keys are wnx_-prefixed and generated with a cryptographically secure random component.
  • The gateway stores only the SHA-256 hash of each key. The full plaintext is shown once at creation and cannot be recovered afterward — copy it then.
  • Keys authenticate via the Authorization: Bearer header or X-API-Key. Keys in the URL query string are not accepted, so they never leak into access logs, proxies, or browser history.
  • Keys have a lifecycle status (active, inactive, revoked, expired). Revocation takes effect on the next request, and the error code distinguishes revoked from inactive from expired so clients react correctly.

Per-key controls

Each key can be constrained independently:

ControlEffect
ScopesLimit the key to specific endpoint families (chat, completions, embeddings, models, balance, admin). Issue least-privilege keys.
Token quotaCap monthly token usage; over-quota requests get 429 quota_exceeded.
Rate limitCap requests per minute per key.
In-flight limitCap concurrent requests per key (GATEWAY_MAX_INFLIGHT_PER_KEY, default 50).
Model permissionsRestrict a key's owner to a subset of public models.
ExpirySet an expiry date after which the key stops working.

Credential encryption at rest

Upstream provider credentials (channel API keys, and the AWS secret for Bedrock) are stored Fernet-encrypted in the database, never in plaintext. They are decrypted only in memory at request-dispatch time.

  • Encryption keys come from CHANNEL_ENCRYPTION_KEYS, which is mandatory in production — the Admin and Gateway both refuse to start without it.
  • The variable accepts a comma-separated list: the first key encrypts new values; the rest allow key rotation without re-encrypting everything at once.
  • Store the key(s) outside version control, in a secrets manager. Clients never see upstream credentials — only their own wnx_ gateway keys.

Audit logging and RBAC

  • The control plane (Admin) keeps an append-only audit log of administrative actions, and enforces role-based access control for operators.
  • The audit worker can optionally ship audit evidence to object storage with server-side retention/immutability controls when you configure it.
  • Client-facing logs record request metadata (not payloads); the gateway deliberately logs request paths without query strings to avoid leaking secrets.

The /my/ portal uses the same ACTIVE user account but does not grant Admin access to MEMBER roles. Login is locked for 15 minutes after 10 failures per email or client IP, Admin sessions expire after eight hours, logout requires POST, and a newly generated API-key plaintext is shown once. Prepaid top-ups are operator-only actions and create append-only credit-ledger entries.

Network and transport

Harden the deployment boundary:

  • Apply the production override. The upstream docker-compose.prod.yml does not pass ENVIRONMENT or the controls below into gateway; .env alone only supplies interpolation. Use the documented override on every create, recreate, and scale command. Without it, the process defaults to development.
  • Terminate TLS at nginx or an external proxy; keep DJANGO_SECURE_SSL_REDIRECT=True.
  • Restrict CORS. With ENVIRONMENT=production, an empty CLIENT_CORS_ORIGINS denies cross-origin browser access and a literal * is refused at boot. In development mode, an empty list becomes *. List explicit origins only if browsers call the gateway directly.
  • Trust proxies explicitly. Set TRUSTED_PROXY_CIDRS so forwarded client IPs are only honoured from your real proxies, preventing audit-IP spoofing.
  • Gate metrics. Behind public nginx, require GATEWAY_METRICS_TOKEN and leave GATEWAY_METRICS_ALLOWED_CIDRS empty. The CIDR path checks the immediate socket peer before token validation, so allowlisting nginx or its network would admit every public request it proxies. Use CIDRs only for a direct, isolated trusted scrape path. With neither control, production denies the endpoint and development opens it.
  • Keep credit evidence durable. Retain the gateway_wal volume and verify /var/lib/cordy is writable. Redis consume-buffer failures otherwise risk a counted under-charge; the repository has no automated replay command.
  • Keep internal services internal. Postgres, PgBouncer, and Redis are not published to the host in the production compose — keep it that way.

Licensing

Cordy Gateway uses an offline, HMAC-signed license. There is no network license check. Enforcement defaults to audit_only (fail-open — it logs but never blocks); strict mode is available if you want it to fail closed. Licensing does not transmit any usage data off the host.

Compliance

Cordy Gateway does not claim any compliance certifications. It is not certified for SOC 2, ISO 27001, HIPAA, FedRAMP, PCI DSS, or any similar standard, and this documentation makes no such claim.

What it does provide are honest, verifiable properties you can build on:

  • Self-hosted and single-tenant — your data stays on your infrastructure.
  • No payload capture by default.
  • Credentials encrypted at rest; API keys stored only as hashes.
  • Per-key scopes, quotas, and rate limits.
  • An append-only administrative audit log with RBAC.

Whether a given deployment meets a specific regulatory requirement depends entirely on how you operate the host, network, database, and secrets — that responsibility is yours.

Hardening checklist

  • CHANNEL_ENCRYPTION_KEYS set identically on Admin and Gateway, stored in a secrets manager.
  • docker-compose.production-overrides.yml applied to every production Compose command; effective ENVIRONMENT=production verified.
  • Strong, unique DJANGO_SECRET_KEY and DB_PASSWORD.
  • TLS terminated; DJANGO_SECURE_SSL_REDIRECT=True; a non-obvious DJANGO_ADMIN_URL.
  • CLIENT_CORS_ORIGINS explicit (never * in production).
  • TRUSTED_PROXY_CIDRS set to your real proxies.
  • /metrics/prometheus gated by bearer token behind public nginx; CIDR bypass empty unless the scrape path is direct and isolated.
  • gateway_wal retained, /var/lib/cordy writable, and WAL/drop alerts covered by the billing response procedure.
  • API keys issued least-privilege (scopes, quotas, expiry).
  • Internal services (Postgres, Redis, PgBouncer) not published to the host.
  • Backups configured and tested; PITR enabled for production.

Next steps

  • Configuration — the security-relevant variables in full.
  • Deployment — applying these controls to a production topology.
  • Admin console — roles, scopes, credit, and platform limits.
  • Operations — monitoring, backups, upgrades, and incident checks.