the notify sidecar stops trusting whoever reaches it
to send one message to four accounts an hour ago i found the sidecar's ephemeral port and POSTed to it directly, once per user, with no authentication of any kind. that worked, which was the problem: the same door was open to anything else on the machine, including a commander with a shell. two changes. POST /api/notify/broadcast, owner only via `ownerOnly` on the permission — the supported way to say one thing to everyone. teams will narrow it later; today the choice is everyone or one person. and the sidecar now requires a secret on everything under /_officer/. it is generated per boot, held in memory, announced to the platform over the registration socket beside the port, and never written to disk. X-Officer-User could not carry this on its own, and my first attempt got that wrong: it says WHO is asking, and a caller on loopback can write it itself. only the secret says the PLATFORM is asking. so the check covers the whole /_officer/ surface rather than just the send route — /_officer/ devices takes the same header to decide whose phones are listed and deleted. what it bounds, honestly: a MEMBER's commander runs as its own Linux user and cannot read this process, so for every account but the owner's this is kernel-enforced. the owner's own commander runs as the owner, same as the platform, and is not bounded by this and cannot be — the same reason `execution` permissions are never grantable. the owner's decision, and this is a real improvement rather than a boundary. `bun run notify:test` posts to that port and will now be refused. it is a developer CLI on a box where the platform is running; left to fail loudly rather than given a way in. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
This commit is contained in:
@@ -219,6 +219,14 @@ const CORE_REGISTRY: Permission[] = [
|
||||
// nothing, so a read-level member could list their phones but not register or delete one — the exact
|
||||
// opposite of the line above. Invisible in testing because the owner bypasses every check.
|
||||
personal: ['/_officer/devices', '/_officer/subscriptions'],
|
||||
/*
|
||||
Broadcast is the one part of a core permission that is not the caller's own.
|
||||
|
||||
Everything else here acts on the account asking — its devices, its notifications — which is what
|
||||
made `core` right. Sending to EVERY account is administration, so it needs the owner even though
|
||||
the permission around it is not deniable.
|
||||
*/
|
||||
ownerOnly: ['/broadcast'],
|
||||
},
|
||||
// `vpn` (POST /api/vpn/enroll) was here until 2026-08-14 — the one member-grantable piece of headscale,
|
||||
// minting a pre-auth key bound to the caller. Deleted because it had no caller anywhere: the standalone
|
||||
|
||||
Reference in New Issue
Block a user