SendOff — a file lands in a real directory in the recipient's own tree
Implements docs/sendoff.md's v2: a folder convention, one endpoint and a push. No staging area, no transfer table, no ack, nothing to expire. Every account already has a home, a file browser over it and a Files app on the phone, so a delivered file is an ordinary file from the moment it arrives. `Documents/SendOff/<machine>/` for something you sent yourself, and `Documents/SendOff/<sender>/` for another account's files. The home is the one that account actually browses — the owner's real login home, a member's managed home — because the two are different directories and writing to the wrong one makes a delivery invisible rather than wrong. CONSENT IS THE RECIPIENT'S, held as a row per pair. Not a role grant: a grant answers "may this role send files", and the person who should decide whether somebody may write into your home is you. Absence denies, and a sender who was not allowed does not see the recipient in their picker at all — the endpoint answers 404 for "no such account" and for "did not allow you" alike, so it cannot be used to enumerate accounts. The owner bypasses this as a SENDER only; receiving still needs their say-so. The sender's folder name comes from the token and never from the payload, so it cannot lie about who put a file there. Names are sanitised to one path segment and filenames to their basename — verified: `../../../../etc` becomes a literal folder, and a filename of `../../../../tmp/pwned.txt` writes `pwned.txt` inside the target. Collisions suffix ` (1)`; nothing is ever overwritten, because the sender cannot see the directory and would have no way to know. The push carries the folder name and a file count, and the catalogue renders "3 file(s) from andre" on the device. That relaxes "content does not travel" by a folder name and an integer — the owner's call, on the grounds that the file never leaves the tailnet and a notification nobody can act on is not worth sending. A filename would be content; whose folder it is, is addressing. `kind: 'app'` rather than `confined` even though it writes into a home: the path is one fixed shape chosen by the server from the caller's token, so there is nothing to traverse and nothing to read back. Requiring a Linux user would mean an account without one could not be sent a file, which is most of the point. Backend and verified end to end. The web send form is next. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -347,6 +347,27 @@ const CORE_REGISTRY: Permission[] = [
|
||||
ws: ['minesweeper'],
|
||||
routes: ['/minesweeper'],
|
||||
},
|
||||
/*
|
||||
SendOff is `app`, not `confined`, and the distinction is the point.
|
||||
|
||||
It writes into a home directory, which normally means `confined` — but it writes exactly one shape of
|
||||
path, `Documents/SendOff/<sender>/`, chosen by the server from the caller's token. There is no path in
|
||||
the request to contain, nothing to traverse out of, and no way to read anything back. So the Linux-user
|
||||
requirement that `confined` exists to enforce buys nothing here, and requiring it would mean an account
|
||||
with no OS user could not be sent a file — which is most of the point of the feature.
|
||||
|
||||
WHO may send to whom is not decided here. It is a row per pair held by the recipient, because the
|
||||
person who should decide whether somebody may put a file in your home is you, not whoever administers
|
||||
roles. This grant only says the feature is reachable at all.
|
||||
*/
|
||||
{
|
||||
key: 'sendoff',
|
||||
label: 'SendOff',
|
||||
description: 'Send files to your own machines, and to accounts that have allowed you',
|
||||
kind: 'app',
|
||||
api: ['/sendoff'],
|
||||
routes: ['/sendoff'],
|
||||
},
|
||||
{
|
||||
key: 'items',
|
||||
label: 'Permission authoring',
|
||||
|
||||
Reference in New Issue
Block a user