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: Bearerheader orX-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 errorcodedistinguishes revoked from inactive from expired so clients react correctly.
Per-key controls
Each key can be constrained independently:
| Control | Effect |
|---|---|
| Scopes | Limit the key to specific endpoint families (chat, completions, embeddings, models, balance, admin). Issue least-privilege keys. |
| Token quota | Cap monthly token usage; over-quota requests get 429 quota_exceeded. |
| Rate limit | Cap requests per minute per key. |
| In-flight limit | Cap concurrent requests per key (GATEWAY_MAX_INFLIGHT_PER_KEY, default 50). |
| Model permissions | Restrict a key's owner to a subset of public models. |
| Expiry | Set 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.ymldoes not passENVIRONMENTor the controls below intogateway;.envalone only supplies interpolation. Use the documented override on every create, recreate, and scale command. Without it, the process defaults todevelopment. - Terminate TLS at nginx or an external proxy; keep
DJANGO_SECURE_SSL_REDIRECT=True. - Restrict CORS. With
ENVIRONMENT=production, an emptyCLIENT_CORS_ORIGINSdenies 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_CIDRSso forwarded client IPs are only honoured from your real proxies, preventing audit-IP spoofing. - Gate metrics. Behind public nginx, require
GATEWAY_METRICS_TOKENand leaveGATEWAY_METRICS_ALLOWED_CIDRSempty. 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_walvolume and verify/var/lib/cordyis 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_KEYSset identically on Admin and Gateway, stored in a secrets manager. -
docker-compose.production-overrides.ymlapplied to every production Compose command; effectiveENVIRONMENT=productionverified. - Strong, unique
DJANGO_SECRET_KEYandDB_PASSWORD. - TLS terminated;
DJANGO_SECURE_SSL_REDIRECT=True; a non-obviousDJANGO_ADMIN_URL. -
CLIENT_CORS_ORIGINSexplicit (never*in production). -
TRUSTED_PROXY_CIDRSset to your real proxies. -
/metrics/prometheusgated by bearer token behind public nginx; CIDR bypass empty unless the scrape path is direct and isolated. -
gateway_walretained,/var/lib/cordywritable, 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.