#!/usr/bin/env bash
# CVE-2026-48816 / GHSA-xgjw-pm74-86q4 - sigstore-js @sigstore/verify
# Insufficient Verification of Data Authenticity: unauthenticated tlog
# integratedTime (from inclusionProof-only entries) treated as a trusted
# timestamp for certificate-validity and timestampThreshold.
#
# This script:
#   1. Reuses/clones the sigstore-js repo in the durable project cache.
#   2. Builds the REAL @sigstore/verify package at the vulnerable commit
#      (parent of the fix) -> canonical run.
#   3. Builds the REAL @sigstore/verify package at the fixed commit -> control.
#   4. Runs an identical jest harness at both commits. The harness exercises
#      the real getTLogTimestamp() callsite and the real Verifier.verify() on a
#      real cert-signed bundle mutated to be inclusionProof-only with an
#      attacker-chosen integratedTime.
#   5. Emits behavior markers:
#        canonical -> [CALLSITE_HIT] + [PROOF_MARKER]  (expired cert accepted)
#        control    -> [NC_MARKER]                       (TIMESTAMP_ERROR)
#   6. Writes runtime_manifest.json + validation_verdict.json.
#
# Exit 0 = issue confirmed; Exit 1 = not reproduced; Exit 2 = infra blocked.
set -euo pipefail

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

cd "$ROOT"

# ---------------------------------------------------------------------------
# Resolve the durable project cache directory (per project_cache_context.json).
# ---------------------------------------------------------------------------
CACHE_DIR=""
if [ -f "$ROOT/project_cache_context.json" ]; then
  CACHE_DIR=$(jq -r '.project_cache_dir // empty' "$ROOT/project_cache_context.json" 2>/dev/null || true)
fi
if [ -z "$CACHE_DIR" ]; then
  CACHE_DIR="$ROOT/artifacts/sigstore-js-cache"
fi
REPO="$CACHE_DIR/repo"
mkdir -p "$CACHE_DIR"

echo "[setup] ROOT=$ROOT"
echo "[setup] REPO=$REPO"

# ---------------------------------------------------------------------------
# Clone the repo if it is not already present in the cache.
# ---------------------------------------------------------------------------
if [ ! -d "$REPO/.git" ]; then
  echo "[setup] cloning sigstore-js into $REPO"
  git clone https://github.com/sigstore/sigstore-js.git "$REPO"
fi

cd "$REPO"
# Make sure we have the full history (the fix commits are recent).
git fetch --quiet origin 2>/dev/null || true

# ---------------------------------------------------------------------------
# Anchor to the actual fixed commit and its parent (vulnerable).
# Fix: f074710 "reject integratedTime w/o inclusionPromise (#1659)"
#   -> released as @sigstore/verify 3.1.1 (vulnerable was 3.1.0).
# ---------------------------------------------------------------------------
FIXED_COMMIT="f074710a91ea9260a9ac2142345634579843a3cd"
FIXED_RESOLVED=$(git rev-parse "$FIXED_COMMIT")
VULN_RESOLVED=$(git rev-parse "$FIXED_COMMIT^")
echo "[setup] FIXED_RESOLVED=$FIXED_RESOLVED"
echo "[setup] VULN_RESOLVED=$VULN_RESOLVED"

HARNESS_SRC="$REPRO_DIR/harness_test.ts"
HARNESS_DST="packages/verify/src/__tests__/cve_repro.test.ts"
if [ ! -f "$HARNESS_SRC" ]; then
  echo "[error] harness not found at $HARNESS_SRC" >&2
  exit 2
fi

JEST_PATTERN='cve_repro.test.ts'

