0093: Team policies come signed from shortleash.dev

On this page

Status: accepted. Amends 0085.

Context

Teams want to decide what Short Leash may do on their computers: that shared sites end after a few hours and ask for a password, that Pair or local AI is off, that nobody runs a database older than a version they support. Polar knows a key is valid, but not which team it's on or what that team wants. 0085 chose to run no server of Short Leash's own and made the license check Pro's only call home; shortleash.dev now has accounts, where a team's owners and admins manage its seats, so it's the natural place to set those rules.

Decision

  • Where it's set. On shortleash.dev, a team's owners and admins edit a policy on its Policies page. Every save is a new version with a history. A policy is enforced, or only reported (the website shows who would be stopped, nothing changes on the computers).
  • What it can set. Constraints on what Short Leash already does, nothing new: the lowest Short Leash allowed; sharing on or off, quick tunnels or only your own domain, the longest a share may last, the protection a shared site needs (password, IP list or either) and whether it may be indexed; features off (Pair, Webhooks, Local AI, Calls, Mail); which services may run and their lowest versions; the lowest version of each runtime.
  • How it arrives. leashd posts the license key, its Polar activation, this computer's hash (the same SHA-256 Polar has), the app version, the policy version it applies and what it runs (runtime versions with a count of sites, services with versions) to https://shortleash.dev/api/v1/license/policy: once a key is activated, a minute after start and every 15 minutes while it runs. The website checks the key against its copy of Polar's keys (kept as SHA-256 hashes), that the activation is that key's and the computer the one it was made on. It answers with the team's policy, or for a key not on a team an empty one, signed with Ed25519 and naming the activation and computer.
  • What leashd trusts. Only an answer whose signature verifies against a public key the build ships (config::defaults::POLICY_PUBLIC_KEYS, by key name, so keys rotate a release ahead) and that names this activation and this computer. A version older than the one it applies is ignored. Builds that may use Polar's sandbox also take LEASH_POLICY_URL and LEASH_POLICY_PUBLIC_KEY, for testing against a local website; released builds ignore both.
  • Where it's kept. The last signed answer in secrets/policy.json (0600), checked again when read. A policy doesn't lapse offline: the last one holds until a new one arrives, the key is deactivated, or the website says the key isn't on a team.
  • Enforcement. Each rule is checked where the thing it constrains starts: a feature where Pro is checked, a share before its tunnel starts (and its expiry is the shorter of the user's and the policy's; indexing off adds noindex), a service before it's added or started, a runtime version before it's installed. When a stricter policy arrives, shares, services, webhook tunnels and Pair calls that break it stop. The refusal names the team: "Pair is turned off by Studio Teal's policy." Each screen a rule changes says so before anyone tries ("Set by Studio Teal: Pair is off."), and Settings › License lists them all.
  • Pinning a computer to its team. IT can deploy the team's id as TeamId, where only an administrator writes: on macOS the managed preferences a device-management profile installs for dev.shortleash.app (/Library/Managed Preferences/), on Windows HKLM\SOFTWARE\Policies\Short Leash (Group Policy). leashd reads it at start. On a pinned computer, Short Leash Pro and sharing work only once a policy from that team has arrived for this activation: with no key, a personal key or another team's, or before the first answer, they're held, and Settings › License and each Pro screen say why. Without a pin nothing waits: a computer that never fetched a policy follows none until it does, so individual Pro users aren't held back while shortleash.dev is unreachable.

Consequences

  • Invariant 16 changes: besides the license check with Polar, leashd asks shortleash.dev for the team policy, and only while a key is activated. The privacy policy says what it sends.
  • shortleash.dev becomes a service the app depends on for Teams. If it's down, the last policy holds and nothing else changes; a computer that never fetched one follows none until it does, unless it's pinned, when Pro and sharing wait for its team's policy.
  • The website's signing key is a new secret to keep and rotate. A build trusts only the keys it shipped with, so rotating means adding the next key a release before signing with it.
  • This is cooperative enforcement on the team's own computers. Someone with administrator rights on their computer who sets out to defeat it can (patch the app, block the website, activate a personal key); the Policies page says so. Pinning a computer to its team closes the personal-key gap on computers IT manages; on others it stays open.