- Shipped
- September 23, 2026 at 4:09 AM UTC
- Author
- Kamo
- Commit
- c0a6547
SecurityService is the trust boundary for Hosted Computers. It holds the session, the rights and the org chart; ComputeService holds the Kubernetes credentials and no session at all. Nothing downstream re-checks a right and nothing here touches a cluster. THE ORG ID IS NEVER TAKEN FROM THE REQUEST. Every call stamps the caller's own resolved organization. A Hosted Computer is a whole machine on a network that sees only its own organization and the internet, so a handler that accepted an org id from a body would not be a leaky list — it would be a door into somebody else's desktop. The assign path additionally resolves the target member's email through the caller's own organization, which doubles as the membership check: without it an administrator could assign a computer to a member id belonging to another tenant and have the machine built in this org's namespace under that person's name. THREE DIFFERENT GATES, BECAUSE THERE ARE THREE DIFFERENT QUESTIONS. Managing needs MANAGE_HOSTED_COMPUTERS. Opening one needs the computer to be yours, or one of the two ACCESS rights to widen it — and whether the caller may open THAT computer is answered by re-asking the same visibility question the nav menu was built from, rather than by a second rule that could disagree. A member who was never shown a computer cannot reach it by guessing an id. Confirming a password needs no right at all: it is how a member's own computer gets a login. THE ORG CHART IS FETCHED ONLY WHEN A RIGHT NEEDS IT, and in one query. ACCESS_SUBORDINATE_COMPUTERS reaches the whole subtree below a member, so the edges come back as pairs and the traversal downstream walks them over a visited set — managerId is a free-form profile field, not a validated tree, so a cycle is one reorganisation away. A member with no rights pays one call, which matters because the nav asks this on every page load. THE TOKEN FORMAT IS NOW SHARED, NOT COPIED. GuacamoleTokenCodec is extracted from GuacamoleJsonAuth because there are two gateways: desktop.<domain> for platform admins and computers.<domain> for tenants, with different signing keys and different connection models. Two copies of that format would be one implementation and one liability, and the mistake the copy would eventually make is the `expires` mistake — it reads like a link TTL and is actually the session's reconnect window, and getting it wrong empties a live user's connection list silently. The floor is enforced by both callers. HostedComputerSso is a sibling rather than a branch. That gateway offers three fixed targets and puts all of them in every token; this one has one connection per computer, named after the computer, with a one-time credential minted per launch — so the member's own Kamo password never travels in a token. The platform desktop has to keep a decryptable copy of an account password precisely so Guacamole can auto-login; this does not, and that is the improvement. PasswordChangeController now pushes the new password to the member's computers. This is the ONLY moment the plaintext exists — Kamo stores a hash and keeps no reversible copy anywhere — and the push is best-effort by construction: a failure must never fail the change the member asked for, and a sleeping computer is marked stale so its next launch asks for a confirmation. Nothing is queued, because a queued password is a stored password with a delay. Suite: 2755 tests, 5 failures, and all five are the same ones pristine origin/main produces — the UnauthenticatedEndpointRatchet on PromotionCodeInternalController (added by 9578ca0 and never added to the baseline) and four **************** NPEs on appliedJobStamp. Verified by running both in a clean worktree at origin/main. My own controller passes the ratchet: every handler resolves a session and answers 401 without one. Needs the hc-guac-sso secret to mint links; until it exists the launch path says so plainly rather than handing back a link that lands on Guacamole's generic error screen.
