Skip to content

MultiSeat 0.6.9

Choose a tag to compare

@github-actions github-actions released this 23 Sep 21:30
· 4 commits to master since this release
8315582

Turning API authentication on from the dashboard could lock you out of your own API, with no way
back except editing a file and restarting the service. If you have ever used that toggle, or might,
take this release.

What was happening

The dashboard has a switch for API key authentication. On some hosts, turning it on locked out
every caller — including the request that would turn it off again.

The authentication middleware accepts a request when the key presented matches the key it holds. A
host configured with ApiKey = "disabled" holds an empty key, because "disabled" resolves to no
key at all. The old code was happy to switch authentication on against that empty key, so from the
next request onward everything received 401 Unauthorized. The call that disables authentication
requires the key too, so it was locked out along with everything else.

Recovery meant editing appsettings.json by hand and restarting the service. Nothing warned about
any of it, and the dashboard reported success, because in memory the switch really had flipped.

And the bug that was actually reported

Even where it did not lock you out, the setting did not stick.

Turning authentication off wrote the change to appsettings.json. MultiSeat loads that file first
and appsettings.local.json second, and the later file wins — so on any host that keeps its key in
appsettings.local.json, which is exactly what the documentation recommends, the change survived
until the next service restart and then silently reverted.

A third thing, underneath both

An empty ApiKey never meant "no key". It meant "go and find one in
C:\ProgramData\MultiSeat\api-key.txt, and generate one if it is not there" — a key the operator has
never been shown. Only the literal string "disabled" switches authentication off. Those two values
look interchangeable and are not, and that is what made this confusing to reason about.

What changes

Authentication can no longer be switched on without a key. The code that did so has been
removed rather than guarded: enabling now takes a key, stores it before raising the flag, and
refuses an empty one. "Enabled with no key" can no longer be expressed.

Turning it on produces a key and gives it to you. If a key is already configured it is kept. If
there is none, one is generated, its file permissions are restricted, and the key is returned in
the response
, so the dashboard can show it and save it. You are never left holding nothing, and
the API is never guarded by a secret you have not seen.

The setting is written to the file that actually decides. When appsettings.local.json sets
ApiKey, that is where the change goes. The response reports which file was written, and reports
the error if it could not be — a toggle that cannot persist no longer looks like one that did.

The ambiguous empty value is gone. A real key is written when authentication is on, and the
literal "disabled" when it is off. Never "".

Who this affects

  • Anyone whose ApiKey is "disabled" — the toggle would have locked you out.
  • Anyone who keeps a key in appsettings.local.json — the toggle did not survive a restart.
  • Anyone who has never touched the toggle is unaffected, and nothing about a working install
    changes when you upgrade.

How this was checked

Ten new tests, and all 577 pass in CI: enabling is refused for a null, empty or whitespace key and
never leaves authentication on; the key is stored before the flag; disabling keeps the key for
reuse; and the file chosen for persistence is the local one when it sets ApiKey, and
appsettings.json when there is no local file, when the local file overrides something else, and
when it cannot be parsed. Every one was confirmed to fail with the fix removed.

Exercised on a real host in the failing state. The reference host was configured "disabled" —
the exact state where the old toggle locked out. On the fixed build, enabling authentication
returned a key, wrote it to appsettings.local.json, accepted requests carrying that key and
refused requests without it; disabling restored the opt-out and left the dashboard usable with no
key. That is the failure this release is about, run end to end rather than inferred from tests.

⚠️ One verification here was initially wrong and worth stating: the check meant to prove a test
would fail without the fix did not actually modify the code it was meant to break, so it proved
nothing until that was caught and redone.

Install

.\prerequisites\install-prerequisites.ps1     # drivers - needed either way
.\scripts\install-service.ps1 -FromZip .

Your configuration is preserved, as of 0.6.4 — appsettings.json and appsettings.local.json are
kept byte for byte, with a backup under C:\ProgramData\MultiSeat\config-backups\.

Afterwards, confirm what landed:

& 'C:\Program Files\MultiSeat\MultiSeat.Service.exe' --config

It should report 0.6.9.

Also in this release

Everything from 0.6.8, if you are coming from 0.6.7 or earlier: seats provision on a clean install
instead of failing with no log. See the 0.6.8 notes.

Known limitations

  • Seats capture the remote desktop surface, not a dedicated virtual display. A Windows
    constraint on where a virtual display can attach, not a regression.
  • The seat refresh rate is one value for the whole host, not one per seat.
  • Tearing a seat down still briefly disturbs a standalone Apollo — measured at a few hundred
    milliseconds, announced in the log before it happens, and not fixable from here.
  • An Apollo that dies before it starts logging still explains nothing. Tracked in ApolloVibe.