0.2.0 - 2026-06-07
First versioned release. Includes a full security/correctness/performance audit, a critical production database repair, and the introduction of changelog + single-source versioning.
⚠️ Published scores change in this release. The scoring adjustments below shift the values in published kind-30385 assertions, so the algorithm version is bumped tov0.3.0(previously advertised inconsistently asv0.1.1/v0.1.2/v0.2.0). Consumers should expect score movement for some relays.
- Fixed stored XSS across the dashboard and network pages.
escHtmlnow escapes quotes (making it attribute-safe), the brokenescAttrwas replaced with a correct encoder, and all relay-controlled fields (name, software, operator pubkey, policy, country names, etc.) are now escaped at every interpolation point, including Leaflet map popups. - Fixed rate-limiter bypass / shared bucket. Client IP is now taken from the
trusted socket address (
server.requestIP); proxy headers (cf-connecting-ip/x-forwarded-for) are only honored when the newapi.trustProxysetting (or--trust-proxyflag) is enabled. Previously all non-Cloudflare clients shared one'unknown'bucket and the header was spoofable. - Blocked SSRF via user-submitted relay URLs.
/api/trackand the prober now reject loopback / private / link-local / cloud-metadata hosts, and the NIP-11 fetch no longer follows redirects, caps the body at 256 KB, and requires a JSON content type. - Capped abuse of write/compute endpoints.
/api/tracknow enforcestargets.maxRelays;/api/network/statsclamps theperiodparameter to an allow-list to prevent cache-busting amplification. - Bounded untrusted relay input. The WoT client now sets a query
limitand hard-caps buffered assertions to prevent memory/CPU exhaustion from a hostile relay. - Hardened secret handling.
config showredacts the private key, written config files arechmod 600, andloadConfigfails fast on malformed config instead of silently falling back to defaults (which had publishing enabled). - Other hardening. Added Subresource Integrity to the Leaflet CDN tags,
X-Content-Type-Options: nosniff/Referrer-Policyto API JSON responses, and secp256k1 scalar-range validation for private keys.
- Critical: repaired missing database primary keys. On databases created
under an older schema,
probes,nip66_metrics,operators,relay_reports, andscore_historywere missing their declared PRIMARY KEY (whichCREATE TABLE IF NOT EXISTScannot retrofit). This silently broke everyON CONFLICTupsert on those tables. A new repair migration deduplicates and adds the missing unique indexes. This restored NIP-66 metric ingestion and operator-trust persistence, which had been silently failing (on the production instance, for ~88 days). - Fixed silent failure of schema migrations. Migrations that recreate tables now run inside transactions (atomic) and no longer swallow real errors, and they preserve primary keys.
- Fixed a
readScorecomputation bug (mismatched sample filters plus an operator-precedence issue that dropped legitimate zero averages). - Fixed an unhandled promise rejection on database initialization that could crash startup; the original error is now surfaced.
- Fixed
updateMonitorStatsto upsert, so monitors are actually recorded; unified the single-relay and bulk NIP-66 stat queries so the detail and list views no longer disagree. - Fixed a time-of-check/time-of-use double-count in report ingestion.
- Fixed WebSocket/socket and listener leaks in the prober (added a close handler and unified teardown), a relay-pool reconnect storm (backoff no longer resets on every brief reconnect), and added a proactive per-minute publish rate cap.
- Fixed overlapping daemon cycles: the probe/publish and checkpoint loops are now self-rescheduling with re-entrancy guards and per-iteration error handling, so a slow or failing run can't stack or kill the loop.
- Fixed
mergeConfigsilently dropping theprobingconfig block. - Fixed Tor/I2P network misclassification (previously overloaded country code
XX); network type is now derived from the relay URL. - Replaced a non-standard
INSERT OR REPLACEwithON CONFLICTfor the network stats cache. - Added global
unhandledRejection/uncaughtExceptionhandlers to the daemon for graceful shutdown instead of silent death.
- Scoring (affects published values — see algorithm
v0.3.0):- Unlisted countries are now treated as "unknown" (jurisdiction score 75) instead of an invented default of 65, so an unlisted country is no longer scored more leniently than a truly-unknown one.
- The monitor-diversity bonus is now capped at 2.8 (diminishing returns).
- The single-relay and bulk NIP-66 latency-percentile formulas were unified so the detail and list views report identical scores.
- Static files are cached in memory; API JSON responses are no longer pretty-printed (smaller payloads on hot paths).
- The cleanup pass now runs
VACUUMto reclaim disk space. - Consolidated the algorithm version into a single source (
src/version.ts); the dashboard, config defaults, assertions, andALGORITHM.mdnow all agree.
CHANGELOG.md(this file) andsrc/version.tsas the single source of truth for versions.api.trustProxyconfig option and--trust-proxyCLI flag.
ALGORITHM.mdnow describes the displayed-avgLatencyMssource blend (probe vs. monitor, distinct from the connect/read percentile weighting), corrects the proof-of-work penalty range, notes themonitorBonuscap, and flags a known limitation: NIP-66-only relays default to 95 uptime (possible survivorship bias), left unchanged pending confirmation that monitors emit downtime events.
- Initial implementation: NIP-XX relay trust assertions (kind 30385) combining direct probing, NIP-66 monitor data, user reports, and operator verification; reliability/quality/accessibility scoring; REST API and web dashboard; network monitoring page.