fix: harden input validation across all RPC endpoints (PENTEST-02)
Manual security audit of 130+ RPC endpoints. Critical fixes: - LND: validate pubkey (66-char hex), Bitcoin addresses, channel points, amount bounds, payment request format, memo length, peer address - Package: validate_app_id on start/stop/restart/bundled-app handlers, validate volume host paths (must be under /var/lib/archipelago/), validate Docker image in bundled-app-start - Container: validate_app_id on all 6 handlers, canonicalize manifest paths - Network: path traversal prevention in connection request deletion - Backup: backup ID validation in delete handler - Webhooks: URL scheme validation, SSRF prevention for private IPs - Security: validate app_id in secret rotation - Interfaces: WiFi password length/null validation, strict IP/gateway/DNS parsing for static ethernet config Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
4b5eb4ed29
commit
aa6c7c2e6a
@@ -1,4 +1,5 @@
|
||||
use super::RpcHandler;
|
||||
use super::package::validate_app_id;
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
impl RpcHandler {
|
||||
@@ -17,24 +18,29 @@ impl RpcHandler {
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing manifest_path"))?;
|
||||
|
||||
// Validate manifest path: reject path traversal and paths outside apps/
|
||||
if manifest_path.contains("..") {
|
||||
// Validate manifest path: reject traversal, resolve to canonical path
|
||||
if manifest_path.contains("..") || manifest_path.contains('\0') {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Invalid manifest_path: path traversal not allowed"
|
||||
));
|
||||
}
|
||||
let path = std::path::Path::new(manifest_path);
|
||||
if path.is_absolute() {
|
||||
let apps_dir = self.config.data_dir.join("apps");
|
||||
if !path.starts_with(&apps_dir) {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Invalid manifest_path: must be under the apps directory"
|
||||
));
|
||||
}
|
||||
let apps_dir = self.config.data_dir.join("apps");
|
||||
let resolved = if std::path::Path::new(manifest_path).is_absolute() {
|
||||
std::path::PathBuf::from(manifest_path)
|
||||
} else {
|
||||
apps_dir.join(manifest_path)
|
||||
};
|
||||
let canonical = resolved
|
||||
.canonicalize()
|
||||
.context("Invalid manifest_path: file not found")?;
|
||||
if !canonical.starts_with(&apps_dir) {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Invalid manifest_path: must be under the apps directory"
|
||||
));
|
||||
}
|
||||
|
||||
// Load manifest
|
||||
let manifest_content = tokio::fs::read_to_string(manifest_path)
|
||||
let manifest_content = tokio::fs::read_to_string(&canonical)
|
||||
.await
|
||||
.context("Failed to read manifest file")?;
|
||||
let manifest: archipelago_container::AppManifest = serde_yaml::from_str(&manifest_content)
|
||||
@@ -62,6 +68,7 @@ impl RpcHandler {
|
||||
.get("app_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing app_id"))?;
|
||||
validate_app_id(app_id)?;
|
||||
|
||||
orchestrator
|
||||
.start_container(app_id)
|
||||
@@ -85,6 +92,7 @@ impl RpcHandler {
|
||||
.get("app_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing app_id"))?;
|
||||
validate_app_id(app_id)?;
|
||||
|
||||
orchestrator
|
||||
.stop_container(app_id)
|
||||
@@ -108,6 +116,7 @@ impl RpcHandler {
|
||||
.get("app_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing app_id"))?;
|
||||
validate_app_id(app_id)?;
|
||||
let preserve_data = params
|
||||
.get("preserve_data")
|
||||
.and_then(|v| v.as_bool())
|
||||
@@ -206,6 +215,7 @@ impl RpcHandler {
|
||||
.get("app_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing app_id"))?;
|
||||
validate_app_id(app_id)?;
|
||||
|
||||
let status = orchestrator
|
||||
.get_container_status(app_id)
|
||||
@@ -229,6 +239,7 @@ impl RpcHandler {
|
||||
.get("app_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing app_id"))?;
|
||||
validate_app_id(app_id)?;
|
||||
let lines = params
|
||||
.get("lines")
|
||||
.and_then(|v| v.as_u64())
|
||||
|
||||
Reference in New Issue
Block a user