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:
@@ -20,12 +20,26 @@ pub struct ContainerStatus {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub state: ContainerState,
|
||||
pub health: Option<String>, // "healthy", "unhealthy", "starting", or None if no healthcheck
|
||||
pub started_at: Option<String>,
|
||||
pub image: String,
|
||||
pub created: String,
|
||||
pub ports: Vec<String>,
|
||||
pub lan_address: Option<String>, // Launch URL for UI access
|
||||
}
|
||||
|
||||
/// Parse health status from podman's Status string (e.g., "Up 5 minutes (healthy)")
|
||||
fn parse_health_from_status(status: &str) -> Option<String> {
|
||||
if let Some(start) = status.rfind('(') {
|
||||
if let Some(end) = status.rfind(')') {
|
||||
if start < end {
|
||||
return Some(status[start + 1..end].to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum ContainerState {
|
||||
Created,
|
||||
@@ -301,6 +315,8 @@ impl PodmanClient {
|
||||
id: parts[0].to_string(),
|
||||
name: parts[1].to_string(),
|
||||
state: ContainerState::from(parts[2]),
|
||||
health: None,
|
||||
started_at: None,
|
||||
image: parts[3].to_string(),
|
||||
created: parts[4].to_string(),
|
||||
ports: vec![], // TODO: Parse ports from parts[5]
|
||||
@@ -387,10 +403,15 @@ impl PodmanClient {
|
||||
};
|
||||
|
||||
let lan_address = Self::lan_address_for(&name);
|
||||
let status_str = container["Status"].as_str().unwrap_or("");
|
||||
let health = parse_health_from_status(status_str);
|
||||
let started_at = container["StartedAt"].as_str().or_else(|| container["Started"].as_str()).map(|s| s.to_string());
|
||||
result.push(ContainerStatus {
|
||||
id: container["Id"].as_str().unwrap_or("").to_string(),
|
||||
name,
|
||||
state: ContainerState::from(container["State"].as_str().unwrap_or("unknown")),
|
||||
health,
|
||||
started_at,
|
||||
image: container["Image"].as_str().unwrap_or("").to_string(),
|
||||
created: container["Created"].as_str().unwrap_or("").to_string(),
|
||||
ports,
|
||||
@@ -431,10 +452,15 @@ impl PodmanClient {
|
||||
};
|
||||
|
||||
let lan_address = Self::lan_address_for(&name);
|
||||
let status_str = container["Status"].as_str().unwrap_or("");
|
||||
let health = parse_health_from_status(status_str);
|
||||
let started_at = container["StartedAt"].as_str().or_else(|| container["Started"].as_str()).map(|s| s.to_string());
|
||||
result.push(ContainerStatus {
|
||||
id: container["Id"].as_str().unwrap_or("").to_string(),
|
||||
name,
|
||||
state: ContainerState::from(container["State"].as_str().unwrap_or("unknown")),
|
||||
health,
|
||||
started_at,
|
||||
image: container["Image"].as_str().unwrap_or("").to_string(),
|
||||
created: container["Created"].as_str().unwrap_or("").to_string(),
|
||||
ports,
|
||||
|
||||
@@ -284,6 +284,8 @@ impl ContainerRuntime for DockerRuntime {
|
||||
id: parts[0].to_string(),
|
||||
name: parts[1].to_string(),
|
||||
state: crate::podman_client::ContainerState::from(parts[2]),
|
||||
health: None,
|
||||
started_at: None,
|
||||
image: parts[3].to_string(),
|
||||
created: parts[4].to_string(),
|
||||
ports: vec![],
|
||||
@@ -356,6 +358,8 @@ impl ContainerRuntime for DockerRuntime {
|
||||
state: ContainerState::from(
|
||||
container["State"].as_str().unwrap_or("unknown")
|
||||
),
|
||||
health: None,
|
||||
started_at: None,
|
||||
image: container["Image"].as_str().unwrap_or("").to_string(),
|
||||
created: container["CreatedAt"].as_str().unwrap_or("").to_string(),
|
||||
ports,
|
||||
|
||||
Reference in New Issue
Block a user