#!/usr/bin/env bash # End-to-end check: runs the demo app (production fat jar) with the agent, drives two user sessions # through the leaky views with plain HTTP requests and asserts the agent's JSON report. # # scripts/e2e.sh build everything, then test # SKIP_BUILD=1 scripts/e2e.sh reuse existing jars # MVN=/path/to/mvn PORT=18765 override maven / port set -euo pipefail ROOT=$(cd "$(dirname "$0")/.." && pwd) PORT=${PORT:-18765} BASE="http://localhost:$PORT" MVN=${MVN:-mvn} WORK=$(mktemp -d) REPORT="$WORK/leaks.json" LOG="$WORK/app.log" AGENT="$ROOT/leakfinder-agent/target/leakfinder-agent-0.1.0-SNAPSHOT.jar" APP="$ROOT/demo-app/target/demo-app-0.1.0-SNAPSHOT.jar" if [[ "${SKIP_BUILD:-0}" != 1 ]]; then (cd "$ROOT" && "$MVN" -q -pl leakfinder-agent install -DskipTests && "$MVN" -q -pl demo-app -Pproduction package -DskipTests) fi APP_PID= cleanup() { [[ -n "$APP_PID" ]] && kill "$APP_PID" 2>/dev/null || true echo "app log: $LOG" } trap cleanup EXIT PORT=$PORT java "-javaagent:$AGENT=scanInterval=2s,gcWait=1s,report=$REPORT" -jar "$APP" >"$LOG" 2>&1 & APP_PID=$! for _ in $(seq 1 120); do grep -q 'Started Application' "$LOG" && break if ! kill -0 "$APP_PID" 2>/dev/null; then echo "app died"; tail -50 "$LOG"; exit 1; fi sleep 1 done grep -q 'Started Application' "$LOG" || { echo "app did not start"; exit 1; } # Opens a route in a new UI of the session identified by cookie jar $1, like a browser tab would: # bootstrap request (v-r=init) followed by the client side "ui-navigate" event. open_view() { local jar="$WORK/$1" route="$2" init key ui init=$(curl -sf --max-time 20 -c "$jar" -b "$jar" "$BASE/?v-r=init&location=$route") key=$(python3 -c 'import json,sys; print(json.load(sys.stdin)["appConfig"]["uidl"]["Vaadin-Security-Key"])' <<<"$init") ui=$(python3 -c 'import json,sys; print(json.load(sys.stdin)["appConfig"]["v-uiId"])' <<<"$init") curl -sf --max-time 20 -o /dev/null -c "$jar" -b "$jar" -H 'Content-Type: application/json' \ -X POST "$BASE/?v-r=uidl&v-uiId=$ui" \ -d "{\"csrfToken\":\"$key\",\"rpc\":[{\"type\":\"event\",\"node\":1,\"event\":\"ui-navigate\",\"data\":{\"route\":\"$route\",\"query\":\"\",\"appShellTitle\":\"\",\"historyState\":null,\"trigger\":\"\"}}],\"syncId\":0,\"clientId\":0}" echo " $1 -> /$route" } echo "driving sessions" open_view alice static-leak open_view alice singleton-leak open_view alice broadcaster-leak open_view alice shared-component open_view bob shared-component # steals alice's banner open_view bob singleton-leak open_view bob broadcaster-leak open_view bob snoop # keeps a reference to alice's view open_view bob "" # clean view curl -sf --max-time 20 "$BASE/api/broadcast" >/dev/null curl -sf --max-time 20 -c "$WORK/alice" -b "$WORK/alice" "$BASE/api/logout" >/dev/null echo " alice logged out" sleep 8 python3 - "$REPORT" <<'EOF' import json, sys report = json.load(open(sys.argv[1])) findings = report["findings"] print(report["lastScanSummary"]) for f in findings: print(f" {f['severity']:5} {f['kind']:34} sessions={f['sessions']} {f['title']}") def text(f): return f["title"] + "\n" + "\n".join(f["details"]) def expect(kind, *needles): if not any(f["kind"] == kind and all(n in text(f) for n in needles) for f in findings): failures.append(f"missing {kind} mentioning {needles}") failures = [] expect("SHARED_ROOT_HOLDS_SESSION_STATE", "static com.example.leakdemo.StaticLeakView.RECENT_VIEWS") expect("SHARED_ROOT_HOLDS_SESSION_STATE", "static com.example.leakdemo.Broadcaster.LISTENERS") expect("SHARED_ROOT_HOLDS_SESSION_STATE", "spring singleton 'gridCache'", "Grid") expect("SHARED_ROOT_HOLDS_SESSION_STATE", "static com.example.leakdemo.SharedComponentView.BANNER") expect("CROSS_SESSION_ATTACH", "com.vaadin.flow.component.html.Span", "SharedComponentView") expect("CROSS_SESSION_MUTATION", "SharedComponentView") expect("SESSION_REFERENCES_FOREIGN_STATE", "SnoopView", ".peeked", "StaticLeakView") expect("DESTROYED_SESSION_RETAINED", "StaticLeakView.RECENT_VIEWS") if any("CleanView" in text(f) for f in findings): failures.append("false positive: CleanView mentioned") if failures: print("\nE2E FAILED:\n " + "\n ".join(failures)) sys.exit(1) print("\nE2E PASSED") EOF