Domain delegation

On a multi-site instance, each domain can name who may manage it (SM154/SM155/SM165). Access lives on the domain, with two separate controls: an allow-list of groups (additive) and a set of user locks (subtractive). The Domains page shows a domain's whole access picture in one place and is the only place access is edited.

Configuration

Two per-domain keys in lazysite.conf, editable from the Domains page, the control API (domain-set) or the lazysite-domains CLI - all gated by the manage_domains capability:

alias.clienta.com.content_root:   sites/clienta
alias.clienta.com.allowed_groups: clienta-editors, agency-leads
alias.clienta.com.locked_users:   alice, clienta-bot
allowed_groups
The groups permitted to manage this domain. A user is allowed a domain when they belong to one of its allowed groups. A non-default domain with an empty allow-list is operator-only.
locked_users
Accounts confined to this domain. A locked user reaches only the domain(s) they are locked to, intersected with what their groups allow - a lock narrows, never grants.

The effective set resolves to a list of content-root scopes and feeds the same confinement checks on every channel: manager UI, control-API token, MCP, WebDAV and the processor's file-browser rooting. A user with no lock and no allow-entry is a general editor, as before. A locked user whose effective set is empty is confined to nothing - never silently unconfined.

The sub-user ceiling

A created account can never out-reach its creator: effective scope is intersected up the created_by chain at resolve time, and disjoint scopes resolve to deny-all. An MCP agent minted by a delegated user is therefore confined to a subset of that user's own domains by construction.

Pre-DNS preview

The domain-preview action renders a registered domain's home page server-side under its own Host, with auth cleared (an anonymous public render), so a team can prepare and debug a new domain before DNS or TLS is live. Only registered hosts can be previewed.

The Domains panel checks

domain-check verifies a registered domain is live on this install, running four ordered checks the panel renders in order:

  • DNS resolves - the name has an address
  • Points to this server - a resolved address matches this install's public IP (discovered from canonical_ip, or by resolving the install's own domain; indeterminate behind an unknown proxy/NAT)
  • HTTPS certificate valid - a trusted certificate covers the host; a trusted certificate that does not cover the host is reported as a coverage gap, listing the names it does cover
  • Serves this lazysite - an HTTPS request lands back on this instance, verified by the /.well-known/lazysite-instance.json marker

The outbound probes are SSRF-guarded: the check refuses to connect when any resolved address is non-public (loopback, RFC1918, link-local and the metadata endpoint, CGNAT, IPv6 ULA), keyed on the resolved IPs so DNS rebinding is closed too, and only operator-registered hosts may be probed at all.

Notes

  • Adding a domain validates the host as a strict DNS name and the content root against traversal and the reserved lazysite/ area; values are rejected if they could smuggle a second conf line
  • Removing a domain leaves its content directory in place by default; purge removes it, confined to the docroot
  • The Groups and Account pages mirror domain access read-only - the earlier group-side binding (dav_scope/home_domain, SM155) was replaced by this domain-owned model
  • DNS, the web-server alias and TLS are preconditions handled by the operator or panel; lazysite never touches them