#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR=$(cd -- "$(dirname -- "$0")" && pwd) HOLDER="$SCRIPT_DIR/mosaic-tmux-holder.service" AGENT="$SCRIPT_DIR/mosaic-agent@.service" INTERACTION="$SCRIPT_DIR/mosaic-interaction-agent@.service" HOLDER_START="$SCRIPT_DIR/../../tools/fleet/start-tmux-holder.sh" START_AGENT="$SCRIPT_DIR/../../tools/fleet/start-agent-session.sh" fail() { echo "FAIL: $*" >&2 exit 1 } [ -f "$HOLDER" ] || fail "missing mosaic-tmux-holder.service" [ -f "$AGENT" ] || fail "missing mosaic-agent@.service" [ -f "$INTERACTION" ] || fail "missing mosaic-interaction-agent@.service" [ -x "$HOLDER_START" ] || fail "missing executable start-tmux-holder.sh" [ -x "$START_AGENT" ] || fail "missing executable start-agent-session.sh" grep -qF 'ExecStart=' "$HOLDER" || fail "holder has no ExecStart" grep -qF 'tmux -L' "$HOLDER" || fail "holder does not use named tmux socket" grep -qF '_holder' "$HOLDER" || fail "holder session is not explicit" grep -qF 'UnsetEnvironment=LD_PRELOAD BASH_ENV ENV' "$HOLDER" || \ fail "holder does not remove loader and shell-control variables" grep -qF 'ExecStart=/usr/bin/env -i HOME=%h PATH=/usr/bin:/bin MOSAIC_TMUX_SOCKET=mosaic-fleet MOSAIC_TMUX_HOLDER=_holder /bin/bash --noprofile --norc %h/.config/mosaic/tools/fleet/start-tmux-holder.sh' "$HOLDER" || \ fail "holder does not clear manager environment before starting tmux" grep -qF 'ExecStop=-/usr/bin/env -i HOME=%h PATH=/usr/bin:/bin MOSAIC_TMUX_SOCKET=mosaic-fleet /bin/bash --noprofile --norc -c' "$HOLDER" || \ fail "holder stop does not clear manager environment" if grep -qF -- '/bin/bash -lc' "$HOLDER"; then fail "holder must not start tmux through a login shell" fi grep -qF 'Requires=mosaic-tmux-holder.service' "$AGENT" || fail "agent does not require holder" grep -qF 'start-agent-session.sh' "$AGENT" || fail "agent unit does not call start-agent-session.sh" if grep -qE '^Environment(File)?=' "$AGENT" "$INTERACTION"; then fail "agent units must not accept ambient or projection environment before strict parsing" fi grep -qF 'UnsetEnvironment=LD_PRELOAD BASH_ENV ENV' "$AGENT" || \ fail "agent unit does not remove loader and shell-control variables" grep -qF 'UnsetEnvironment=LD_PRELOAD BASH_ENV ENV' "$INTERACTION" || \ fail "interaction unit does not remove loader and shell-control variables" grep -qF 'ExecStart=/usr/bin/env -i HOME=%h MOSAIC_AGENT_NAME=%i PATH=/usr/bin:/bin /bin/bash --noprofile --norc' "$AGENT" || \ fail "agent unit does not clear bootstrap environment before strict parsing" grep -qF 'start-agent-session.sh --stop %i' "$AGENT" || \ fail "agent stop does not use the validated exact-stop path" grep -qF 'Requires=mosaic-tmux-holder.service' "$INTERACTION" || fail "interaction service does not require holder" grep -qF 'ExecStart=/usr/bin/env -i HOME=%h MOSAIC_AGENT_NAME=%i PATH=/usr/bin:/bin /bin/bash --noprofile --norc' "$INTERACTION" || \ fail "interaction unit does not clear bootstrap environment before strict parsing" grep -qF 'start-interaction-service.sh %i' "$INTERACTION" || fail "interaction service does not use shared strict parsing" grep -qF 'start-agent-session.sh --stop %i' "$INTERACTION" || \ fail "interaction stop does not use the validated exact-stop path" if command -v systemd-analyze >/dev/null 2>&1; then systemd-analyze verify --user "$HOLDER" "$AGENT" "$INTERACTION" >/tmp/mosaic-fleet-systemd-verify.log 2>&1 || { cat /tmp/mosaic-fleet-systemd-verify.log >&2 fail "systemd-analyze verify failed" } fi # Real isolated socket regression: a preexisting server with an LD_PRELOAD # constructor marker must fail closed, while a fresh named server is created. if command -v tmux >/dev/null 2>&1 && command -v cc >/dev/null 2>&1; then TEST_ROOT=$(mktemp -d) TEST_SOCKET="mosaic-holder-test-$$" trap 'tmux -L "$TEST_SOCKET" kill-server >/dev/null 2>&1 || true; rm -rf "$TEST_ROOT"' EXIT MARKER="$TEST_ROOT/loader-marker" LIBRARY="$TEST_ROOT/marker.so" HOLDER_HOME="$TEST_ROOT/holder-home" mkdir -p "$HOLDER_HOME/.config/mosaic/fleet/run" chmod 700 "$HOLDER_HOME/.config" "$HOLDER_HOME/.config/mosaic" \ "$HOLDER_HOME/.config/mosaic/fleet" "$HOLDER_HOME/.config/mosaic/fleet/run" printf '123e4567-e89b-12d3-a456-426614174000\n' > \ "$HOLDER_HOME/.config/mosaic/fleet/run/holder-owner" chmod 600 "$HOLDER_HOME/.config/mosaic/fleet/run/holder-owner" cat > "$TEST_ROOT/marker.c" <<'EOF' #include #include #include __attribute__((constructor)) static void mark_loader(void) { const char *path = getenv("MOSAIC_LOADER_MARKER"); if (path != NULL) { int fd = open(path, O_WRONLY | O_CREAT | O_APPEND, 0600); if (fd >= 0) { write(fd, "loaded\\n", 7); close(fd); } } } EOF cc -shared -fPIC -o "$LIBRARY" "$TEST_ROOT/marker.c" MOSAIC_LOADER_MARKER="$MARKER" LD_PRELOAD="$LIBRARY" \ tmux -L "$TEST_SOCKET" new-session -d -s _holder 'sleep 60' [ -s "$MARKER" ] || fail "contaminated fixture did not execute loader constructor" server_pid=$(tmux -L "$TEST_SOCKET" display-message -p '#{pid}') : > "$MARKER" if /usr/bin/env -i HOME="$HOLDER_HOME" PATH=/usr/bin:/bin \ MOSAIC_TMUX_SOCKET="$TEST_SOCKET" MOSAIC_TMUX_HOLDER=_holder "$HOLDER_START" \ >"$TEST_ROOT/holder.out" 2>&1; then fail "holder adopted contaminated named server" fi grep -qF 'global environment does not match the owned-server contract' "$TEST_ROOT/holder.out" || \ fail "holder did not report contaminated server environment" [ "$(tmux -L "$TEST_SOCKET" display-message -p '#{pid}')" = "$server_pid" ] || \ fail "holder replaced a contaminated server instead of failing closed" [ ! -s "$MARKER" ] || fail "holder execution triggered a contaminated loader" # Agent validation must reject the same unmanaged server without cleaning its # global environment or adding a managed session. AGENT_HOME="$HOLDER_HOME/.config/mosaic" AGENT_NAME=loader-safe AGENT_WORKDIR="$AGENT_HOME/work" AGENT_BIN="$TEST_ROOT/agent-bin" mkdir -p "$AGENT_HOME/fleet/agents" "$AGENT_WORKDIR" "$AGENT_BIN" chmod 700 "$AGENT_HOME/fleet/agents" cat > "$AGENT_HOME/fleet/agents/$AGENT_NAME.env.generated" < "$AGENT_HOME/fleet/agents/$AGENT_NAME.env.local" chmod 600 "$AGENT_HOME/fleet/agents/$AGENT_NAME.env.generated" \ "$AGENT_HOME/fleet/agents/$AGENT_NAME.env.local" cat > "$AGENT_BIN/mosaic" <<'EOF' #!/bin/sh sleep 30 EOF chmod 700 "$AGENT_BIN/mosaic" server_environment_before=$(tmux -L "$TEST_SOCKET" show-environment -g | sort) server_sessions_before=$(tmux -L "$TEST_SOCKET" list-sessions | sort) if /usr/bin/env -i HOME="$HOLDER_HOME" PATH=/usr/bin:/bin MOSAIC_HOME="$AGENT_HOME" \ "$START_AGENT" "$AGENT_NAME" >"$TEST_ROOT/agent.out" 2>&1; then fail "agent launcher adopted contaminated named server" fi [ "$(tmux -L "$TEST_SOCKET" display-message -p '#{pid}')" = "$server_pid" ] || \ fail "agent launcher changed unmanaged server PID" [ "$(tmux -L "$TEST_SOCKET" show-environment -g | sort)" = "$server_environment_before" ] || \ fail "agent launcher changed unmanaged global environment" [ "$(tmux -L "$TEST_SOCKET" list-sessions | sort)" = "$server_sessions_before" ] || \ fail "agent launcher changed unmanaged sessions" tmux -L "$TEST_SOCKET" kill-server /usr/bin/env -i HOME="$HOLDER_HOME" PATH=/usr/bin:/bin \ MOSAIC_TMUX_SOCKET="$TEST_SOCKET" MOSAIC_TMUX_HOLDER=_holder "$HOLDER_START" tmux -L "$TEST_SOCKET" has-session -t '=_holder:0.0' || fail "fresh holder was not created" if tmux -L "$TEST_SOCKET" show-environment -g LD_PRELOAD 2>/dev/null | grep -q '^LD_PRELOAD='; then fail "fresh holder retained LD_PRELOAD" fi /usr/bin/env -i HOME="$HOLDER_HOME" PATH=/usr/bin:/bin MOSAIC_HOME="$AGENT_HOME" \ "$START_AGENT" "$AGENT_NAME" tmux -L "$TEST_SOCKET" has-session -t "=$AGENT_NAME:0.0" || \ fail "agent did not launch on a valid owned server" tmux -L "$TEST_SOCKET" kill-server trap - EXIT rm -rf "$TEST_ROOT" fi echo "ok - fleet systemd unit templates"