Files
stack/agents/dewey/work/queue-74/demo.sh
T
jason.woltjeandClaude Opus 5.5 fce04c1556 review(dewey): row 74 round 2 packet, repeated-Host 400 and proxy-Host 403 (#1568)
Round 2 per Sage's notes revs 521 and 523: a repeated Host is 400 before
any check; HEADER with a Host other than NAME is 403 proxy Host refused;
HEAD, x.NAME and --auth-header tests; the README proxy contract lines.
Candidate manifest 17b6d7f6, base 17ea0dbc. 34 of 34 mutants killed;
gate green.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 18:23:26 -05:00

47 lines
3.7 KiB
Bash

#!/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 <worktree> <scratch>.
# 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"