160 lines
8.1 KiB
Bash
Executable File
160 lines
8.1 KiB
Bash
Executable File
#!/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 <fcntl.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
__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" <<EOF
|
|
MOSAIC_AGENT_NAME=$AGENT_NAME
|
|
MOSAIC_AGENT_CLASS=code
|
|
MOSAIC_AGENT_RUNTIME=pi
|
|
MOSAIC_AGENT_MODEL=
|
|
MOSAIC_AGENT_REASONING=
|
|
MOSAIC_AGENT_TOOL_POLICY=code
|
|
MOSAIC_AGENT_WORKDIR=$AGENT_WORKDIR
|
|
MOSAIC_TMUX_SOCKET=$TEST_SOCKET
|
|
EOF
|
|
printf 'MOSAIC_RUNTIME_BIN=%s\n' "$AGENT_BIN" > "$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"
|