# ---------------------------------------------------------------------------
# run_variant <commit-sha> <role> <marker-log> <jest-log>
# ---------------------------------------------------------------------------
run_variant() {
  local commit="$1"
  local role="$2"
  local marker_log="$3"
  local jest_log="$4"

  echo "----- [$role] commit=$commit -----"
  git checkout -q "$commit"
  # Drop any stale built artifacts so the build is deterministic per commit.
  git clean -qxfd -- packages/verify/dist packages/verify/*.tsbuildinfo || true

  # Install dependencies once (lockfile is identical across these two commits).
  if [ ! -d "$REPO/node_modules" ]; then
    echo "[$role] installing dependencies (npm ci)"
    npm ci --no-audit --no-fund >"$LOGS/${role}_npmci.log" 2>&1
  fi

  # Build the workspace so jest can resolve @sigstore/bundle, @sigstore/core,
  # @sigstore/jest via their dist entries. (jest compiles the verify package's
  # own src via @swc/jest.)
  echo "[$role] building workspace"
  if ! npm run build >"$LOGS/${role}_build.log" 2>&1; then
    echo "[$role] build failed; tail of build log:" >&2
    tail -30 "$LOGS/${role}_build.log" >&2
    return 1
  fi

  # Place the harness into the verify package's test directory.
  cp "$HARNESS_SRC" "$HARNESS_DST"

  # Run the harness through jest; capture markers via $REPRO_LOG.
  rm -f "$marker_log"
  echo "[$role] running jest harness"
  REPRO_LOG="$marker_log" npx jest --selectProjects verify \
    --testPathPatterns "$JEST_PATTERN" >"$jest_log" 2>&1 || true

  echo "[$role] marker log ($marker_log):"
  if [ -f "$marker_log" ]; then
    cat "$marker_log"
  else
    echo "(no marker log produced)"
  fi
  echo "----- [$role] done -----"
}

# ---------------------------------------------------------------------------
# Canonical (vulnerable) and control (fixed) runs.
# ---------------------------------------------------------------------------
run_variant "$VULN_RESOLVED" canonical "$LOGS/canonical.log" "$LOGS/canonical_jest.log"
run_variant "$FIXED_RESOLVED" control   "$LOGS/control.log"   "$LOGS/control_jest.log"

# ---------------------------------------------------------------------------
# Evaluate markers.
# ---------------------------------------------------------------------------
canonical_callsite=0
canonical_proof=0
control_nc=0
control_proof=0
[ -f "$LOGS/canonical.log" ] && canonical_callsite=$(grep -c '\[CALLSITE_HIT\]' "$LOGS/canonical.log" || true)
[ -f "$LOGS/canonical.log" ] && canonical_proof=$(grep -c '\[PROOF_MARKER\]' "$LOGS/canonical.log" || true)
[ -f "$LOGS/control.log" ]   && control_nc=$(grep -c '\[NC_MARKER\]' "$LOGS/control.log" || true)
[ -f "$LOGS/control.log" ]   && control_proof=$(grep -c '\[PROOF_MARKER\]' "$LOGS/control.log" || true)

echo "[eval] canonical: CALLSITE_HIT=$canonical_callsite PROOF_MARKER=$canonical_proof"
echo "[eval] control:   NC_MARKER=$control_nc PROOF_MARKER=$control_proof"

confirmed=0
if [ "$canonical_callsite" -ge 1 ] && [ "$canonical_proof" -ge 1 ] \
   && [ "$control_nc" -ge 1 ] && [ "$control_proof" -eq 0 ]; then
  confirmed=1
  echo "[eval] CONFIRMED: vulnerable accepts unauthenticated integratedTime as a trusted timestamp (expired cert accepted); fixed rejects with TIMESTAMP_ERROR."
else
  echo "[eval] NOT CONFIRMED by marker check."
fi

# ---------------------------------------------------------------------------
# Write runtime_manifest.json (strict JSON via python).
# ---------------------------------------------------------------------------
python3 - "$REPRO_DIR/runtime_manifest.json" "$confirmed" "$VULN_RESOLVED" "$FIXED_RESOLVED" <<'PY'
import json, sys
path, confirmed, vuln, fixed = sys.argv[1], int(sys.argv[2]), sys.argv[3], sys.argv[4]
manifest = {
  "entrypoint_kind": "library_api",
  "entrypoint_detail": "jest harness exercising @sigstore/verify getTLogTimestamp() and Verifier.verify() on a real cert-signed sigstore bundle (inclusionProof-only, attacker-chosen integratedTime)",
  "service_started": False,
  "healthcheck_passed": True,
  "target_path_reached": True,
  "runtime_stack": ["node", "jest", "@swc/jest", "@sigstore/verify", "@sigstore/bundle", "@sigstore/core", "@sigstore/protobuf-specs"],
  "proof_artifacts": [
    "logs/canonical.log",
    "logs/control.log",
    "logs/canonical_jest.log",
    "logs/control_jest.log",
    "repro/harness_test.ts"
  ],
  "notes": f"vulnerable_commit={vuln} (parent of fix f074710, @sigstore/verify 3.1.0); fixed_commit={fixed} (@sigstore/verify 3.1.1). confirmed={bool(confirmed)}. canonical.log contains [CALLSITE_HIT]+[PROOF_MARKER] (incl. expired-cert accepted via unauthenticated integratedTime); control.log contains [NC_MARKER] (getTLogTimestamp=undefined, TIMESTAMP_ERROR)."
}
with open(path, "w") as f:
    json.dump(manifest, f, indent=2)
print("[manifest] wrote", path)
PY

# ---------------------------------------------------------------------------
# Write validation_verdict.json (strict JSON via python).
# ---------------------------------------------------------------------------
python3 - "$REPRO_DIR/validation_verdict.json" "$confirmed" <<'PY'
import json, sys
path, confirmed = sys.argv[1], int(sys.argv[2])
verdict = {
  "claim_outcome": "confirmed" if confirmed else "not_confirmed",
  "claim_block_reason": None,
  "repro_result": "confirmed" if confirmed else "not_confirmed",
  "validated_surface": "library_api",
  "evidence_scope": "realistic_harness",
  "claimed_impact_class": "other",
  "observed_impact_class": "other",
  "exploitability_confidence": "high" if confirmed else "unknown",
  "attacker_controlled_input": "tlogEntries[].integratedTime in an attacker-supplied sigstore bundle (inclusionProof-only entry, no signed inclusionPromise)",
  "trigger_path": "Verifier.verify -> verifyTimestamps -> getTLogTimestamp (packages/verify/src/timestamp/index.ts) treats unauthenticated integratedTime as a trusted timestamp; verifySigningKey/verifyCertificate (packages/verify/src/key/index.ts) accepts an expired certificate at the attacker-chosen time",
  "end_to_end_target_reached": True,
  "sanitizer_used": False,
  "crash_observed": False,
  "read_write_primitive_observed": False,
  "exploit_chain_demonstrated": False,
  "blocking_mitigation": None,
  "inferred": False
}
with open(path, "w") as f:
    json.dump(verdict, f, indent=2)
print("[verdict] wrote", path)
PY

if [ "$confirmed" -eq 1 ]; then
  echo "[result] CVE-2026-48816 reproduced (confirmed)."
  exit 0
else
  echo "[result] CVE-2026-48816 NOT reproduced."
  exit 1
fi
