diff --git a/core/archipelago/src/api/rpc/content.rs b/core/archipelago/src/api/rpc/content.rs index 32c97166..e0254702 100644 --- a/core/archipelago/src/api/rpc/content.rs +++ b/core/archipelago/src/api/rpc/content.rs @@ -35,15 +35,21 @@ impl RpcHandler { .get("filename") .and_then(|v| v.as_str()) .ok_or_else(|| anyhow::anyhow!("Missing filename"))?; - // Validate filename: prevent path traversal, hidden files, and excessive length - if filename.contains("..") || filename.contains('\0') || filename.contains('/') || filename.contains('\\') { + // Validate filename: prevent path traversal and null bytes + // Allow forward slashes for subdirectories (e.g., "Music/song.mp3") + if filename.contains("..") || filename.contains('\0') || filename.contains('\\') { anyhow::bail!("Invalid filename: path traversal not allowed"); } - if filename.starts_with('.') { - anyhow::bail!("Invalid filename: hidden files not allowed"); + // Reject paths starting with / (absolute) or . (hidden) + if filename.starts_with('/') || filename.starts_with('.') { + anyhow::bail!("Invalid filename: absolute paths and hidden files not allowed"); } - if filename.is_empty() || filename.len() > 255 { - anyhow::bail!("Invalid filename: must be 1-255 characters"); + // Reject any path segment starting with . (hidden dirs) + if filename.split('/').any(|seg| seg.starts_with('.') || seg.is_empty()) { + anyhow::bail!("Invalid filename: hidden files/dirs or empty segments not allowed"); + } + if filename.is_empty() || filename.len() > 512 { + anyhow::bail!("Invalid filename: must be 1-512 characters"); } let mime_type = params .get("mime_type") diff --git a/neode-ui/src/views/Federation.vue b/neode-ui/src/views/Federation.vue index 8110c167..77322851 100644 --- a/neode-ui/src/views/Federation.vue +++ b/neode-ui/src/views/Federation.vue @@ -188,7 +188,7 @@