Skip to content

Subscriptions need a credential; the socket leaves /tmp - #2

Merged
inkly merged 5 commits into
mainfrom
feat/bus-auth
Sep 22, 2026
Merged

inkly merged 5 commits into
mainfrom
feat/bus-auth

Conversation

@inkly

@inkly inkly commented Sep 22, 2026

Copy link
Copy Markdown
Collaborator
  • Subscribing (the event and action websockets, socket.io websocket and polling) needs a user token or, from loopback, the boot's secret; anyone who reached the dashboard's port could read every event before. A browser cannot set a header on a websocket, so the subscription routes, and only they, also take the token from ?token=.
  • The access log writes the path, not the URI: tokens never reach the journal.
  • The unix socket moves from /tmp, where a local user could squat the path, to /var/run/casaos/message-bus.sock (0600), in a runtime directory the bus creates 0755 and brings back to 0755 if an older bus left it 0777.
  • CasaOS-Common v0.4.27.

Ships with the dashboard and UserService that send the credential.

Brings in external.MessageBusSocketPath, which the bus needs to move its unix socket out of /tmp. None of the changed Common APIs (ExecuteScripts, PublishEventInSocket) are used here, so nothing else had to change.
The bus listened on /tmp/message-bus.sock. /tmp is world-writable, so any local user could create that path first and keep the bus from listening. The socket now lives at external.MessageBusSocketPath(RuntimePath), /var/run/casaos/message-bus.sock by default, a directory only root can write. main creates the runtime directory before anything else, a socket left by the previous run is removed before listening, and the socket stays chmod 0600 since requests on it skip authentication. app-management publishes there through Common's PublishEventInSocket.
skipJWT let any GET with Upgrade: websocket through on the subscribe routes, so anyone who reached the dashboard's port could read every event. That exemption is gone: a request skips the user JWT only on the unix socket or from loopback with the internal secret (external.IsInternalRequest), which is how user-service subscribes.

Browsers cannot set headers on a WebSocket, so the subscription routes - GET /event/{source_id}, GET /action/{source_id} and GET/POST on /socket.io and /socket.io/, websocket and polling alike - also take the JWT from the token query parameter, which the dashboard now sends. The lookup is keyed on method and matched route, so every other route ignores the parameter. The OpenAPI spec documents it as a second, query-based security scheme on those six operations; codegen is regenerated from it.

The access log used echo's default format, which records the request URI with its query string, token included. It now records the path instead. Nothing else in the repo logs a request URL or query: the websocket and socket.io handlers log the remote address only.

Tests cover each subscription route without a credential, with ?token, with a tampered ?token, with the header, with the secret from loopback and from the LAN; ?token on non-subscription routes; and an access log free of the token.
The bus created its runtime directory with Common's file.IsNotExistMkDir, which chmods it to 0777 without the sticky bit. When the bus is the first service to create /var/run/casaos (the gateway failed or has not started yet, or RuntimePath differs), any local user could unlink message-bus.sock and bind their own socket in its place, taking app-management's event posts away from the bus. Create it with os.MkdirAll(path, 0o755), as writeAddressFile and the gateway already do, so only root can write there.
MkdirAll leaves an existing directory as it is, and buses before this one
created /var/run/casaos 0777. Until the next reboot that directory would hold
the bus socket, and anyone could swap it. The bus now brings it back to 0755.
@inkly
inkly merged commit 78812ad into main Sep 22, 2026
1 check passed
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