From 93aaeb4abe16626415355ef4600237477feb6ead Mon Sep 17 00:00:00 2001 From: Dorian Date: Thu, 19 Mar 2026 20:35:41 +0000 Subject: [PATCH] fix: node names not DIDs, file sharing path validation, sync results - nodeName() shows friendly "Node-XXXX" instead of truncated DID - nodeNameFromDid() for sync results lookup - Map labels use node names - Content filename validation: allow / for subdirectories (Music/song.mp3) but still block .., \, null bytes, hidden files, absolute paths - Increased filename max length to 512 for paths with subdirectories Co-Authored-By: Claude Opus 4.6 (1M context) --- core/archipelago/src/api/rpc/content.rs | 18 ++++++++++++------ neode-ui/src/views/Federation.vue | 23 +++++++++++++++++++---- 2 files changed, 31 insertions(+), 10 deletions(-) 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 @@
- {{ shortDid(r.did) }} + {{ nodeNameFromDid(r.did) }} {{ r.apps }} apps {{ r.error }}
@@ -230,7 +230,7 @@
- {{ node.name || shortDid(node.did) }} + {{ nodeName(node) }}
- {{ node.name || shortDid(node.did) }} + {{ nodeName(node) }}
{{ node.trust_level }}
@@ -578,7 +578,7 @@ const mapNodes = computed(() => { for (const node of nodes.value) { result.push({ did: node.did, - label: node.name || shortDid(node.did), + label: nodeName(node), trust_level: node.trust_level as 'trusted' | 'observer' | 'untrusted', online: isOnline(node), app_count: node.last_state?.apps?.length ?? 0, @@ -773,6 +773,21 @@ function shortDid(did: string): string { return did.slice(0, 16) + '...' + did.slice(-8) } +/** User-friendly node display name. Prefers name, falls back to "Node-XXXX" from DID hash. */ +function nodeName(node: { name?: string | null; did: string }): string { + if (node.name) return node.name + const suffix = node.did.replace(/^did:key:z6Mk/, '').slice(-6).toUpperCase() + return `Node-${suffix}` +} + +/** Look up display name from DID (for sync results that only have a DID). */ +function nodeNameFromDid(did: string): string { + const node = nodes.value.find(n => n.did === did) + if (node) return nodeName(node) + const suffix = did.replace(/^did:key:z6Mk/, '').slice(-6).toUpperCase() + return `Node-${suffix}` +} + function timeAgo(iso: string): string { const seconds = Math.floor((Date.now() - new Date(iso).getTime()) / 1000) if (seconds < 60) return 'just now'