From 80e168787ed608e83a065033ee94c8bfc3025ce7 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Fri, 12 Jun 2026 14:25:06 +0200 Subject: [PATCH] fix(xray): confine log.access/error to the panel log folder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An authenticated admin could set xrayTemplateConfig.log.access/error to an arbitrary path (via the raw Xray editor or a wholesale DB import), making the supervised Xray process write its log there — an arbitrary file write as the Xray user (root in many deployments). resolveXrayLogPaths now reduces any log path to its base filename under config.GetLogFolder(), so absolute paths and ".." traversal can no longer escape the log folder; "" and "none" still disable logging. --- internal/web/service/xray.go | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/internal/web/service/xray.go b/internal/web/service/xray.go index 0b4f507b0..d3769979b 100644 --- a/internal/web/service/xray.go +++ b/internal/web/service/xray.go @@ -3,6 +3,7 @@ package service import ( "encoding/json" "errors" + "path" "path/filepath" "runtime" "strings" @@ -498,11 +499,6 @@ func ensureStatsPolicy(policy json_util.RawMessage) json_util.RawMessage { return out } -// resolveXrayLogPaths rewrites relative `log.access` / `log.error` values to -// absolute paths under config.GetLogFolder(), so Xray writes those files -// alongside the panel's other logs regardless of the working directory the -// panel was launched from. Values that are empty, "none", or already absolute -// are left untouched, as are unparseable log blocks. func resolveXrayLogPaths(logCfg json_util.RawMessage) json_util.RawMessage { if len(logCfg) == 0 { return logCfg @@ -521,21 +517,15 @@ func resolveXrayLogPaths(logCfg json_util.RawMessage) json_util.RawMessage { if trimmed == "" || strings.EqualFold(trimmed, "none") { continue } - if filepath.IsAbs(trimmed) { + base := path.Base(filepath.ToSlash(trimmed)) + if base == "" || base == "." || base == ".." || base == "/" { continue } - cleaned := filepath.ToSlash(filepath.Clean(trimmed)) - base := filepath.Base(cleaned) - if base == "" || base == "." || base == string(filepath.Separator) { + confined := filepath.Join(config.GetLogFolder(), base) + if confined == trimmed { continue } - // Only rewrite bare names ("./access.log", "access.log"). - // A nested relative path like "./logs/foo.log" is treated as - // a deliberate user choice and left alone. - if cleaned != base { - continue - } - parsed[key] = filepath.Join(config.GetLogFolder(), base) + parsed[key] = confined changed = true } if !changed {