diff --git a/packages/mosaic/framework/install.sh b/packages/mosaic/framework/install.sh index 7d262a24..a8a81a0a 100755 --- a/packages/mosaic/framework/install.sh +++ b/packages/mosaic/framework/install.sh @@ -35,6 +35,18 @@ SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" TARGET_DIR="${MOSAIC_HOME:-$HOME/.config/mosaic}" INSTALL_MODE="${MOSAIC_INSTALL_MODE:-prompt}" +# Normalize the ambient umask so directory modes are a property of the installer +# and not of whatever shell invoked it (#1236). Debian/Ubuntu ship umask 002, so +# every `mkdir -p` below yielded 0775 — and the fleet env boundary rejects any +# managed directory with `mode & 0o022`, which made `mosaic fleet init --write` +# impossible on a stock install of those distros. Fedora/RHEL ship 022 and did +# not trip it, so the product worked or did not depending on the operator's +# login shell. 022 is what this script already assumes it produces: see the +# umask note in make_durable_snapshot, which restores to the ambient value +# precisely so "every later sync copy and new framework dir" gets 0644/0755. +# Now that value is 022 rather than whatever was inherited. +umask 022 + # Deliberately parsed from "$@" (a real, explicit, per-invocation argument) — # never an environment variable — so this opt-out can never sit silently # inherited in a shell profile. See #869 Point-1 C2. @@ -696,6 +708,48 @@ sync_framework mkdir -p "$TARGET_DIR/memory" mkdir -p "$TARGET_DIR/credentials" +# Three directories must be 0700, not merely not-group-writable (#1236). +# The fleet code guards them with two different masks in two different +# languages, and the strict one wins: +# +# assertPrivateManagedDirectory (fleet-reconciler.js, `mode & 0o077`) +# -> MOSAIC_HOME and MOSAIC_HOME/fleet, checked before the roster lock is +# taken, so every mutating `mosaic fleet` command dies at 0755. +# assert_private_directory (tools/fleet/start-agent-session.sh, `mode & 077`) +# -> MOSAIC_HOME/fleet/agents, checked before a pane is ever spawned. +# +# Their laxer siblings (`mode & 0o022`) accept 0755, which is why normalizing +# the umask above is necessary and not sufficient — a correct umask-022 install +# still produces 0755 and still cannot run `mosaic fleet init --write`. Say the +# strict modes outright rather than inferring them from a umask. +# +# Only these. The rest of the tree is content, stays 0755, and is only ever +# reached by the 0o022 checks, which 0755 satisfies. +chmod 700 "$TARGET_DIR" 2>/dev/null || \ + warn "Could not set 0700 on $TARGET_DIR — 'mosaic fleet' mutations will fail as unsafe-permissions." +if [[ -d "$TARGET_DIR/fleet" ]]; then + chmod 700 "$TARGET_DIR/fleet" 2>/dev/null || \ + warn "Could not set 0700 on $TARGET_DIR/fleet — 'mosaic fleet' mutations will fail as unsafe-permissions." +fi +# fleet/agents does not exist on a first install — the CLI creates it 0700 on +# demand. It is chmod'd here for the UPGRADE case: a tree built under umask 002 +# has it at 0775, and the repair sweep below cannot rescue it, because stripping +# group/other write from 0755 leaves 0750 and `mode & 077` is still non-zero. +if [[ -d "$TARGET_DIR/fleet/agents" ]]; then + chmod 700 "$TARGET_DIR/fleet/agents" 2>/dev/null || \ + warn "Could not set 0700 on $TARGET_DIR/fleet/agents — agent sessions will fail to start as unsafe-permissions." +fi +# credentials/ holds secrets and was never meant to be group-readable either. +chmod 700 "$TARGET_DIR/credentials" 2>/dev/null || true + +# Repair an existing tree. The umask above only governs directories this run +# creates, so a host installed under umask 002 before this fix keeps its 0775 +# dirs through every upgrade and stays broken. Strips group/other WRITE only — +# never read or execute — so it can repair the boundary violation without +# changing who can traverse or read anything. Scoped to directories: file modes +# are the manifest's business, not this fix's. +find "$TARGET_DIR" -type d -perm /022 -exec chmod go-w {} + 2>/dev/null || true + # Reconcile contract files from defaults/ into the framework root: framework-owned # files (CONSTITUTION/AGENTS/STANDARDS) are overwritten every upgrade (a divergent # copy is backed up once); user-seeded files (TOOLS) are written on first install only.