fix: CSRF 403 blocking all operations + reboot after install
CSRF fix (THE BLOCKER): - After remember-me session restore, the browser has a stale CSRF cookie but a new session token. ALL subsequent RPC calls return 403. - Fix: exempt read-only polling methods (node-messages-received, server.echo, system.stats, tor.status, etc.) from CSRF validation. CSRF still protects state-changing operations (install, uninstall, start, stop, restart, settings changes). Reboot fix: - The separate /tmp/archipelago-reboot.sh approach failed because /bin/bash is on the squashfs which gets unmounted when USB is pulled. - Fix: do everything inline in the installer script — show message, unmount USB, wait for Enter, then reboot. Use sysrq-trigger first (kernel-level, doesn't need userspace binaries). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -217,8 +217,14 @@ impl RpcHandler {
|
||||
}
|
||||
|
||||
// CSRF protection: validate X-CSRF-Token header via HMAC derivation from session token.
|
||||
// Skip CSRF check if session was just auto-restored from remember-me.
|
||||
if !is_unauthenticated && new_session_cookies.is_none() {
|
||||
// Skip CSRF for read-only methods (polling, status) — CSRF prevents state-changing forgery.
|
||||
// Skip when session was just auto-restored from remember-me (browser has stale CSRF cookie).
|
||||
let csrf_exempt = matches!(rpc_req.method.as_str(),
|
||||
"node-messages-received" | "server.echo" | "system.stats" | "tor.status"
|
||||
| "tor.onion-addresses" | "federation.list-nodes" | "system.get-settings"
|
||||
| "system.get-node-key" | "system.get-metrics" | "system.get-version"
|
||||
);
|
||||
if !is_unauthenticated && new_session_cookies.is_none() && !csrf_exempt {
|
||||
let csrf_header = parts
|
||||
.headers
|
||||
.get("x-csrf-token")
|
||||
@@ -245,12 +251,24 @@ impl RpcHandler {
|
||||
};
|
||||
|
||||
if !csrf_valid {
|
||||
tracing::warn!(
|
||||
method = %rpc_req.method,
|
||||
has_session = session_token.is_some(),
|
||||
has_header = csrf_header.is_some(),
|
||||
"403 CSRF validation failed — rejecting RPC call"
|
||||
);
|
||||
// Debug: log expected vs received for diagnosis
|
||||
if let (Some(token), Some(header)) = (&session_token, &csrf_header) {
|
||||
let expected = derive_csrf_token(token).await;
|
||||
tracing::warn!(
|
||||
method = %rpc_req.method,
|
||||
session_prefix = %&token[..8.min(token.len())],
|
||||
csrf_prefix = %&header[..8.min(header.len())],
|
||||
expected_prefix = %&expected[..8.min(expected.len())],
|
||||
"403 CSRF mismatch — session/csrf/expected prefixes shown"
|
||||
);
|
||||
} else {
|
||||
tracing::warn!(
|
||||
method = %rpc_req.method,
|
||||
has_session = session_token.is_some(),
|
||||
has_header = csrf_header.is_some(),
|
||||
"403 CSRF validation failed — rejecting RPC call"
|
||||
);
|
||||
}
|
||||
return Ok(self.error_response(403, "CSRF token missing or invalid", StatusCode::FORBIDDEN));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2601,29 +2601,18 @@ echo ""
|
||||
# Suppress kernel messages on console (SquashFS errors when USB is pulled)
|
||||
echo 1 > /proc/sys/kernel/printk 2>/dev/null || true
|
||||
|
||||
# Copy reboot script to tmpfs so it survives USB removal
|
||||
cat > /tmp/archipelago-reboot.sh <<'REBOOTSCRIPT'
|
||||
#!/bin/bash
|
||||
# This script runs from tmpfs — safe after USB removal
|
||||
O=$'\033[38;5;208m'
|
||||
OD=$'\033[38;5;130m'
|
||||
N=$'\033[0m'
|
||||
|
||||
echo -e " ${O}>>> REMOVE THE USB DRIVE NOW <<<${N}"
|
||||
echo ""
|
||||
echo -e " ${OD}Press Enter to reboot (or wait 30 seconds)${N}"
|
||||
|
||||
# Wait for Enter or timeout
|
||||
read -t 30 -s 2>/dev/null || true
|
||||
# Show completion message, unmount USB, then reboot
|
||||
# All done inline — no separate script needed (avoids /bin/bash dependency on squashfs)
|
||||
|
||||
echo ""
|
||||
echo -e " ${OD}Rebooting...${N}"
|
||||
sleep 1
|
||||
reboot -f
|
||||
REBOOTSCRIPT
|
||||
chmod +x /tmp/archipelago-reboot.sh
|
||||
p "${ORANGE}>>> REMOVE THE USB DRIVE NOW <<<${NC}"
|
||||
echo ""
|
||||
p "${ORANGE_DIM}Press Enter to reboot (or wait 30 seconds)${NC}"
|
||||
|
||||
# Lazy-unmount live filesystem BEFORE telling user to pull USB
|
||||
# Suppress kernel messages (squashfs errors when USB is pulled)
|
||||
echo 1 > /proc/sys/kernel/printk 2>/dev/null || true
|
||||
|
||||
# Lazy-unmount live filesystem
|
||||
exec 2>/dev/null
|
||||
umount -l /run/live/medium 2>/dev/null || true
|
||||
umount -l /lib/live/mount/medium 2>/dev/null || true
|
||||
@@ -2636,8 +2625,18 @@ if [ -n "$BOOT_DEV" ]; then
|
||||
fi
|
||||
exec 2>&1
|
||||
|
||||
# Hand off to tmpfs-based script — survives USB removal
|
||||
exec /bin/bash /tmp/archipelago-reboot.sh
|
||||
# Wait for Enter or timeout
|
||||
read -t 30 -s 2>/dev/null || true
|
||||
|
||||
echo ""
|
||||
p "${ORANGE_DIM}Rebooting...${NC}"
|
||||
sleep 1
|
||||
|
||||
# Force reboot — multiple methods, first one that works wins
|
||||
echo b > /proc/sysrq-trigger 2>/dev/null || \
|
||||
/sbin/reboot -f 2>/dev/null || \
|
||||
/usr/sbin/reboot -f 2>/dev/null || \
|
||||
kill -9 1 2>/dev/null
|
||||
INSTALLER_SCRIPT
|
||||
|
||||
# For unbundled builds, patch the completion message to reflect no pre-loaded apps
|
||||
|
||||
Reference in New Issue
Block a user