mirror of
https://github.com/Such-Software/smirk-backend-core.git
synced 2026-10-17 07:21:32 +02:00
Fixes every confirmed backend finding from the v0.3.0 pre-ship review. Identity (the two that corrupt accounts): - nostr_register creates a row with pubkey_hash NULL, so that wallet's later /auth/extension call matched nothing, fell through the rotation path (which no client ever satisfies, since nothing sends rotation_signature) and minted a SECOND account. A fingerprint match on a NULL-pubkey_hash row now adopts the submitted pubkey_hash onto that row, but only when the row's on-file BTC key equals the submitted one, so a bare fingerprint (which check_restore discloses, and is therefore not a secret) still cannot claim an account. - The fingerprint-ownership check no longer short-circuits on returning_by_pubkey. It was skipped for returning users, so a fingerprint owned by another row got backfilled, violated the UNIQUE index, and 500'd that account on every subsequent login. - The rotation proof is verified over a distinct, instance- and target-bound message. It shared the login message, so a captured login signature was a replayable rotation proof against another instance. Abuse control: - The rate limiters key on the real client IP, honouring X-Forwarded-For only from a trusted proxy. Behind the TLS terminator every request looked like 127.0.0.1, so the whole internet shared one bucket and ~3 req/s 429'd every wallet unlock globally. - A PoW solution is single-use instead of replayable for the challenge TTL. - Delegated /verify is opt-in and rate-limited; it was an unauthenticated amplifier into Electrum, LWS and a full-genesis grin scan. - A grin scan with no start_height gets the same depth policy and PoW price as one that passes a height, instead of an unbounded free scan. Retention and logging: - The retention sweep is implemented AND scheduled. RETENTION_* was advertised and inert, which made the privacy policy's retention promise untrue. Zero means keep forever, and each statement is batched so a backlog drains over ticks rather than holding row locks. - login_events.ip_hash is no longer written, and legacy values are scrubbed: nothing ever read it, so it was retention without a purpose. - Tracing spans log user_id only, not the raw npub and username. - secret-scan.sh matches the coverage its comment claims. Also fixes the test suite, which did not compile on this commit: NewSession gained an id inc64cd9band two call sites were never updated. mint_session now binds one session_id for both the token and the row, since a different id there would silently recreate the bugc64cd9bfixed.
52 lines
2.8 KiB
Markdown
52 lines
2.8 KiB
Markdown
# Privacy Policy
|
|
|
|
The canonical Smirk privacy policy lives at **https://smirk.cash/privacy** and
|
|
describes the data practices of the instance operated by Such Software LLC
|
|
(api.smirk.cash).
|
|
|
|
## For operators who self-host
|
|
|
|
This backend is open source and self-hostable. **If you run your own instance, you
|
|
are an independent data controller for it** and the Such Software policy does not
|
|
speak for you. What your instance receives from wallets that connect to it:
|
|
|
|
- **Monero/Wownero view keys** (to scan for users' incoming funds; a view key
|
|
cannot spend) and their scan/registration state.
|
|
- **Public addresses** for BTC/LTC balance lookups and **signed transactions** to
|
|
broadcast. Recipient address and amount are not sent on broadcast.
|
|
- A one-way **seed fingerprint** (SHA-256) as a non-reversible wallet identifier.
|
|
- If enabled: the **username to npub** mapping published in your NIP-05 directory,
|
|
and, for the relay, encrypted message envelopes (ciphertext only).
|
|
- IP addresses **never in raw form**. The per-IP rate limiter keeps its counters
|
|
in memory, and the only IP-derived value written to disk is a salted one-way
|
|
hash on `restore_attempts`, which the seed-restore abuse limiter reads back.
|
|
Login events record no IP at all, and the raw-IP columns that older schemas
|
|
carried on `sessions` and `audit_logs` were dropped in migration
|
|
`20260722000001_privacy_drop_pii`. One exception, and it is about you rather
|
|
than your users: the tamper-evident operator audit chain (`admin_audit_logs`)
|
|
records the IP of privileged admin actions.
|
|
|
|
Private keys and seed phrases are never transmitted to the backend.
|
|
|
|
## Retention
|
|
|
|
Retention is enforced by the backend, not merely advertised. A background sweep
|
|
deletes `login_events` older than `RETENTION_LOGIN_EVENTS_DAYS` and `audit_logs`
|
|
older than `RETENTION_AUDIT_DAYS`, in bounded batches so a long-running instance
|
|
drains its backlog without stalling writers. Setting either knob to `0` means
|
|
keep indefinitely, so an unset value never destroys data by surprise.
|
|
|
|
The `admin_audit_logs` chain is deliberately exempt: it is hash-chained and
|
|
MAC'd, so a deleted row reads as tampering to the verifier. Prune it on purpose
|
|
if your policy calls for it.
|
|
|
|
Self-service erasure (`ERASURE_ENABLED`) is separate and user-initiated: a
|
|
confirmed request deletes the account and its owned rows after a grace window,
|
|
purging or anonymizing that user's login events per `ERASURE_PURGE_LOGIN_EVENTS`.
|
|
|
|
If you offer your instance to others, publish your own privacy policy describing
|
|
how you handle this data, your retention, and your jurisdiction. The knobs that
|
|
affect retention and data exposure (login-event and audit retention, erasure,
|
|
relay retention, landing-page exposure) are documented in
|
|
[docs/operations/CONSOLE.md](docs/operations/CONSOLE.md) and the root `.env.example`.
|