Files
kandev/docs/decisions/2026-07-31-system-service-user-continuity.md
T
2026-08-01 11:19:24 +01:00

103 lines
5.5 KiB
Markdown

# ADR-2026-07-31-system-service-user-continuity: Preserve System Service Identity Across Reinstallation
**Status:** accepted
**Date:** 2026-07-31
**Area:** backend, cli, security, operations
## Context
`kandev service install --system` currently derives its service account from the process that runs
the installer. A command invoked through `sudo` normally sees the non-root `SUDO_USER`, while the
same command invoked from a root login resolves to root. Re-running installation after a package
upgrade can therefore silently rewrite an existing non-root service as root while leaving
`KANDEV_HOME_DIR` and its managed repositories owned by the original account.
Git correctly rejects a repository owned by another account when the process runs with greater or
different authority. The new managed-checkout origin reconciliation made that latent mismatch
visible during every launch and resume: `git remote set-url` exits 128 before an agent starts. The
same identity drift can affect database access, secrets, generated files, executor credentials, and
any future operation, so treating only the Git symptom would leave the underlying operational and
security boundary broken.
Kandev also writes `<KANDEV_HOME_DIR>/service/install.json`. That file is intentionally readable and
writable by the service account. A privileged installer cannot trust it as the authority for a
future run-as account: a compromised service could edit the file to request a more privileged
identity before an administrator reruns installation.
## Decision
System-service identity is durable installation state. `kandev service install --system` resolves
the target account in this order:
1. An explicit `--run-as <user>` supplied for this installation.
2. The account in an existing root-controlled, Kandev-managed systemd unit or launchd plist.
3. A non-root `SUDO_USER` for a first installation.
4. Otherwise no implicit account: installation fails and requires `--run-as`, including the
explicit `--run-as root` choice.
Only an existing definition carrying Kandev's managed-service marker participates in preservation.
The installer parses the effective account from the root-controlled unit or plist, validates that
the account exists, and rejects malformed or contradictory managed definitions before writing.
Service-owned install metadata remains useful for status and update compatibility checks, but it
may only corroborate the selected account; it cannot select or elevate it.
Before replacing or restarting a system service, the installer validates the system Kandev home:
- If the home does not exist, installation fails with pre-create and ownership guidance. Kandev
does not create a privileged system data root implicitly.
- If it exists, its owner UID must match the selected account's UID.
- A mismatch fails before the service definition is changed or restarted and reports the current
owner, selected account, and explicit recovery choices.
Kandev does not recursively chown an existing data tree, add `safe.directory=*`, or suppress Git's
ownership check. An operator intentionally migrating service identity must explicitly select the
new account and reconcile the home ownership according to local policy before installation can
complete. This keeps destructive or privilege-changing filesystem operations outside an otherwise
routine package update.
`--run-as` is valid only with `service install --system`. User services continue to run as the
calling user and do not expose an account override.
## Consequences
- Reinstalling after a Homebrew, npm, or bundle upgrade cannot silently change an established
system service from a non-root account to root merely because the command was run from a root
shell.
- First-time root-shell installs become explicit. Operators who genuinely want a root service use
`--run-as root` and receive a reviewable command history.
- Existing installations that already run as root remain root when their managed definition is
valid; continuity does not force an unrelated migration.
- A service/data ownership mismatch is detected before restart, instead of appearing later as an
opaque database, Git, or secret-store failure.
- Service-owned metadata cannot be edited to cause privilege escalation on the next privileged
install.
- Deliberate account migration requires an additional operator-managed ownership step and may need
ACL-specific handling on unusual deployments.
## Alternatives Considered
### Continue deriving the account from the current shell
Rejected. The account running a package update is not durable service configuration, and root
logins lack the `SUDO_USER` signal that happened to make earlier installs work.
### Trust `service/install.json` as the preserved identity
Rejected. The service account owns that file. Letting it choose the account written into a
root-controlled service definition would create a privilege-escalation path.
### Automatically chown the Kandev home when the account changes
Rejected. Recursive ownership changes are destructive, can cross operator-managed mount or
symlink boundaries, discard intended ACL semantics, and turn a typo into a broad data mutation.
### Add a broad Git `safe.directory` exception
Rejected. That would mask service identity drift, weaken Git's repository-ownership protection,
and leave non-Git files under the wrong operational authority.
### Always run system services as root
Rejected. It expands task and integration authority unnecessarily and breaks existing non-root
installations whose data and credentials belong to their established service account.