From ba48f205559d7e8c00d754abd5b87f2dfc65f871 Mon Sep 17 00:00:00 2001 From: mohammadmseet-hue Date: Fri, 24 Jul 2026 15:56:12 +0200 Subject: [PATCH] Validate file paths in ReadFile and sanitize HTML in process table - lib/manager/container.go: in containerData.ReadFile, verify the resolved path stays within the per-pid "/proc//root" prefix after path.Join normalization, so a `filepath` containing ".." cannot escape the container root. - cmd/internal/pages/assets/js/containers.js: add escapeHtml() and apply it to the string fields rendered in the process listing table (which uses allowHtml), and encodeURI the cgroup link href. --- cmd/internal/pages/assets/js/containers.js | 21 ++++++++++++++------- lib/manager/container.go | 5 +++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/cmd/internal/pages/assets/js/containers.js b/cmd/internal/pages/assets/js/containers.js index ee2f61867f..61e45d9abf 100644 --- a/cmd/internal/pages/assets/js/containers.js +++ b/cmd/internal/pages/assets/js/containers.js @@ -14,6 +14,12 @@ google.charts.load('current', {packages: ['corechart', 'gauge', 'default', 'format', 'ui', 'table']}); +// Escape HTML special characters to prevent XSS when rendering in tables with allowHtml. +function escapeHtml(str) { + if (typeof str !== 'string') return str; + return str.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, '''); +} + function humanize(num, size, units) { var unit; for (unit = units.pop(); units.length && num >= size; unit = units.pop()) { @@ -622,10 +628,10 @@ function drawProcesses(isRoot, rootDir, processInfo) { var data = []; for (var i = 0; i < processInfo.length; i++) { var elements = []; - elements.push(processInfo[i].user); + elements.push(escapeHtml(processInfo[i].user)); elements.push(processInfo[i].pid); elements.push(processInfo[i].parent_pid); - elements.push(processInfo[i].start_time); + elements.push(escapeHtml(processInfo[i].start_time)); elements.push({ v: processInfo[i].percent_cpu, f: processInfo[i].percent_cpu.toFixed(2) @@ -639,15 +645,16 @@ function drawProcesses(isRoot, rootDir, processInfo) { v: processInfo[i].virtual_size, f: humanizeIEC(processInfo[i].virtual_size) }); - elements.push(processInfo[i].status); - elements.push(processInfo[i].running_time); - elements.push(processInfo[i].cmd); + elements.push(escapeHtml(processInfo[i].status)); + elements.push(escapeHtml(processInfo[i].running_time)); + elements.push(escapeHtml(processInfo[i].cmd)); elements.push(processInfo[i].psr); if (isRoot) { var cgroup = processInfo[i].cgroup_path; // Use the raw cgroup link as it works for all containers. - var cgroupLink = '' + - cgroup.substr(0, 30) + ' '; + var escapedCgroup = escapeHtml(cgroup); + var cgroupLink = '' + + escapedCgroup.substr(0, 30) + ' '; elements.push({v: cgroup, f: cgroupLink}); } data.push(elements); diff --git a/lib/manager/container.go b/lib/manager/container.go index 59de49c7b4..1cc15bb962 100644 --- a/lib/manager/container.go +++ b/lib/manager/container.go @@ -25,6 +25,7 @@ import ( "path" "sort" "strconv" + "strings" "sync" "sync/atomic" "time" @@ -411,6 +412,10 @@ func (cd *containerData) ReadFile(filepath string, inHostNamespace bool) ([]byte } for _, pid := range pids { fp := path.Join(rootfs, "/proc", strconv.Itoa(pid), "/root", filepath) + expectedPrefix := path.Join(rootfs, "/proc", strconv.Itoa(pid), "/root") + "/" + if !strings.HasPrefix(fp, expectedPrefix) { + return nil, fmt.Errorf("invalid file path %q: resolves outside container root", filepath) + } if data, rerr := os.ReadFile(fp); rerr == nil { return data, nil }