What a permission model looks like from inside Notes
A browser-based desktop makes cloud boundaries visible in ordinary actions: a note that survives a reload but does not reach another browser, shared rooms, guest accounts, a lock screen the server enforces, and writes refused when another site sends them.

On this page
Nobody opening a note or a calculator thinks about a permission model, and for most of the desktop’s history nobody had to, because the hardware settled the question. The note was on a disk in the machine in front of you, and anyone who wanted to read it had to get to that machine. Move the same window into a browser tab backed by a cloud service and that answer goes away. The note is now a record on a server that holds everybody’s notes, and the window showing it is a page that whoever is using the browser can inspect and script. Whose note this is, who else may see it and what happens to it when you leave all have to be decided again, on the server, for every request.
Macalicious, which I developed with coding agents, is a good place to watch that happen. It is a reconstruction of the Catalina desktop in a browser tab, not a native operating system and nothing of Apple’s, and the apps’ server halves live in one Rust application on Ferrite, the application platform I own. There is one module per app behind /api/<app>/, more than 40 of them, all sharing one session layer and one storage layout. Because it is a desktop, each boundary turns up as something a person would do anyway: reloading a page, trying it on somebody else’s computer, joining a chat room, signing out, locking the screen.
A note, a reload and a second browser
Type a sentence into Notes and reload the tab. The sentence comes back, although nobody has signed in. The browser’s first request to the API was given a session, 32 random bytes in a cookie that the page’s own scripts can’t read and that lasts 400 days, the longest browsers honour. The server keeps only a record keyed by the token’s SHA-256, so neither the store nor any reply body holds a usable token, and a cookie whose record is missing, because it was forged or the state was wiped, is replaced instead of adopted. This browser now has something that behaves like a personal machine without anyone having made an account.
Now open the same desktop in another person’s browser. Your sentence isn’t there, and the way it isn’t there matters. No error appeared and no request was refused. Both browsers asked for the same address, /api/notes/data, and the second got what anyone gets before their first save, revision zero and no data. The address doesn’t name an owner. The server works that out from the cookie: the session has a principal, which is the session’s own id until someone signs in, and a module’s storage for the person is a prefix built from the app’s name and that principal, apps/notes/users/<principal>/. An app module is handed that storage already bound to the request’s principal, so there is no owner field in the request to tamper with and no ownership check for the module to forget. You would see the same thing in a second browser of your own, because until someone signs in the server knows a browser and not a person.
That is the line between what the page does and what the server has to do. The page draws windows, remembers what you were doing and asks for things, and it can’t be trusted with more than that, because anyone using the browser can change what it asks. A page could hide another person’s note. Only the server can decline to have read it.
One address for everyone has a consequence further out. A shared cache that stored the reply to /api/notes/data would hand one person’s note to whoever asked next, so API replies are not cacheable as public content, while the desktop’s built files, which are the same for everybody, are served from / as ordinary content.
Crossing the line on purpose
Messages and Chess want the opposite, since a room or a game is only worth having if the other person sees it. So each module has a second space, apps/<name>/shared/, whose keys are shared by every session of the app and belong to everyone, not to the person whose request wrote them. Sharing is therefore not a flag on a record that some code path might forget to check. It is a different place to put data, which a module has to ask for by name, so reading the module tells you which of its writes are private. Anything narrower than everyone using the app is the module’s own job. A video call, for instance, sits in the shared space under a random 128-bit id that only its two members are given, and every read checks membership.
A few apps have to reach past both spaces, and they are the ones you would expect. Console, Activity Monitor, About This Mac and System Information report on the application as a whole, and Spotlight and Storage Management read the person’s own documents from other apps. Those modules, along with the shared helpers for accounts and notifications, are the only ones meant to use the whole data store, and every other app keeps to its two spaces. Spotlight is the instructive one, because a search across your apps has to cross between applications without crossing between people, and the key layout is what makes that possible: a person’s keys name both the app and the person.
Becoming someone else
Signing in changes whose data a request reaches, because the principal becomes the account id, which is random and never derived from the name, and that is also what lets the same person find the same notes in another browser. The session itself doesn’t survive the change. Signing in, signing up, choosing Guest User and signing out each delete the old record and issue a new token in the same reply. Otherwise, anyone who had managed to plant a known token in your browser would find it upgraded to your account the moment you signed in.
Guest User shows how far this extends into lifecycle. A guest on a Mac is expected to leave nothing behind, and here a guest’s session and data last 24 hours at most. Deleting the data at sign-out isn’t enough, because a sign-out may not happen and a write may arrive late, so every write to a guest’s own space is stamped with an expiry and the store itself deletes it, whatever a log-out or the purge managed to do. Shared keys carry no such stamp, so whatever a guest wrote into a room stays there after the guest has gone, because the room was never theirs alone.
The lock screen shows most clearly that the picture isn’t the permission. If locking only covered the windows, the apps behind them would go on answering, so the lock is enforced on the server and no module answers until the password is given. The page’s contribution is timing. A read already on its way could reach the server before the lock request does, so the moment the person chooses Lock Screen, before that request is sent, the page sets a cookie saying a lock has been asked for. While the browser carries any such cookie no app answers, with a 423, whichever request arrives first. A lock that succeeds deletes its own cookie, and otherwise it expires after 20 seconds. The page can safely set this itself because it grants nothing: a browser carrying it can only do less.
Requests the person never made
All of this assumes the browser is doing what the person wanted, and another website can arrange for it not to. A page elsewhere can make a visitor’s browser send a write to the desktop’s API, so the server checks sec-fetch-site, a header the browser sets itself to describe how the requesting page relates to the target. Safe methods always pass. An unsafe method is refused when the browser says the request is cross-site, or same-site but from another origin, which is the case a neighbouring subdomain would produce. The session cookie’s SameSite=Lax is the second line, since a cross-site write doesn’t carry the session in the first place. A request with no such header is allowed: curl and the test harness don’t send one, and it isn’t something a page can forge. The application can’t make the stricter comparison of Origin against its own address, because the request envelope it receives has no verified host.
Where it stops
This design has selected tests behind it, which isn’t the same as a proof against every attack, and it has at least one gap the code states openly. If the gateway loses its reply to a browser’s first, cookieless request and redelivers it, the second delivery creates a second session, and whatever the first delivery wrote stays in the first session, which no cookie will ever reach again. That is accepted as a known limit for a demo, and it wouldn’t be acceptable for data that people would miss.
What I like about working through this on a desktop is that the expectations already exist. Nobody has to be taught what Lock Screen ought to mean, what should be left after a guest signs out, or whether a sentence you typed should turn up on a stranger’s computer. Each of those is a requirement on the server that a person can state without ever having heard the word principal.
Chris Chabot, November 2026