#!/usr/bin/env bash
# =============================================================================
# Security / negative test — proves the reported attack chain is dead
# (STRIDE S10 / E1). Requires a RUNNING stack; if kali-sandbox is not up the
# suite SKIPS (exit 0) so it is safe in CI without a daemon.
#
# Asserts, from the host:
#   1. The MCP SSE port answers on 127.0.0.1 (loopback publish present).
#   2. Unauthenticated request -> 401 (bearer enforced) OR connection refused
#      (token unset but loopback-only still blocks LAN; localhost probe shows the
#      auth layer). With MCP_AUTH_TOKEN set, no-token/ wrong-token -> 401.
#   3. With the correct token -> NOT 401 (the agent path still works).
#   4. The port is NOT reachable on a non-loopback host IP.
#
# Run:  bash tests/test_exploit_blocked.sh
# =============================================================================
set -uo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO_ROOT"

PASS=0; FAIL=0
pass() { PASS=$((PASS+1)); printf '  \033[0;32mPASS\033[0m %s\n' "$1"; }
fail() { FAIL=$((FAIL+1)); printf '  \033[0;31mFAIL\033[0m %s\n' "$1"; }

if ! docker compose ps --format '{{.Service}} {{.State}}' 2>/dev/null | grep -q 'kali-sandbox running'; then
    echo "kali-sandbox not running — skipping live exploit-blocked checks."
    exit 0
fi

# Load MCP_AUTH_TOKEN from .env if present.
TOKEN="$(grep -E '^MCP_AUTH_TOKEN=' .env 2>/dev/null | head -1 | cut -d= -f2-)"
PORT=8000
URL="http://127.0.0.1:${PORT}/sse"

status() { curl -s -o /dev/null -w '%{http_code}' --max-time 4 "$@" 2>/dev/null; }

echo "== 1. loopback publish present =="
code_noauth="$(status "$URL")"
if [[ -n "$code_noauth" ]]; then pass "MCP :$PORT answers on 127.0.0.1 (code=$code_noauth)"; else fail "MCP :$PORT not answering on loopback"; fi

echo "== 2. unauthenticated access blocked (STRIDE S9: fail-closed) =="
if [[ -n "$TOKEN" ]]; then
    code="$(status "$URL")"
    [[ "$code" == "401" ]] && pass "no token -> 401" || fail "no token -> expected 401, got $code"
    code="$(status -H 'Authorization: Bearer wrong-token' "$URL")"
    [[ "$code" == "401" ]] && pass "wrong token -> 401" || fail "wrong token -> expected 401, got $code"
    code="$(status -H "Authorization: Bearer ${TOKEN}" "$URL")"
    [[ "$code" != "401" ]] && pass "correct token -> not 401 (agent path works, code=$code)" || fail "correct token wrongly rejected"
else
    # S9 fail-closed: even with MCP_AUTH_TOKEN unset the server must REJECT, not
    # serve everyone. A token-less request must not succeed.
    code="$(status "$URL")"
    [[ "$code" == "401" ]] && pass "token unset -> 401 (fail-closed)" || fail "token unset -> expected 401 (fail-closed), got $code"
fi

echo "== 3. NOT reachable on a non-loopback interface =="
HOST_IP="$(ip -4 -o addr show scope global 2>/dev/null | awk '{print $4}' | cut -d/ -f1 | head -1)"
if [[ -n "$HOST_IP" ]]; then
    if timeout 3 bash -c "exec 3<>/dev/tcp/${HOST_IP}/${PORT}" 2>/dev/null; then
        fail "MCP :$PORT is reachable on LAN IP $HOST_IP (exposure NOT closed!)"
        exec 3>&- 2>/dev/null
    else
        pass "MCP :$PORT refused on LAN IP $HOST_IP (remote attack closed)"
    fi
else
    echo "  (no global IP detected — skipping LAN-reach check)"
fi

echo
echo "-----------------------------------------"
printf 'Exploit-blocked suite: \033[0;32m%d passed\033[0m, ' "$PASS"
if [[ $FAIL -gt 0 ]]; then printf '\033[0;31m%d failed\033[0m\n' "$FAIL"; exit 1; else printf '%d failed\n' "$FAIL"; fi
