#!/usr/bin/env bash # Fast, sandboxed selftests for the auth checkpoint (M19, corrected in #48). # # No Docker, no network, no real credentials. Mosaic-managed accounts live # under the data root (config-driven, like every other tool); the default # harness credential appears only as an explicit PI_AUTH_FILE fixture. # The suite asserts the core safety property: credential MATERIAL from # fixtures never reaches auth.sh or agent.sh output. set -uo pipefail cd "$(dirname "$0")/.." SANDBOX="$(mktemp -d)" trap 'rm -rf "$SANDBOX"' EXIT PASS=0 FAIL=0 # Status colors: terminal-only, NO_COLOR-respecting; plain when piped. if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then C_OK=$'\033[0;32m'; C_FAIL=$'\033[0;31m'; C_RESET=$'\033[0m' else C_OK=""; C_FAIL=""; C_RESET="" fi check() { if [ "$2" = "0" ]; then PASS=$((PASS+1)); echo "${C_OK}OK${C_RESET} $1"; else FAIL=$((FAIL+1)); echo "${C_FAIL}FAIL${C_RESET} $1"; fi } SECRET="SK-TEST-DO-NOT-PRINT-9f2b" TOKEN="ACCESS-TOKEN-SHOULD-NOT-PRINT" ENVVAL="ENVVAL-SHOULD-NOT-PRINT" mkdir -p "$SANDBOX/pi" "$SANDBOX/data/auth" "$SANDBOX/data/state" "$SANDBOX/empty" # mock system config: data root inside the sandbox (never the real one) printf '{"configVersion":1,"environment":"development","dataRoot":"%s","execution":{"backend":"docker","provider":"zai","model":"m","adapter":"mock"}}' "$SANDBOX/data" > "$SANDBOX/mock-config.json" CFG="MOSAIC_CONFIG=$SANDBOX/mock-config.json" # release pointer so agent.sh's ensure stays on the no-op fast path REL="$(tr -d '[:space:]' < RELEASE)" printf '{"pointerVersion":1,"release":"%s","imageTag":"mosaic-poc-agent:0.84.4-r%s","activatedAt":"2026-01-01T00:00:00Z"}\n' "$REL" "$REL" > "$SANDBOX/data/state/active.json" # fixtures: a default-harness credential and two mosaic-managed accounts printf '{"anthropic":{"type":"oauth","access":"%s"},"zai":{"type":"api_key","key":"%s"}}' "$TOKEN" "$SECRET" > "$SANDBOX/pi/auth.json" printf '{}' > "$SANDBOX/data/auth/work.json" printf '{}' > "$SANDBOX/data/auth/personal.json" chmod 600 "$SANDBOX/pi/auth.json" "$SANDBOX/data/auth/work.json" "$SANDBOX/data/auth/personal.json" # status: missing default harness credential -> exit 3, accounts still listed OUT="$(env PI_AUTH_FILE="$SANDBOX/pi/absent.json" $CFG scripts/auth.sh status 2>"$SANDBOX/err.txt")"; RC=$? [ "$RC" -eq 3 ] \ && printf '%s\n' "$OUT" | grep -q ' work (perms 600)' \ && check "status with missing harness credential exits 3 and still lists accounts" 0 || check "status with missing harness credential exits 3 and still lists accounts" 1 # status: happy path — harness providers + mosaic accounts, both labeled OUT="$(env PI_AUTH_FILE="$SANDBOX/pi/auth.json" $CFG scripts/auth.sh status 2>&1)"; RC=$? [ "$RC" -eq 0 ] \ && printf '%s\n' "$OUT" | grep -q 'read-only to the stack' \ && printf '%s\n' "$OUT" | grep -q ' anthropic type=oauth source=auth\.json' \ && printf '%s\n' "$OUT" | grep -q ' zai type=api_key source=auth\.json' \ && printf '%s\n' "$OUT" | grep -q 'mosaic-managed accounts' \ && check "status reports harness credential (read-only) + mosaic accounts" 0 || check "status reports harness credential (read-only) + mosaic accounts" 1 # the core safety property: fixture secret material never reaches output printf '%s\n' "$OUT" | grep -q "$SECRET" \ && check "api key material never reaches output" 1 || check "api key material never reaches output" 0 printf '%s\n' "$OUT" | grep -q "$TOKEN" \ && check "oauth token material never reaches output" 1 || check "oauth token material never reaches output" 0 # status: unparseable harness file -> exit 2 printf 'not json' > "$SANDBOX/pi/broken.json" env PI_AUTH_FILE="$SANDBOX/pi/broken.json" $CFG scripts/auth.sh status >/dev/null 2>&1; RC=$? [ "$RC" -eq 2 ] && check "unparseable credential file exits 2" 0 || check "unparseable credential file exits 2" 1 # status: symlinked harness file -> exit 4 ln -s "$SANDBOX/pi/auth.json" "$SANDBOX/pi/link.json" env PI_AUTH_FILE="$SANDBOX/pi/link.json" $CFG scripts/auth.sh status >/dev/null 2>&1; RC=$? [ "$RC" -eq 4 ] && check "symlinked credential file exits 4" 0 || check "symlinked credential file exits 4" 1 # status: env-side names informational — name shown, value never OUT="$(env FAKE_TEST_API_KEY=$ENVVAL PI_AUTH_FILE="$SANDBOX/pi/auth.json" $CFG scripts/auth.sh status 2>&1)" printf '%s\n' "$OUT" | grep -q "FAKE_TEST_API_KEY" \ && check "env-side credential names reported" 0 || check "env-side credential names reported" 1 printf '%s\n' "$OUT" | grep -q "$ENVVAL" \ && check "env var values never reach output" 1 || check "env var values never reach output" 0 # accounts: mosaic-managed, under the data root — a data root with no accounts dir is the default state: (none), exit 0, creates nothing printf '{"configVersion":1,"environment":"development","dataRoot":"%s","execution":{"backend":"docker","provider":"zai","model":"m","adapter":"mock"}}' "$SANDBOX/empty" > "$SANDBOX/mock-config-empty.json" OUT="$(env PI_AUTH_FILE="$SANDBOX/pi/auth.json" MOSAIC_CONFIG="$SANDBOX/mock-config-empty.json" scripts/auth.sh accounts 2>&1)"; RC=$? [ "$RC" -eq 0 ] && printf '%s\n' "$OUT" | grep -q '(none)' && [ ! -d "$SANDBOX/empty/auth" ] \ && check "accounts without an accounts dir reports none and creates nothing" 0 || check "accounts without an accounts dir reports none and creates nothing" 1 # accounts: listing with active marker OUT="$(env PI_AUTH_FILE="$SANDBOX/data/auth/work.json" $CFG scripts/auth.sh accounts 2>&1)"; RC=$? printf '%s\n' "$OUT" | grep -q '^ work (perms 600) <- active (PI_AUTH_FILE)$' \ && printf '%s\n' "$OUT" | grep -q '^ personal (perms 600)$' \ && check "accounts lists files and marks the active one" 0 || check "accounts lists files and marks the active one" 1 # accounts: loose perms flagged loudly printf '{}' > "$SANDBOX/data/auth/loose.json" chmod 644 "$SANDBOX/data/auth/loose.json" OUT="$(env $CFG scripts/auth.sh accounts 2>&1)" printf '%s\n' "$OUT" | grep -q 'loose (perms 644) \[not 0600 — agent --auth will refuse\]' \ && check "loose account perms flagged in listing" 0 || check "loose account perms flagged in listing" 1 # agent.sh --auth: missing mosaic-managed account file refuses (pre-container) env $CFG scripts/agent.sh researcher --auth nope /dev/null 2>"$SANDBOX/err.txt"; RC=$? [ "$RC" -eq 4 ] && grep -q "no mosaic-managed credential file for auth account 'nope'" "$SANDBOX/err.txt" \ && check "agent --auth with missing account file refuses (exit 4)" 0 || check "agent --auth with missing account file refuses (exit 4)" 1 # agent.sh --auth: loose perms refuse (0600 required, mirroring gitea-api.sh) env $CFG scripts/agent.sh researcher --auth loose /dev/null 2>"$SANDBOX/err.txt"; RC=$? [ "$RC" -eq 4 ] && grep -q "must be 0600 (got 644)" "$SANDBOX/err.txt" \ && check "agent --auth with non-0600 account file refuses" 0 || check "agent --auth with non-0600 account file refuses" 1 # agent.sh --auth: invalid account name refuses env $CFG scripts/agent.sh researcher --auth "bad~name" /dev/null 2>"$SANDBOX/err.txt"; RC=$? [ "$RC" -eq 4 ] && grep -q "invalid auth account name" "$SANDBOX/err.txt" \ && check "agent --auth with invalid account name refuses" 0 || check "agent --auth with invalid account name refuses" 1 # auth.sh: invalid config refuses (fail closed, like every tool) MOSAIC_CONFIG="$SANDBOX/no-such-config.json" scripts/auth.sh status >/dev/null 2>&1; RC=$? [ "$RC" -ne 0 ] && check "auth.sh without valid config refuses" 0 || check "auth.sh without valid config refuses" 1 echo echo "selftest: $PASS passed, $FAIL failed" [ "$FAIL" -eq 0 ]