#!/usr/bin/env bash # Row 74 (#1568): the Console in preview mode, driven with curl on loopback only (round 2). # Run from a worktree with the candidate applied: bash demo.sh . # No system config (MOSAIC_CONFIG names a missing file), so the bus routes answer # not-configured once past the sign-in check; the board flag names a closed port. set -u TREE=$1 SCRATCH=$2 NAME=mosaic-preview.woltje.com HEADER=X-authentik-uid ID=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef # a stand-in; ops-01 sends the real id LOG=$SCRATCH/demo-server.txt cd "$TREE" || exit 1 echo "== partial flag set, refused at start" MOSAIC_CONFIG=$SCRATCH/no-config.json node packages/webui/src/cli.mjs serve --port 0 --public-host "$NAME" --auth-header "$HEADER"; echo "exit $?" MOSAIC_CONFIG=$SCRATCH/no-config.json node packages/webui/src/cli.mjs serve --port 0 \ --board http://127.0.0.1:9 --public-host "$NAME" --auth-header "$HEADER" --allow-user "$ID" > "$LOG" 2>&1 & PID=$! trap 'kill "$PID" 2>/dev/null' EXIT for _ in $(seq 50); do grep -q '^Mosaic Console' "$LOG" && break; sleep 0.1; done cat "$LOG" PORT=$(sed -n 's|^Mosaic Console: http://127.0.0.1:\([0-9]*\)/$|\1|p' "$LOG") U=http://127.0.0.1:$PORT c() { local what=$1 out; shift; out=$(curl -s -w '\n%{http_code}' "$@"); printf '%-42s %s %.110s\n' "$what" "${out##*$'\n'}" "$(printf '%s' "${out%$'\n'*}" | tr -d '\n')"; } echo "== refusals" c "wrong Host, no header" -H "Host: evil.example" "$U/api/bus/inbox" c "wrong Host, signed in" -H "Host: evil.example" -H "$HEADER: $ID" "$U/api/bus/inbox" c "public name with a port" -H "Host: $NAME:443" -H "$HEADER: $ID" "$U/api/bus/inbox" c "wrong Origin" -H "Host: $NAME" -H "$HEADER: $ID" -H "Origin: https://evil.example" "$U/api/bus/inbox" c "missing header" -H "Host: $NAME" "$U/api/bus/inbox" c "wrong user" -H "Host: $NAME" -H "$HEADER: ${ID%?}0" "$U/api/bus/inbox" c "Host rewritten to loopback, wrong user" -H "Host: 127.0.0.1:$PORT" -H "$HEADER: mallory" "$U/api/config" c "Host rewritten to localhost, right user" -H "Host: localhost:$PORT" -H "$HEADER: $ID" "$U/api/config" # curl sends one Host however many are given, so the repeated Host goes over a raw socket. exec 3<>"/dev/tcp/127.0.0.1/$PORT" printf 'GET /api/bus/inbox HTTP/1.1\r\nHost: 127.0.0.1:%s\r\nHost: %s\r\nConnection: close\r\n\r\n' "$PORT" "$NAME" >&3 out=$(tr -d '\r' <&3); exec 3<&- printf '%-42s %s %.110s\n' "two Hosts, loopback then the public name" "$(sed -n '1s/^HTTP\/1.1 \([0-9]*\).*/\1/p' <<<"$out")" "$(grep '^{' <<<"$out")" c "POST /api/seen, signed in" -H "Host: $NAME" -H "$HEADER: $ID" -H 'Content-Type: application/json' -d '{"seen":true}' "$U/api/seen" c "POST /api/reply, signed in" -H "Host: $NAME" -H "$HEADER: $ID" -H 'Content-Type: application/json' -d '{"agent":"p/a","text":"x"}' "$U/api/reply" c "POST /api/reply, loopback Host" -H 'Content-Type: application/json' -d '{"agent":"p/a","text":"x"}' "$U/api/reply" echo "== allowed" c "GET /healthz, signed in, lowercase header" -H "Host: $NAME" -H "x-authentik-uid: $ID" -H "Origin: https://$NAME" "$U/healthz" c "GET /api/config, signed in" -H "Host: $NAME" -H "$HEADER: $ID" "$U/api/config" c "GET / (the page), signed in" -H "Host: $NAME" -H "$HEADER: $ID" "$U/" c "GET /api/bus/inbox, signed in (no bus)" -H "Host: $NAME" -H "$HEADER: $ID" "$U/api/bus/inbox" c "GET /api/board, signed in (board down)" -H "Host: $NAME" -H "$HEADER: $ID" "$U/api/board" c "GET /healthz, loopback, no header" "$U/healthz"