Initial commit
This commit is contained in:
Executable
+102
@@ -0,0 +1,102 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user