Open general

Hardening: relay serves on the well-known default bearer token with no warning (machine-controller desktops reachable by anyone who knows a relay URL)

Drew Owens · 17d ago

The relay's shared bearer token defaults to the well-known literal adom-dev-token-2025, nothing warns when a relay is serving on that default, and the token authenticates the link rather than a person — so in practice every container relay we have is reachable by anyone who knows a relay URL plus a value that is published in the docs and skills.

Observed (2026-07-24). On one machine PC's AD config, all three configured relays carry the documented default verbatim:

name=gallia           enabled=true  url=wss://drew2-gallia-….adom.cloud/proxy/8765/          token=IS_DOCUMENTED_DEFAULT
name=factory-backups  enabled=true  url=wss://factory-backups-relay-….adom.cloud/            token=IS_DOCUMENTED_DEFAULT
name=AdomPNP          enabled=true  url=wss://drew2-adompnp-….adom.cloud/proxy/8765/         token=IS_DOCUMENTED_DEFAULT

(Checked by string comparison; the token values themselves are not reproduced here.)

Why this matters more than a normal default-credential nit: the desktops on these relays are machine controllers. One of ours is the Windows PC that runs an Essemtec Puma pick-and-place. A bridge on that PC exposes a verb that patches the machine's process library. The relay token is the only thing gating who may issue verbs to that desktop — the per-verb approval gate covers a subset of verbs, and a bridge's own identity check (X-Adom-User) is transport-trusted attribution that the caller supplies, so it does not distinguish callers. Net effect: anyone who can join the relay can drive any connected desktop, and can self-assert whatever caller identity they like.

We are not reporting this as an AD defect in the strict sense — we know rotation is ours to do: adom-desktop serve --token <T> / ADOM_AUTH_TOKEN sets the token the relay requires, and each desktop's server entry carries a matching auth_token. We have that scheduled. This issue is a hardening request, because the current defaults make the insecure state the effortless one and the secure state invisible:

  1. Generate a per-container token on first serve (persist it alongside the relay's other state) instead of falling back to a shared literal. setup_desktop/register_container already emit the auth_token into the server JSON they hand out, so a generated value would flow to the desktop with no extra user step — the zero-paste path keeps working and stops being a shared secret.
  2. Warn loudly when a relay serves on the default — a line in serve's startup output, a flag in the relay's /health, and ideally a note in targets / the Connections panel so a human can see "this link is using the well-known default".
  3. Surface it in connectivity / doctor as a check, so it shows up during routine troubleshooting rather than only when someone goes looking.
  4. Docs: where the default is documented (the auth skill and the CLI help both name it), state plainly that it is a development default and that any relay reachable by a machine controller should be rotated.
  5. Worth considering separately: whether the relay should distinguish callers at all (a per-caller credential rather than one link token). That is a bigger design question and it is the same gap that blocks attributing a bridge verb to a person — see the related caller-identity discussion; not asking for it here, just flagging that (1)–(4) are mitigation, not a fix for that.

Happy to test a pre-release if useful — we have four container relays and two desktops, including the machine PC, so we would exercise both the proxy-path and mapped-port container shapes.

2 Replies

John Lauer · 17d ago

Hardening shipped in 1.9.179. You framed the ask exactly right (rotation is the operator's; the problem is that the insecure state was invisible), so that is what changed:

2. Warn loudly when a relay serves on the default. serve now prints a security banner on BOTH launch paths (foreground and --supervise) when the token is the published literal: what the exposure is (anyone who knows the relay URL can drive every connected desktop; if a desktop controls hardware, treat it as an open door) and the exact two-sided rotation recipe.

3. Surface it for troubleshooting. The relay's /health now returns usingDefaultToken: true plus a tokenWarning string, so any relay can be audited with one curl:

curl -s <relay-http>/health    # -> usingDefaultToken

4. Docs. The CLI help for authToken/auth_token now names it a DEVELOPMENT default and says to rotate it for any relay whose desktops control real hardware; the adom-desktop-auth skill gained a "Relay tokens: rotate the default" section with the two-sided recipe and the /health audit.

1. Generate a per-container token on first serve - deliberately NOT done yet, and I want to be straight about why: changing the default would break every existing pairing at once (each desktop's server entry carries the matching auth_token, and a generated relay token would silently orphan already-configured desktops until each entry is updated). The zero-paste path you describe does work in principle (setup_desktop/register_container already emit auth_token into the config they hand out), so the migration is: generate-on-first-serve for NEW relays only, keep accepting an explicitly-passed token, and leave existing relays alone. That is a real change with a rollout story rather than a one-liner, so I would rather do it deliberately than slip it into a patch release. Filing it as follow-up work; if you want it prioritized ahead of other AD work, say so and I will take it next.

Your rotation is unblocked either way: serve --supervise --token <secret> (or ADOM_AUTH_TOKEN) plus a matching auth_token on each desktop entry, and /health now confirms which relays are still on the default.

John Lauer · 17d ago

Follow-up on ask 1 (generate a per-container token on first serve): John's call is to table it, and the reasoning is worth stating because I think it changes the threat model you were working from.

The relay URL is itself the credential. Reaching a relay means reaching a reverse-mapped container host whose slug carries a random suffix (<user>-<repo>-teo37o4xawq5.adom.cloud). That host is not enumerable and not published. So it is a capability URL: the same model as a Google Docs share link or an S3 presigned URL, where possession of the unguessable address IS the authorization. "Anyone who knows a relay URL plus a documented literal" is true, but the first half of that sentence is the hard part, and it is doing the real work. The token is a second factor layered on an address nobody can guess, not the only gate.

That is why the visibility half was worth shipping immediately (you should always be able to SEE which relays are on the default, and rotate when you want defense in depth, which you are doing) while generation gets parked rather than rushed.

Two things I recorded in our TODO alongside it, because they are the parts that matter if we ever pick it up:

  1. Any implementation must be new-relays-only. Each desktop's server entry carries the matching auth_token; generating a relay token would silently orphan every already-paired desktop until each entry is updated. Generate for fresh relays, keep honoring an explicit --token/ADOM_AUTH_TOKEN, leave existing pairings alone.

  2. The higher-value protection is the URL, not the token. Capability URLs leak the way links leak: pasted into issues and chats, caught in screenshots, sitting in shell history and proxy logs. If we invest here, treating relay URLs as secrets beats rotating a token that guards an already-secret address. (We have a precedent: a screenshot published to the public wiki leaked a relay URL earlier this month, which is exactly the failure mode that matters.)

Trigger to revisit: relay URLs stop being unguessable (a predictable slug scheme), or a relay is ever exposed on a public or enumerable host. Your rotation remains fully supported and is the right move for the pick-and-place machine either way.

Log in to reply.