Skip to content

fix: initialize the servers before publishing their addresses (7.0.1:1) - #25

Merged
MattDHill merged 1 commit into
masterfrom
fix/relay-address-before-init
Aug 28, 2026
Merged

fix: initialize the servers before publishing their addresses (7.0.1:1)#25
MattDHill merged 1 commit into
masterfrom
fix/relay-address-before-init

Conversation

@MattDHill

@MattDHill MattDHill commented Aug 28, 2026

Copy link
Copy Markdown
Member

On install, the SMP and XFTP addresses were published as:

smp://undefined:null@relay.example:5223

Well-formed enough for a client to accept and store, and impossible to authenticate against.

Cause: init order, not a race

export const init = sdk.setupInit(
  restoreInit,
  versionGraph,
  setInterfaces,   // 3rd — reads the fingerprint and the password
  actions,
  initServers,     // 5th — runs `smp-server init`, which writes them
  watchTorProxy,
  setDependencies,
)

setupInit runs handlers sequentially, so on a fresh install setInterfaces executed before smp-server init had ever run. smp-configs is empty at that point — no fingerprint file, no smp-server.ini to read AUTH.create_password from — and both empty reads were interpolated straight into username.

The pass does re-run on its own, since both reads are .const() and setupInit gives each handler a constRetry. But the window it corrects is two subcontainer spawns doing keypair generation wide, and it recurs on every reinstall — with a fresh CA on the other side.

Fix

initServers moves above setInterfaces. It's a seed step: it reads no host or interface state, only volumes and file models (smpMounts/xftpMounts are plain volume mounts), while setInterfaces is the thing that consumes what it writes. The dependency only ever ran one way. This matches how lnd and cln — the other two packages whose setInterfaces reads generated files — order their own seed steps ahead of it.

That's the whole change. The reads are left unguarded, because after the reorder nothing can reach them empty: execFail throws if smp-server init fails, the non-install path already throws when the ini carries no create_password, and setupInit awaits each handler without catching — so either failure aborts init rather than arriving at setInterfaces with nothing to read.

(lnd and cln do guard their equivalent reads, but for a condition this package doesn't have: their credentials are deleted and re-minted at runtime by rune revocation and macaroon rotation, so both pass a custom eq to ignore the gap. Nothing here ever removes the fingerprint.)

Bumped to 7.0.1:1; wrapper-only, no migration.

How it surfaced

simplex-websocket-bridge-startos resolves this interface as its Local relay and picked up the undefined:null form while following a reinstalled server to its new address. It landed a fingerprint-shape guard on its side (Start9-Community/simplex-websocket-bridge-startos#14) — worth keeping regardless, since a consumer shouldn't trust a relay URI's shape — but the malformed value originated here.

Related: Start9Labs/start-technologies#3861 fixes the SDK-side reason the bridge stopped noticing address changes at all.

@MattDHill
MattDHill force-pushed the fix/relay-address-before-init branch from cc37436 to 6e7dd80 Compare August 28, 2026 20:09
@MattDHill MattDHill changed the title fix: withhold the relay address until the server is initialized (7.0.1:1) fix: initialize the servers before publishing their addresses (7.0.1:1) Aug 28, 2026
`setInterfaces` ran third in the init chain and `initServers` fifth, so on
install the interfaces pass read the CA fingerprint and the auth password
before `smp-server init` had written either. Both are interpolated into
`username`, so the pass published `smp://undefined:null@host:5223` — a
well-formed address no client can authenticate against — until the `.const()`
watches re-ran it. The window is two subcontainer spawns doing keypair
generation wide, and it recurs on every reinstall.

`initServers` is a seed step: it reads no host or interface state, only volumes
and file models, while `setInterfaces` consumes what it writes. Hoisting it
above `setInterfaces` closes the window, and matches how lnd and cln order
their own seed steps.

Nothing guards the reads, because after the reorder nothing can reach them
empty: `execFail` throws if `smp-server init` fails, the non-install path
already throws when the ini has no `create_password`, and `setupInit`
propagates either — so a failure aborts init rather than reaching
`setInterfaces`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@MattDHill
MattDHill force-pushed the fix/relay-address-before-init branch from 6e7dd80 to f249192 Compare August 28, 2026 20:12
@MattDHill
MattDHill merged commit 5955440 into master Aug 28, 2026
3 checks passed
@MattDHill
MattDHill deleted the fix/relay-address-before-init branch August 28, 2026 20:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant