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
@@ -435,10 +435,46 @@ fn create_tar_gz(data_dir: &Path) -> Result<Vec<u8>> {
|
||||
fn extract_tar_gz(data_dir: &Path, tar_gz_data: &[u8]) -> Result<()> {
|
||||
let gz = GzDecoder::new(tar_gz_data);
|
||||
let mut archive = Archive::new(gz);
|
||||
let canonical_base = data_dir
|
||||
.canonicalize()
|
||||
.context("Failed to canonicalize data_dir")?;
|
||||
|
||||
archive
|
||||
.unpack(data_dir)
|
||||
.context("Failed to extract backup archive")?;
|
||||
for entry_result in archive.entries().context("Failed to read tar entries")? {
|
||||
let mut entry = entry_result.context("Failed to read tar entry")?;
|
||||
let entry_path = entry.path().context("Failed to get entry path")?.to_path_buf();
|
||||
|
||||
// Reject entries with path traversal components
|
||||
for component in entry_path.components() {
|
||||
if matches!(component, std::path::Component::ParentDir) {
|
||||
anyhow::bail!(
|
||||
"Tar entry contains path traversal: {}",
|
||||
entry_path.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let target = data_dir.join(&entry_path);
|
||||
// Verify the resolved path stays within data_dir
|
||||
// For new files that don't exist yet, check the parent directory
|
||||
let check_path = if target.exists() {
|
||||
target.canonicalize()?
|
||||
} else if let Some(parent) = target.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
parent.canonicalize()?.join(target.file_name().unwrap_or_default())
|
||||
} else {
|
||||
target.clone()
|
||||
};
|
||||
if !check_path.starts_with(&canonical_base) {
|
||||
anyhow::bail!(
|
||||
"Tar entry escapes target directory: {}",
|
||||
entry_path.display()
|
||||
);
|
||||
}
|
||||
|
||||
entry
|
||||
.unpack(&target)
|
||||
.with_context(|| format!("Failed to extract: {}", entry_path.display()))?;
|
||||
}
|
||||
|
||||
debug!("Backup extracted to {:?}", data_dir);
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user