Security model
AXON layers defences so that no single failure is catastrophic. From the edge inward:
1 — Edge
- Cloudflare fronts the domain (DNS, CDN, WAF, TLS).
- Caddy terminates TLS inside the host and reverse‑proxies to the gateway. Backend services publish no ports — they're reachable only on the private Docker network.
2 — Gateway authentication
- A global JWT guard verifies an HS256 bearer token (
verifyBearer, timing‑safe, HS256‑only) on every request. - A small public‑route allowlist (anchored‑prefix matched, so no substring bypass) exempts only what must be unauthenticated:
/auth/nonce,/auth/login,/auth/refresh,/kyc/webhook,/admin/login,/health*. - Rate limiting is applied per client IP by path class (auth ~20/min, AI ~40/min, default ~300/min) to blunt brute‑force and abuse.
- Sensitive internal headers (
x-axon-service-token,x-axon-admin-user) are stripped on ingress so a client can't forge them.
3 — Service authorisation & data scoping
- Defence in depth: each service re‑verifies the JWT in its
@CurrentUser()decorator — it never trusts the gateway blindly. - Profile scoping: business‑scoped endpoints require an
X-AXON-Profile-Idheader, and the service checks that the token's user actually owns that profile (mismatch →403). This is why a personal account can't read another business's data. - No commingling: personal and business wallets are distinct addresses (different HD indexes); operations are stamped with the acting profile so the two contexts never mix on‑chain or in the ledger.
- Session hardening: access tokens are short‑lived (~2h); refresh tokens are single‑use and rotated, with reuse detection that revokes the whole token family if an old token is replayed.
4 — On-chain custody
- Users sign their own transactions with their Privy wallet — AXON never holds user keys for custody actions.
- The backend uses prepare → confirm: it prepares the call, the user signs, and the service verifies the resulting transaction on‑chain before mutating state. The chain is the source of truth.
- The escrow contract enforces its own access control (initiator/counterparty modifiers, acceptance gating, the 5‑day permissionless‑release timeout, and the fee snapshot).
5 — Integrity & audit
- Webhooks (e.g. Sumsub, MoonPay) are authenticated by HMAC signature over the raw body, and de‑duplicated via
WebhookEventso replays are harmless. - Money movements are written to the append‑only, hash‑chained settlement ledger, whose head is periodically anchored to Base — tampering is detectable and provable.
- Admin and auth actions are written to an append‑only
AuditLog(e.g.user.signin,auth.refresh_reuse_detected,settlement.recorded, policy changes).
Secrets
- Configuration is loaded through a fail‑closed
getSecret()— in production a missing required secret stops the service rather than starting in an insecure default. NEXT_PUBLIC_*values are baked into the web bundle at build time (notablyNEXT_PUBLIC_PRIVY_APP_ID, which the auth/wagmi provider stack requires).
Remediation history
The codebase went through structured security hardening (P0/P1/P2). Highlights now in place: real JWT verification everywhere, gateway rate‑limiting, DB‑backed single‑use nonces, rotating refresh tokens with reuse detection, webhook signature + dedupe, exact on‑chain transaction verification on the custody path, and an escrow fee‑snapshot. See docs/REMEDIATION_PLAN_2026-06-10.md and docs/TECHNICAL_REVIEW_2026-06-10.md in the repo for the full record.