Skip to content

Security: Neverdecel/neverpay

Security

SECURITY.md

Security

neverpay is a single-operator, self-hosted application that handles money (crypto receipts), license keys, file delivery, and an admin console. This documents its threat model, the controls in place, and accepted residual risk.

Reporting

Found a vulnerability? Open a private report via the repository's security advisories, or email the maintainer. Please don't file public issues for exploitable bugs until a fix is out.

Controls in place

Custody / funds

  • Non-custodial: only watch-only xpubs / receiving accounts are configured. Private keys never touch neverpay. There is no funds-moving code path.
  • A unique pay-to address per order (atomic HD index for EVM/UTXO/Tron; a unique X-address / muxed M-address for XRP/XLM), so payments are unambiguously attributable and addresses aren't reused.

Admin auth

  • bcrypt password hashing (cost 12); 256-bit random session tokens; sessions expire (7d) and are validated server-side on every request.
  • Session cookie is HttpOnly, SameSite=Lax, and Secure when served over HTTPS (NEVERPAY_BASE_URL=https://…).
  • Password policy: a 12-character minimum plus a small common-password blocklist, enforced by neverpay setup and for NEVERPAY_ADMIN_PASSWORD on first run. The auto-generated default password is 96-bit. (An already-configured install with a now-sub-policy NEVERPAY_ADMIN_PASSWORD is not bricked on upgrade — it logs a warning and keeps the existing password.)
  • POST /admin/login is rate-limited (10/min/IP) and brute-force throttled: consecutive failures from an IP trigger an escalating, auto-expiring cooldown (capped at 15 min), with a fixed ~750 ms delay on every failed attempt. A correct password is always accepted — even from a throttled IP — and clears the cooldown, so the throttle only delays repeated wrong guesses. Active throttling is visible on /admin/status.

License API (/api/v1)

  • Unauthenticated by design — the signed license token is the credential, and it is not a cookie, so permissive CORS (*) grants no privilege.
  • Offline verification uses Ed25519: tampering invalidates the signature.
  • Rate-limited (120/min/IP).

Web hardening

  • html/template auto-escaping on all rendered data; the only raw-CSS sink (brand accent) is strictly hex-validated.
  • SQL is fully parameterized (no string-built queries).
  • Downloads: 160-bit unguessable token, gated on fulfilled + time window; files served via filepath.Base (no path traversal); uploads stored under a random name.
  • Security headers: X-Content-Type-Options: nosniff, Referrer-Policy: no-referrer (keeps download-token URLs out of Referer), and X-Frame-Options: DENY everywhere except the embeddable checkout (/p/…, /order/…), which is intentionally framed by seller sites.

Webhooks

  • Outbound deliveries are HMAC-SHA256 signed (X-Neverpay-Signature: t=…,v1=… over t.body); verify with timestamp tolerance to resist replay.
  • SSRF-guarded: webhook URLs must be http(s) and, by default, may not resolve to loopback/private/link-local addresses (incl. cloud metadata 169.254.169.254); the check runs at dial time, after DNS, so rebinding can't bypass it, and redirects are not followed. Set NEVERPAY_WEBHOOK_ALLOW_PRIVATE=1 to target a same-host/LAN receiver.

Admin CSRF

  • The admin session cookie is SameSite=Lax; in addition, state-changing admin requests (incl. login/logout) are rejected when they are cross-origin (Sec-Fetch-Site, falling back to an Origin/Referer host check). The public checkout and licensing API are intentionally cross-origin and unguarded.

Privacy

  • No telemetry, no third-party browser calls (system-font stack, embedded assets) — runs cleanly as a Tor hidden service.

Operator responsibilities

  • Serve behind HTTPS (or a .onion). neverpay speaks HTTP; terminate TLS at a reverse proxy and set NEVERPAY_BASE_URL accordingly.
  • Behind a proxy, set NEVERPAY_TRUST_PROXY=1 so per-client rate limiting uses the real client IP from X-Forwarded-For. Do not set it when neverpay is directly exposed — X-Forwarded-For is then attacker-spoofable.
  • Choose a strong NEVERPAY_ADMIN_PASSWORD (or use the generated one).
  • The SQLite DB holds sensitive secrets — the license signing seed, webhook secrets, and the admin hash. Protect and back it up (see DEPLOY.md). Losing the signing seed means previously issued keys can't be verified. The admin status page (/admin/status) is session-gated and renders only the public key — the seed is never exposed over HTTP.

Accepted residual risk

  • No CSRF tokens on admin forms. Mitigated by SameSite=Lax session cookies (browsers don't send the cookie on cross-site POST) plus admin frame-deny. Acceptable for a single-operator console; revisit if multi-user admin lands.
  • Unbounded activation rows when a key has no seat limit (seats = 0). A holder of a valid key could create many HWID activations. Bounded by the API rate limit; set a seat limit for keys that need hard caps.
  • Login rate-limit/throttle is keyed by source IP. On a shared egress (Tor exit, CGNAT, VPN — set NEVERPAY_TRUST_PROXY=1 behind a reverse proxy to get per-client IPs) the operator and an attacker share one key. The brute-force throttle never blocks a correct password (it's verified before the cooldown is consulted), but a flood from a shared IP can still hit the coarse 10/min/IP request limit; a logged-in operator (7-day session) is unaffected.
  • Stale pending orders / expired sessions accumulate as rows (rejected on read). Negligible at single-operator scale; prune via DB maintenance if needed.
  • EVM detection reads current balance, not cumulative received. Safe given fresh per-order addresses; don't sweep an order's address before fulfillment.

There aren't any published security advisories