NeroLink 1.0.0 - Fabric 26.1.2
Curse Maven Snippet
What's new
The first production release. Security, privacy and robustness hardening of the bridge; the companion app must be updated for direct-mode pairing. Requires Neroland Core 1.13.0 or newer, below 2.0.
Security
- Direct mode is now TLS. The local listener (default port
25580) serves TLS with a long-lived self-signed ECDSA P-256 certificate generated on first start and stored atconfig/nerolink/bridge-tls.p12(owner-only permissions where the filesystem supports them). The same certificate is reused across worlds and restarts; delete the file to rotate it, after which every direct-mode device must re-pair. A corrupt file is moved aside tobridge-tls.p12.brokenand a new certificate is generated. The companion app pins the certificate's SHA-256 fingerprint. - Channel-bound direct pairing. On the direct TLS listener the app sends
codeProof = hex(PBKDF2-HMAC-SHA256(password = code uppercased with dashes removed, salt = "nerolink-pair-v1:" + lowercase hex certificate fingerprint, 100000 iterations, 32 bytes))instead of the code, so the code never crosses the wire and a pairing through a different certificate fails. PBKDF2 makes brute-forcing a captured proof take months rather than the code's 5-minute life; the bridge derives each code's proof once, in the background, when/nerolink pairissues it, so failed attempts cost the server nothing. A plaincodeon the direct listener is rejected with400 VALIDATION("codeProof required on direct connections (update the NeroLink app)"). Over the relay the plaincodeis still sent (TLS to the relay's public certificate). - Pairing brute-force protection. Failed redemptions are budgeted: 5 per source per
minute (the client IP on direct connections; on relay connections a salted, daily-rotating
hash of the client IP forwarded by the relay), with a 120-per-minute global backstop.
Attempts are refused with
429 RATE_LIMITED(retryAfterMs: 60000) only while a budget is exceeded; pending codes are never voided, so a flood cannot lock players out once it stops. - Hardening. HTTP bodies are capped at 16 KiB; 16 concurrent TCP connections per address;
connections idle for 90 s are closed; untrusted JSON nested deeper than 32 levels is rejected
before parsing; WebSocket control frames are charged to the per-device rate limit; at most 32
topic subscriptions per connection, and topic ids must look like
module.section; every response carriesCache-Control: no-store; client-supplied names are sanitised before logging; a request waiting on the server thread times out after 10 s with503. - Single-player and integrated servers bind
127.0.0.1by default, so a single-player world never opens a network port on its own (seesingleplayerLanAccess).
Added
/nerolink pairnow whispers a Direct address (only when direct mode is reachable from the network) and a Security code — the first 16 hex digits of the certificate fingerprint asXXXX-XXXX-XXXX-XXXX. The app shows the same code; the player checks they match.- Config keys
directEnabled(defaulttrue;false= relay only),tlsEnabled(defaulttrue; plain HTTP is refused unlessbindAddressis127.0.0.1, and is for local tooling only: the app's direct mode requires TLS),singleplayerLanAccess(defaultfalse) andmaxDevicesPerPlayer(default5, range1–64). GET /api/v1/session(authenticated) returns{playerUuid, deviceId, deviceName}for the calling token; the relay uses it to bind push registrations to the real player.POST /api/v1/pairresponses now includedeviceId.- Relay tunnel:
reqframes may carryip(a salted hash, never an IP); new bridge→relay framepush_unbind {playerUuid, deviceId}. - Tests:
PairingServiceTest(10),BridgeTlsTest(4, including a pairing-proof vector shared with the app),JsonTest(4) andRateLimiterTest(1). 38 tests pass on:neoforge:26.2.
Changed
- Default relay moved to
https://relay.nerolandmc.net(washttps://nerorelay.neroserver.xyz). This changes therelayOrigindefault used by/nerolink setup. Existing servers keep the origin already written toconfig/nerolink.propertiesand their stored registration: setrelayOriginto the new host and run/nerolink setupagain, then re-pair devices (the app URL changes). Dev tooling (tools/setup_dev_relay.py, VS Code tasks), README and wiki updated. - Modrinth page description links the NeroLink app beta and the ecosystem pages on
nerolandmc.net. maxClients(default64) is now really the global cap on concurrent live-update (WebSocket) connections, direct and relay combined. Extra connections are closed with1013(direct) /4503(relay) "server busy". The per-player device cap moved tomaxDevicesPerPlayer.POST /api/v1/privacy/eraseis now bridge-scoped. It erases NeroLink's own data (device tokens, preferences, pending pairing code, live sockets, relay push registrations) but no longer triggers Core's erasure across every Neroland mod — a bearer token on a phone should not be able to wipe game progress. The response carriesscope: "bridge"and a note pointing at/neroland data erasemefor full erasure. Core'sPlayerDataErasurehook still purges NeroLink data when/neroland data erasemeruns.- Retention. Expired devices (inactive longer than
tokenExpiryDays, default 90) are deleted by a sweep at bridge start and every 6 hours, and every token has an absolute lifetime of 365 days. - Relay
erasetombstones andpush_unbindframes are queued persistently in the world's relay settings (capped at 4096 entries) while the tunnel is down, and delivered when it reconnects. Only queued when a relay is configured. - The default
privacyNoticeTextnow lists everything stored: hashed device token, the device name the player enters, pairing and last-connected times and notification preferences; that devices you stop using are deleted automatically; export/delete from the app; and the relay transit note. - Revoking a device (
/nerolink revoke,DELETE /session, expiry) now also closes its live WebSocket immediately and unbinds its relay push registration. - Discovery
bridgeVersionandcoreVersionreport the real installed versions from loader metadata. snapshotCadenceHotMs/snapshotCadenceColdMsare documented as reserved (not used yet).- Metadata: a real mod description in all three loader manifests;
displayURL/ homepagehttps://neroland.co.za; Fabric's Core dependency range is now>=<pin> <2.0.0like the other loaders. Build pin Core1.13.0.
Fixed
/nerolink setupwhile the relay tunnel was already up could leave the bridge reconnecting in a loop (the old connection's close tore down the new one). Connections now carry a generation and stale callbacks are ignored.- Live-update deltas published while a batch was being sent could be lost; batching is now atomic per topic.
- Queued relay erasures are removed from the queue only after they were actually written to the tunnel.
DELETE /sessionnow revokes on the server thread, so a revoke can never be lost to a concurrent world save; devices found expired at login are left for the retention sweep, which also closes their sockets and unbinds their relay push.- The device-token store is thread-safe (it was read and written from network threads while the game thread saved it).
- A reconnecting device no longer loses its live subscription when its old socket finishes closing.
- Stop/restart: the listener waits for its socket and threads to shut down (
SO_REUSEADDR), so reopening a single-player world quickly no longer fails with "address in use". - Discovery no longer reports the hard-coded
bridgeVersion"0.0.1-alpha.1" /coreVersion"2.0.0".
Known limitations
- The relay is not end-to-end encrypted: it terminates TLS and can read traffic in memory to forward it (it never stores it).
- Crash telemetry uses the global Sentry client shared with other Neroland mods.
- The
serverIdin pair/discovery responses is still derived from the world name.
All Relations
- All Relations
- Embedded Library
- Optional Dependency
- Required Dependency
- Tool
- Incompatible
- Include
This mod has no related projects