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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user