Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 (when Tor routing is on) routing everything over Tor. 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

MeasureWhat it preventsWhere
Requests are never auto-paidA stranger draining you with an Invoice-1Ingest policy (decide() → SurfaceRequest)
Replies bound to counterparty + pending txA forged Standard-2/Invoice-2 finalizing somethingIngest policy
Size ceilings (64 K / 32 K / 30 K / 256)Memory-blow-up / DoS via huge messagesProtocol constants
Encrypted key at restOffline key theft; password grindingIdentity: NIP-49 ncryptsec, scrypt log_N=16, 0600
Processed-id archive + 30-day TTLReplaying an old payment messageStorage (processed db)
NIP-98 single-use authReplaying a name registration requestName authority
Per-sender rate limitsSpam flooding from one keyNostrService (contact 30/h, unknown 10/h)
Everything over Tor, no clearnet lookups (when Tor routing is on)Your IP / network location exposed to the relay and on-path observersTor, Name resolution
Relay-side randomized release + NIP-59 backdatingMatching a send to a receive by timingTor pillar
Hostname-validated TLS over every circuitA hostile hop or lying resolver reading or MITMing a connectionTor, Tor exit path
NIP-44 v3 context binding (when negotiated)Ciphertext from one wrap layer replayed as the otherProtocol
Relays gated by a local NIP-11 probeA broken or hostile relay pool entry silently dropping paymentsRelays
Reserved names, homograph folding, cooldownImpersonation / squatting on namesName authority
Tag-independent classificationA sender lying about message type via tagsProtocol (classify by parsed slate only)
A failed dispatch is finalOne intended payment being paid twice after Try againSend & request, Ingest policy (nostr_cancel_slate(); SendFailed never finalized or re-sent)
One cancel-safety ruleCancelling a payment that may already be on chainCancel & decline (cancel_safe())
Sessions never touch payment messages; crypto needs the Direct messages grantA trusted site reading or writing your wallet’s payment messagesAuthorize Sessions (is_wallet_protocol_payload(), decide_crypto())
Site HTTP auth for its own domain only; strict site domains and callback bindingA site’s signature or token being used at another serviceAuthorize Sessions, Sign in (http_auth_target(), domain_bound())
Password change moves seed and identities togetherA wallet left with the seed and identities on different passwordsIdentity (change_password())
Secret screens hidden from capture; secret copies cleared after 45 s (capture: Android)A seed or nsec leaking through screenshots, screen recording, the Recents view or the clipboardIdentity (FLAG_SECURE, copySecretText)
Overlay touches ignored on money-commit screens (Android)Another app’s window steering a hold-to-send or hold-to-acceptSend & request (touch_filter_wanted())

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 many Goblin clients reach the relay over Tor (those connections arrive from shared Tor 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.
  • Failed dispatch and the cancel-safety rule: nostr_cancel_slate() (goblin/src/wallet/wallet.rs:4026), cancel_safe() (goblin/src/wallet/types.rs:417).
  • Session guards: goblin/src/nostr/session/classify.rs:158 (is_wallet_protocol_payload()), :251 (http_auth_target()); goblin/src/nostr/session/mod.rs:339 (decide_crypto()); goblin/src/nostr/authuri.rs:223, :323 (site domain, callback binding).
  • Android capture, clipboard and overlay guards: goblin/android/app/src/main/java/mw/gri/android/MainActivity.java:387-413 (touch filter), :552-610 (secret copy and clear), :826 (FLAG_SECURE).
  • Replay protection: goblin/src/nostr/store.rs and the server’s NIP-98 handling.
  • Live guards are covered by goblin/tests/{nostr_e2e,replay_check}.rs.