Files
stack/scripts/test-auth.sh
T
jason.woltje 073bbfdb6a feat(auth): M19 harness auth tooling — auth.sh checkpoint + per-launch account injection (#47)
Investigation (pi 0.84.4 docs + host auth.json metadata, values never
read): provider stacking is native (one auth.json keyed by provider;
resolution --api-key > auth.json > env > models.json; OAuth auto-refresh).
Multi-account per provider is NOT native -> named-file design:
auth.<account>.json + per-launch injection.

- scripts/auth.sh: status (provider names, credential types, perms,
  env-side names informational — never credential material) and accounts
  (named files, active marker). Exit codes per convention: 3 missing for
  a read, 2 unparseable, 4 file/environment (symlinks refuse).
- scripts/agent.sh --auth <account>: resolves auth.<account>.json and
  exports PI_AUTH_FILE (the existing compose read-only mount source — no
  new plumbing); missing/invalid account refuses pre-container.
- scripts/test-auth.sh: 13 no-Docker cases; core assertion is the safety
  property itself — fixture key/token/env VALUES never reach output.
- Docs: TOOLS.md Auth section, AGENTS.md command surface + suites.

Headless task runs keep the default credential (worker auth selection is
a separate policy decision). Real-host smoke: anthropic/openai-codex
oauth + zai api_key reported, perms 600, no named accounts yet.

Suites 24/90/14/17/13 + verify green. Agreed sequence M16-M19 complete;
M20 owner-gated.
2026-09-03 19:58:50 -05:00

98 lines
5.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# Fast, sandboxed selftests for the auth checkpoint (M19).
#
# No Docker, no network, no real credentials: every case runs against
# fixture files via PI_AUTH_FILE. 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/creds" "$SANDBOX/empty"
printf '{"anthropic":{"type":"oauth","access":"%s"},"zai":{"type":"api_key","key":"%s"}}' "$TOKEN" "$SECRET" > "$SANDBOX/creds/auth.json"
printf '{}' > "$SANDBOX/creds/auth.work.json"
printf '{}' > "$SANDBOX/creds/auth.personal.json"
# status: missing credential file -> exit 3 (config missing for a read)
OUT="$(PI_AUTH_FILE="$SANDBOX/creds/absent.json" scripts/auth.sh status 2>"$SANDBOX/err.txt")"; RC=$?
[ "$RC" -eq 3 ] && grep -q "credential file not found" "$SANDBOX/err.txt" \
&& check "status on missing credential file exits 3" 0 || check "status on missing credential file exits 3" 1
# status: happy path — providers sorted with credential types
OUT="$(PI_AUTH_FILE="$SANDBOX/creds/auth.json" scripts/auth.sh status 2>&1)"; RC=$?
[ "$RC" -eq 0 ] \
&& 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' \
&& check "status lists providers with credential types" 0 || check "status lists providers with credential types" 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 file -> exit 2
printf 'not json' > "$SANDBOX/creds/broken.json"
PI_AUTH_FILE="$SANDBOX/creds/broken.json" 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 credential file -> exit 4 (must be a regular file)
ln -s "$SANDBOX/creds/auth.json" "$SANDBOX/creds/link.json"
PI_AUTH_FILE="$SANDBOX/creds/link.json" 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 are informational — name shown, value never
OUT="$(FAKE_TEST_API_KEY=$ENVVAL PI_AUTH_FILE="$SANDBOX/creds/auth.json" 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: empty directory -> (none), exit 0
OUT="$(PI_AUTH_FILE="$SANDBOX/empty/auth.json" scripts/auth.sh accounts 2>&1)"; RC=$?
[ "$RC" -eq 0 ] && printf '%s\n' "$OUT" | grep -q "(none)" \
&& check "accounts with no named files reports none" 0 || check "accounts with no named files reports none" 1
# accounts: listing with active marker
OUT="$(PI_AUTH_FILE="$SANDBOX/creds/auth.work.json" scripts/auth.sh accounts 2>&1)"; RC=$?
[ "$RC" -eq 0 ] && printf '%s\n' "$OUT" | grep -q '^ work <- active (PI_AUTH_FILE)$' \
&& printf '%s\n' "$OUT" | grep -q '^ personal$' \
&& check "accounts lists files and marks the active one" 0 || check "accounts lists files and marks the active one" 1
# accounts: missing directory -> exit 3
PI_AUTH_FILE="$SANDBOX/nodir/auth.json" scripts/auth.sh accounts >/dev/null 2>&1; RC=$?
[ "$RC" -eq 3 ] && check "accounts on missing directory exits 3" 0 || check "accounts on missing directory exits 3" 1
# agent.sh --auth: missing account file refuses before any container work
scripts/agent.sh researcher --auth nope </dev/null >/dev/null 2>"$SANDBOX/err.txt"; RC=$?
[ "$RC" -eq 4 ] && grep -q "no 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: invalid account name refuses
scripts/agent.sh researcher --auth "bad~name" </dev/null >/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
echo
echo "selftest: $PASS passed, $FAIL failed"
[ "$FAIL" -eq 0 ]