135 lines
5.8 KiB
Bash
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
|