Security hardening
Summary. A grab-bag of the defensive choices that don’t fit on one feature page: never auto-paying a request, binding replies to the expected counterparty, hard size ceilings, encrypted keys at rest, replay protection, rate limiting, and routing everything over the mixnet. This page is a map to where each lives.
Motivation
A wallet that accepts messages from strangers and moves money is an attractive target. Goblin’s posture is defense-in-depth: assume any incoming message is hostile, validate before acting, cap everything, and never let the network see more than ciphertext.
The measures
| Measure | What it prevents | Where |
|---|---|---|
| Requests are never auto-paid | A stranger draining you with an Invoice-1 | Ingest policy (decide() → SurfaceRequest) |
| Replies bound to counterparty + pending tx | A forged Standard-2/Invoice-2 finalizing something | Ingest policy |
| Size ceilings (64 K / 32 K / 30 K / 256) | Memory-blow-up / DoS via huge messages | Protocol constants |
| Encrypted key at rest | Offline key theft; password grinding | Identity: NIP-49 ncryptsec, scrypt log_N=16, 0600 |
| Processed-id archive + 30-day TTL | Replaying an old payment message | Storage (processed db) |
| NIP-98 single-use auth | Replaying a name registration request | Name authority |
| Per-sender rate limits | Spam flooding from one key | NostrService (contact 30/h, unknown 10/h) |
| Everything over Nym | IP exposure + timing correlation | Nym |
| Reserved names, homograph folding, cooldown | Impersonation / squatting on names | Name authority |
| Tag-independent classification | A sender lying about message type via tags | Protocol (classify by parsed slate only) |
On the server side, the name authority runs under a hardened systemd sandbox and trusts an X-Real-IP set by its reverse proxy for rate limiting. Because Goblin clients share mixnet exit IPs, server-side abuse controls are tuned to be per-connection / per-account rather than naive per-IP.
References
- Ingest invariants:
goblin/src/nostr/ingest.rs. - Protocol ceilings + tag-independence:
goblin/src/nostr/protocol.rs. - Key at rest:
goblin/src/nostr/identity.rs. - Replay protection:
goblin/src/nostr/store.rsand the server’s NIP-98 handling. - Live guards are covered by
goblin/tests/{nostr_e2e,replay_check}.rs.