Security
Secure by default where we control the surface, explicit about the parts you must add, and honest about where the project stands.
The security posture
The principle throughout: fail closed. An unconfigured deployment rejects requests rather than accepting them, binds to loopback rather than the world, and refuses rather than assumes.
Authentication
Every API call carries Authorization: Bearer <token>. Who the caller is comes from the token, never from a field in the request body. Three modes, selected with OMS_AUTH_MODE:
| Mode | What it is | Use it for |
|---|---|---|
api-key | Static keys mapped to users and roles. The default. With no keys configured, every request is rejected: an unconfigured OMS is closed, not open. | Server-to-server integrations |
jwt | HS256-signed tokens from your identity provider. | Plugging in your own user system |
dev | Accepts everything. You have to opt into it by name. | Local development, never production |
Browser WebSocket clients pass the token as the second Sec-WebSocket-Protocol offer; tokens in query strings are rejected (they end up in logs). The auth layer is a small SPI, so a custom provider is a single class, not a fork.
// Browser WebSocket auth: token as the second subprotocol, never in the URL
new WebSocket('wss://oms.example.com/ws', ['bearer', token])Hardened edges
- Money and ids are validated at the door: malformed decimal strings, out-of-range values, and oversized fields get a
400, not a stack trace. - CORS is an explicit allowlist; the default allows no browser origins.
- Secrets can be read from files (
*_FILEvariants), keeping them out of environment listings. - Every authenticated mutation lands in an append-only audit log.
- Foreign order ids return
404, not403, so ids can't be probed for existence. - CI runs dependency and secret scanning (Trivy, plus govulncheck on the Go code) on every commit, in every repo.
The admin plane
The admin gateway binds to 127.0.0.1 by default and refuses to bind wider unless a bearer token is configured. Process execution is shell-free (no command-string injection surface). Treat :8082 as root on the exchange: if you expose it beyond the box, put your own network controls in front as well.
What you still own
Open Exchange terminates none of your perimeter. TLS, DDoS protection, rate limiting at the edge, IP allowlists, and network segmentation are yours, as are KYC, custody, and licensing (see scope). A reasonable minimum for anything internet-facing: a TLS-terminating reverse proxy in front of the OMS, the admin gateway reachable only over a private network or tunnel, and the cluster ports never exposed at all.
Reporting a vulnerability
Privately, please: email info@openexchange.dev with the repo, impact, and reproduction steps, or use GitHub's private reporting if enabled on the repo. Don't open a public issue. The full policy, including scope and what we prioritize, is in each repo's SECURITY.md.