Files
sovereign_browser/tests/test_identity_isolation.sh
T

135 lines
5.8 KiB
Bash

#!/bin/bash
# tests/test_identity_isolation.sh
#
# End-to-end test for the WebKit data isolation fix (Phase 0 of
# plans/webkit-data-isolation.md). Verifies that switching Nostr identity
# wipes the previous user's cookies + localStorage so the next user does
# not see the previous user's web session.
#
# Prereqs:
# - ./browser.sh start --login-method generate (user A is logged in)
# - python3 -m http.server 8765 running in tests/local-site/
#
# Flow:
# 1. Capture user A's pubkey via login_status.
# 2. Open http://localhost:8765/identity-leak-test.html
# 3. Click "Write fresh marker" — writes localStorage + cookie.
# 4. Read the marker value via eval.
# 5. switch_identity to a fresh generated key (user B).
# 6. Reopen the test page.
# 7. Read localStorage + cookie via eval.
# 8. PASS if both are empty; FAIL if user A's marker leaked through.
set -u
MCP="http://localhost:17777/mcp"
URL="http://localhost:8765/identity-leak-test.html"
# MCP responses are SSE-formatted: "event: message\ndata: {json}\n\n".
# mcp_call <json> -> extracts the inner tool result text (a JSON string)
mcp_call() {
local payload="$1"
local raw
raw=$(curl -s -X POST "$MCP" -H 'Content-Type: application/json' -d "$payload")
# Extract the data: line, then parse the nested JSON with python.
echo "$raw" | python3 -c '
import sys, json, re
raw = sys.stdin.read()
m = re.search(r"^data: (.+)$", raw, re.MULTILINE)
if not m:
print("ERROR: no data line", file=sys.stderr)
sys.exit(2)
outer = json.loads(m.group(1))
content = outer.get("result", {}).get("content", [])
if not content:
print("ERROR: no content", file=sys.stderr)
sys.exit(2)
text = content[0].get("text", "")
# text is itself a JSON string: {"success":true,"data":{...},"id":N}
try:
inner = json.loads(text)
print(json.dumps(inner))
except json.JSONDecodeError:
print(text)
'
}
echo "[test] Checking browser is up + logged in..."
STATUS=$(mcp_call '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"login_status","arguments":{}}}')
PUBKEY_A=$(echo "$STATUS" | python3 -c 'import sys,json; d=json.load(sys.stdin); print(d.get("data",{}).get("pubkey",""))')
if [ -z "${PUBKEY_A:-}" ]; then
echo "[test] FAIL: not logged in as user A"
echo "$STATUS"
exit 1
fi
echo "[test] User A pubkey: $PUBKEY_A"
echo "[test] Opening test page..."
mcp_call '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"open","arguments":{"url":"'"$URL"'"}}}' >/dev/null
sleep 1.5
# Write the marker directly via eval (same effect as clicking the
# "Write fresh marker" button, but deterministic — no dependency on the
# click tool's selector/ref resolution).
echo "[test] Writing marker as user A via eval..."
MARKER_A="userA-$(date +%s)-$RANDOM"
mcp_call '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"eval","arguments":{"script":"localStorage.setItem(\"sb_identity_marker\",\"'"$MARKER_A"'\"); document.cookie=\"sb_identity_marker='"$MARKER_A"'; path=/\"; \"wrote\""}}}' >/dev/null
sleep 0.5
echo "[test] Reading marker back as user A..."
READBACK=$(mcp_call '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"eval","arguments":{"script":"localStorage.getItem(\"sb_identity_marker\") || \"\""}}}' \
| python3 -c 'import sys,json; d=json.load(sys.stdin); print(d.get("data",{}).get("result","") if isinstance(d.get("data"),dict) else d.get("data",""))')
echo "[test] User A marker readback: '${READBACK:-<empty>}'"
MARKER_A="$READBACK"
if [ -z "${MARKER_A:-}" ]; then
echo "[test] FAIL: could not read user A marker — test setup issue"
exit 1
fi
echo "[test] Switching identity to a fresh generated key (user B)..."
SWITCH=$(mcp_call '{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"switch_identity","arguments":{"method":"generate"}}}')
echo "$SWITCH" | python3 -c 'import sys,json; d=json.load(sys.stdin); print("switch result:", "OK" if d.get("success") else "FAIL", d.get("data",{}).get("pubkey","") if isinstance(d.get("data"),dict) else "")'
PUBKEY_B=$(echo "$SWITCH" | python3 -c 'import sys,json; d=json.load(sys.stdin); print(d.get("data",{}).get("pubkey","") if isinstance(d.get("data"),dict) else "")')
if [ -z "${PUBKEY_B:-}" ]; then
echo "[test] FAIL: switch_identity did not return a pubkey"
echo "$SWITCH"
exit 1
fi
if [ "$PUBKEY_A" = "$PUBKEY_B" ]; then
echo "[test] FAIL: switch_identity returned the same pubkey"
exit 1
fi
echo "[test] User B pubkey: $PUBKEY_B"
sleep 1.5
echo "[test] Reopening test page as user B..."
mcp_call '{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"open","arguments":{"url":"'"$URL"'"}}}' >/dev/null
sleep 1.5
echo "[test] Reading localStorage + cookie as user B..."
LS_B=$(mcp_call '{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"eval","arguments":{"script":"localStorage.getItem(\"sb_identity_marker\") || \"\""}}}' \
| python3 -c 'import sys,json; d=json.load(sys.stdin); print(d.get("data",{}).get("result","") if isinstance(d.get("data"),dict) else d.get("data",""))')
CK_B=$(mcp_call '{"jsonrpc":"2.0","id":8,"method":"tools/call","params":{"name":"eval","arguments":{"script":"(document.cookie.split(\"; \").find(function(c){return c.indexOf(\"sb_identity_marker=\")===0})||\"\").split(\"=\").slice(1).join(\"=\")"}}}' \
| python3 -c 'import sys,json; d=json.load(sys.stdin); print(d.get("data",{}).get("result","") if isinstance(d.get("data"),dict) else d.get("data",""))')
echo "[test] User B localStorage: '${LS_B:-<empty>}'"
echo "[test] User B cookie: '${CK_B:-<empty>}'"
PASS=1
if [ -n "${LS_B:-}" ]; then
echo "[test] FAIL: localStorage leaked from user A to user B"
PASS=0
fi
if [ -n "${CK_B:-}" ]; then
echo "[test] FAIL: cookie leaked from user A to user B"
PASS=0
fi
if [ "$PASS" = "1" ]; then
echo "[test] PASS: identity switch wiped user A's web data; user B sees a fresh session."
exit 0
else
echo "[test] FAIL: identity leak detected."
exit 1
fi