security+feat: v1.3.0 — pentest remediation, container reliability, UI overhaul
Security (33 pentest findings addressed): - CRITICAL: backend binds 127.0.0.1, path traversal in tor.rs/dwn fixed - HIGH: federation requires signatures, XSS login redirect, RBAC viewer restricted - HIGH: tar slip prevention, S3 SSRF validation, backup ID validation - MEDIUM: remember-me random secret, TOTP session rotation, password re-auth - LOW: CSP unsafe-inline removed, CORS dev-only, onion/webhook validation Container reliability: - Memory limits on all 37 containers (OOM prevention) - Exited vs stopped state distinction with health-aware status badges - Crash recovery coordination (no more restart cascade) - User-stopped tracking survives reboots - Tiered boot recovery (databases → core → services → apps) UI: - Wallet TransactionsModal, health-aware app status badges - Restart button on containers, exited/crashed red state - Mesh view overhaul, glass button updates, BaseModal/ToggleSwitch - Apps sticky header removed, dev faucet, mutable mock wallet Infrastructure: - LND REST port 8080 exposed over Tor (LND Connect fix) - Nginx cookie_session fix, deploy script Tor config updated - Dev environment: podman auto-start, boot mode simulation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
28763c4f09
commit
84a56c80de
@@ -442,7 +442,7 @@ impl RpcHandler {
|
||||
"auth.changePassword" => self.handle_auth_change_password(params, &session_token).await,
|
||||
"auth.onboardingComplete" => self.handle_auth_onboarding_complete().await,
|
||||
"auth.isOnboardingComplete" => self.handle_auth_is_onboarding_complete().await,
|
||||
"auth.resetOnboarding" => self.handle_auth_reset_onboarding().await,
|
||||
"auth.resetOnboarding" => self.handle_auth_reset_onboarding(params).await,
|
||||
|
||||
// Container orchestration (for Archipelago-managed containers)
|
||||
"container-install" => self.handle_container_install(params).await,
|
||||
@@ -789,7 +789,7 @@ impl RpcHandler {
|
||||
self.metrics_store.record_rpc_latency(elapsed_ms).await;
|
||||
|
||||
// Build response (cache successful results for cacheable methods)
|
||||
let rpc_resp = match result {
|
||||
let mut rpc_resp = match result {
|
||||
Ok(data) => {
|
||||
if is_cacheable {
|
||||
self.response_cache.set(rpc_req.method.clone(), data.clone()).await;
|
||||
@@ -893,8 +893,62 @@ impl RpcHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// On successful TOTP verification, the session is already upgraded to full
|
||||
// (handled inside handle_login_totp/handle_login_backup)
|
||||
// On successful TOTP verification, set the rotated session cookie
|
||||
if (rpc_req.method == "auth.login.totp" || rpc_req.method == "auth.login.backup")
|
||||
&& rpc_resp.error.is_none()
|
||||
{
|
||||
// Extract token (clone to release immutable borrow before mutable borrow below)
|
||||
let new_token_opt = rpc_resp
|
||||
.result
|
||||
.as_ref()
|
||||
.and_then(|r| r.get("new_session_token"))
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string());
|
||||
|
||||
if let Some(new_token) = new_token_opt {
|
||||
let csrf_token = derive_csrf_token(&new_token);
|
||||
let remember_token = self.session_store.create_remember_token();
|
||||
response.headers_mut().append(
|
||||
"Set-Cookie",
|
||||
format!(
|
||||
"session={}; HttpOnly; SameSite=Strict; Path=/{}",
|
||||
new_token,
|
||||
self.cookie_suffix()
|
||||
)
|
||||
.parse()
|
||||
.unwrap(),
|
||||
);
|
||||
response.headers_mut().append(
|
||||
"Set-Cookie",
|
||||
format!(
|
||||
"csrf_token={}; SameSite=Strict; Path=/{}",
|
||||
csrf_token,
|
||||
self.cookie_suffix()
|
||||
)
|
||||
.parse()
|
||||
.unwrap(),
|
||||
);
|
||||
response.headers_mut().append(
|
||||
"Set-Cookie",
|
||||
format!(
|
||||
"remember={}; HttpOnly; SameSite=Strict; Path=/; Max-Age={}{}",
|
||||
remember_token,
|
||||
REMEMBER_TTL,
|
||||
self.cookie_suffix()
|
||||
)
|
||||
.parse()
|
||||
.unwrap(),
|
||||
);
|
||||
// Strip the token from the response body — don't leak it to JS
|
||||
if let Some(result) = rpc_resp.result.as_mut() {
|
||||
if let Some(obj) = result.as_object_mut() {
|
||||
obj.remove("new_session_token");
|
||||
}
|
||||
}
|
||||
let body_bytes = serde_json::to_vec(&rpc_resp).unwrap_or_default();
|
||||
*response.body_mut() = hyper::Body::from(body_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
// On password change, rotate the session token for the caller
|
||||
if rpc_req.method == "auth.changePassword" && rpc_resp.error.is_none() {
|
||||
|
||||
Reference in New Issue
Block a user