- Status: done
- Date: 2026-06-09
- Specs touched:
APP_ISOLATION.md
Open WebUI installed but its container crash-looped with sqlite3.OperationalError: unable to open database file. The migration could not create its SQLite DB under the /app/backend/data bind. Root cause: a Tier-3 container runs with cap_drop: [ALL], which strips CAP_DAC_OVERRIDE, so it can only write its private data/ dir when it runs as that dir's owner — root-ness alone is not enough. The brain pinned user: only for apps that declare folders; a folderless app (Open WebUI) emitted no user: and ran as the image default (root). That happens to work only when the brain itself is root and thus owns the data/ dir it created (production) — it breaks in the native inner-loop brain, which runs as the unprivileged dev user (UID 1000), so the dir is 1000-owned and the root-UID container cannot write it.
This was a real divergence from APP_ISOLATION.md # Owner-scoped instances / # User content, which says every instance runs as a resolved user: with file ownership lined up natively — with no folderless carve-out.
- Every instance now resolves a runtime identity (
lifecycle.go, install step 6).isois always built: folder apps keep the owner UID/GID (personal) or malmo-app identity (household) and their folder binds; a folderless app runs as the brain's own effective UID/GID (os.Geteuid()/os.Getegid()) — the creator and owner of thedata/dirwriteInstanceDirjust made. Production brain → root (behavior-identical to before); native dev brain → the dev user, so the bind stays writable. writeOverridestampsuser:on every instance, not just folder apps (theisoit receives is now always non-nil; the empty-mountspath makes folder binds /group_addno-ops). Theisolationstruct doc was updated — it is no longer nil for folderless apps.data/is chowned to the resolved identity beforecompose up. No-op for folderless apps (already euid-owned); a real chown for folder apps under the production brain — which also closes a latent prod gap where a folder app writing private state into root-owneddata/could not write it. The chown is privilege-aware: under the production brain (euid 0) a failure is a hard install error; under the unprivileged native dev brain it cannot chown to a host-agent-assigned UID it does not own, so that case is downgraded to aslog.Warnand the install proceeds (folder-apps-in-dev were already not faithfully runnable — no regression; folderless apps, the common dev path, are unaffected).
catalog/open-webui/compose.ymlheader comment corrected — it no longer claims "runs as root, nouser:override needed"; it now documents that malmo pinsuser:to thedata/-owning identity and that Open WebUI tolerates a non-root runtime user given a writabledata/+ injectedWEBUI_SECRET_KEY.APP_ISOLATION.md# Volumes gained the explicit rule: every instance runs as a resolveduser:and itsdata/dir is owned by that UID, with the folderless-app identity spelled out and thecap_drop: [ALL]/CAP_DAC_OVERRIDEreasoning.
TestInstallFolders_FolderlessSkipsIdentity→TestInstallFolders_FolderlessRunsAsBrainIdentity: asserts a folderless override now carriesuser: <euid>:<egid>, binds no folders (volumes:/group_add:absent), and still makes no host identity calls. Full Go suite (make test-nopam) green.
- Dev-fake UID fidelity for folder apps. The fake host-agent's
resolveHome/wellKnownIdentityreturn UIDs in the [3000,3999] range, which the unprivileged native brain can neither chown to nor match — so folder apps are still not faithfully runnable in the inner loop (the chown is warned-and-skipped). Making the fake report the brain's own euid/egid would close this; deferred here to keep the change focused and avoid test-determinism churn. - Production identity for folderless apps is root. Pinning a folderless app to the brain euid means root in production. A non-root default sandbox identity for folderless apps (a dedicated low-privilege service UID) is a larger security decision, intentionally out of scope.
- Door-2 folder apps run as the brain's euid in production (root). A Door-2 custom app that declares folders goes through the same
installpath and resolves folder-app UIDs correctly, but itsdata/dir is still owned by root (the brain euid) in production rather than by the installing user. This is the same identity gap as for folderless apps; a dedicated per-app service UID for Door-2 is the right fix but is out of scope here. - Existing broken instances are not retroactively healed. This fix is install-time; an already-installed instance whose
data/is owner-mismatched (e.g. the Open WebUI that triggered this) needs a reinstall or a one-shotchown.