From 77849062ab9a79659555bdea2df0cc84b3926dc1 Mon Sep 17 00:00:00 2001 From: Clint Branham Date: Sun, 22 Mar 2026 08:15:02 -0500 Subject: [PATCH] fix: add Uri.EscapeDataString to service URL path interpolations VmService, ContainerService, StorageService, and SnapshotService now wrap user-controlled string parameters (node names, storage names) with Uri.EscapeDataString() in URL path construction, preventing path traversal or encoding issues. Integer parameters (vmid) are left unwrapped. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Services/SnapshotService.cs | 8 ++++---- .../Services/StorageService.cs | 18 +++++++++--------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/PSProxmoxVE.Core/Services/SnapshotService.cs b/src/PSProxmoxVE.Core/Services/SnapshotService.cs index aa21bb8..838c4c2 100644 --- a/src/PSProxmoxVE.Core/Services/SnapshotService.cs +++ b/src/PSProxmoxVE.Core/Services/SnapshotService.cs @@ -25,7 +25,7 @@ namespace PSProxmoxVE.Core.Services if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node)); using var client = new PveHttpClient(session); - var response = client.GetAsync($"nodes/{node}/qemu/{vmid}/snapshot") + var response = client.GetAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/snapshot") .GetAwaiter().GetResult(); var data = JObject.Parse(response)["data"]; return data?.ToObject() ?? Array.Empty(); @@ -61,7 +61,7 @@ namespace PSProxmoxVE.Core.Services formData["description"] = description!; using var client = new PveHttpClient(session); - var response = client.PostAsync($"nodes/{node}/qemu/{vmid}/snapshot", formData) + var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/snapshot", formData) .GetAwaiter().GetResult(); return ParseTask(response, node); } @@ -84,7 +84,7 @@ namespace PSProxmoxVE.Core.Services if (string.IsNullOrWhiteSpace(snapname)) throw new ArgumentNullException(nameof(snapname)); using var client = new PveHttpClient(session); - var response = client.DeleteAsync($"nodes/{node}/qemu/{vmid}/snapshot/{Uri.EscapeDataString(snapname)}") + var response = client.DeleteAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/snapshot/{Uri.EscapeDataString(snapname)}") .GetAwaiter().GetResult(); return ParseTask(response, node); } @@ -107,7 +107,7 @@ namespace PSProxmoxVE.Core.Services if (string.IsNullOrWhiteSpace(snapname)) throw new ArgumentNullException(nameof(snapname)); using var client = new PveHttpClient(session); - var response = client.PostAsync($"nodes/{node}/qemu/{vmid}/snapshot/{Uri.EscapeDataString(snapname)}/rollback") + var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/snapshot/{Uri.EscapeDataString(snapname)}/rollback") .GetAwaiter().GetResult(); return ParseTask(response, node); } diff --git a/src/PSProxmoxVE.Core/Services/StorageService.cs b/src/PSProxmoxVE.Core/Services/StorageService.cs index e9f93e0..e33aba2 100644 --- a/src/PSProxmoxVE.Core/Services/StorageService.cs +++ b/src/PSProxmoxVE.Core/Services/StorageService.cs @@ -29,7 +29,7 @@ namespace PSProxmoxVE.Core.Services if (session == null) throw new ArgumentNullException(nameof(session)); var resource = node != null - ? $"nodes/{node}/storage" + ? $"nodes/{Uri.EscapeDataString(node)}/storage" : "storage"; using var client = new PveHttpClient(session); @@ -57,7 +57,7 @@ namespace PSProxmoxVE.Core.Services if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node)); if (string.IsNullOrWhiteSpace(storage)) throw new ArgumentNullException(nameof(storage)); - var resource = $"nodes/{node}/storage/{storage}/content"; + var resource = $"nodes/{Uri.EscapeDataString(node)}/storage/{Uri.EscapeDataString(storage)}/content"; if (!string.IsNullOrEmpty(contentType)) resource += $"?content={Uri.EscapeDataString(contentType!)}"; @@ -102,7 +102,7 @@ namespace PSProxmoxVE.Core.Services using var client = new PveHttpClient(session); var response = client.UploadFileAsync( - $"nodes/{node}/storage/{storage}/upload", + $"nodes/{Uri.EscapeDataString(node)}/storage/{Uri.EscapeDataString(storage)}/upload", filePath, formFields, checksum, @@ -144,7 +144,7 @@ namespace PSProxmoxVE.Core.Services }; using var client = new PveHttpClient(session); - var response = client.PostAsync($"nodes/{node}/storage/{storage}/download-url", formData) + var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/storage/{Uri.EscapeDataString(storage)}/download-url", formData) .GetAwaiter().GetResult(); return ParseTask(response, node); } @@ -184,7 +184,7 @@ namespace PSProxmoxVE.Core.Services if (string.IsNullOrWhiteSpace(storage)) throw new ArgumentNullException(nameof(storage)); using var client = new PveHttpClient(session); - client.DeleteAsync($"storage/{storage}").GetAwaiter().GetResult(); + client.DeleteAsync($"storage/{Uri.EscapeDataString(storage)}").GetAwaiter().GetResult(); } /// @@ -221,7 +221,7 @@ namespace PSProxmoxVE.Core.Services if (string.IsNullOrWhiteSpace(storage)) throw new ArgumentNullException(nameof(storage)); using var client = new PveHttpClient(session); - var response = client.GetAsync($"nodes/{node}/storage/{storage}/status").GetAwaiter().GetResult(); + var response = client.GetAsync($"nodes/{Uri.EscapeDataString(node)}/storage/{Uri.EscapeDataString(storage)}/status").GetAwaiter().GetResult(); var data = JObject.Parse(response)["data"]; return data?.ToObject() ?? new PveStorageStatus(); } @@ -241,7 +241,7 @@ namespace PSProxmoxVE.Core.Services if (string.IsNullOrWhiteSpace(volume)) throw new ArgumentNullException(nameof(volume)); using var client = new PveHttpClient(session); - client.DeleteAsync($"nodes/{node}/storage/{storage}/content/{Uri.EscapeDataString(volume)}") + client.DeleteAsync($"nodes/{Uri.EscapeDataString(node)}/storage/{Uri.EscapeDataString(storage)}/content/{Uri.EscapeDataString(volume)}") .GetAwaiter().GetResult(); } @@ -262,7 +262,7 @@ namespace PSProxmoxVE.Core.Services if (config == null) throw new ArgumentNullException(nameof(config)); using var client = new PveHttpClient(session); - client.PutAsync($"nodes/{node}/storage/{storage}/content/{Uri.EscapeDataString(volume)}", config) + client.PutAsync($"nodes/{Uri.EscapeDataString(node)}/storage/{Uri.EscapeDataString(storage)}/content/{Uri.EscapeDataString(volume)}", config) .GetAwaiter().GetResult(); } @@ -281,7 +281,7 @@ namespace PSProxmoxVE.Core.Services if (config == null) throw new ArgumentNullException(nameof(config)); using var client = new PveHttpClient(session); - var response = client.PostAsync($"nodes/{node}/storage/{storage}/content", config) + var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/storage/{Uri.EscapeDataString(storage)}/content", config) .GetAwaiter().GetResult(); return ParseTask(response, node); }