babalae/better-genshin-impact · error · ArgumentException
文件路径 '{path}' 越界访问!
Error message
文件路径 '{path}' 越界访问! What it means
Thrown by ScriptUtils.NormalizePath as a path-traversal guard: after combining root + path and resolving to an absolute path, the result does not start with root. This blocks directory-escape attempts (e.g., '../../etc/passwd' or absolute paths that leave root).
Source
Thrown at BetterGenshinImpact/Core/Script/Utils/ScriptUtils.cs:35
// 检查是否含有非法文件名字符
var invalidChars = Path.GetInvalidFileNameChars();
string fileName = Path.GetFileName(path);
if (fileName.Any(c => invalidChars.Contains(c)))
{
throw new ArgumentException($"文件路径 '{path}' 包含非法字符");
}
// 替换分隔符
path = path.Replace('\\', '/');
// 组合并获取绝对路径
var fullPath = Path.GetFullPath(Path.Combine(root, path));
// 防止越界访问
if (!fullPath.StartsWith(root, StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException($"文件路径 '{path}' 越界访问!");
}
return fullPath;
}
}
View on GitHub (pinned to a7cb36712d)
Solutions
- Ensure callers pass truly relative paths; strip leading slashes/separators from user input before NormalizePath.
- Use a stricter containment check: compare full path segments, not StartsWith string comparison (StartsWith can be fooled by sibling dirs sharing a prefix, e.g., root='C:\app' matches 'C:\app-other\x').
- Add a trailing separator to root before the comparison: fullPath.StartsWith(root + Path.DirectorySeparatorChar, OrdinalIgnoreCase) || fullPath == root.
- If symlinks are a concern, resolve them with Path.GetFullPath after enumerating links, or disallow symlinks entirely.
Example fix
// before
var fullPath = Path.GetFullPath(Path.Combine(root, path));
if (!fullPath.StartsWith(root, StringComparison.OrdinalIgnoreCase))
throw new ArgumentException($"文件路径 '{path}' 越界访问!");
return fullPath;
// after (trailing-separator containment, prefix-collision-safe)
var rootFull = Path.GetFullPath(root).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
+ Path.DirectorySeparatorChar;
var fullPath = Path.GetFullPath(Path.Combine(rootFull, path));
if (!fullPath.StartsWith(rootFull, StringComparison.OrdinalIgnoreCase))
throw new UnauthorizedAccessException($"文件路径 '{path}' 越界访问! 已解析={fullPath}, 根={rootFull}");
return fullPath; Defensive patterns
Strategy: validation
Validate before calling
// Trailing-separator containment (prefix-collision-safe)
var rootFull = Path.GetFullPath(root).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
+ Path.DirectorySeparatorChar;
var fullPath = Path.GetFullPath(Path.Combine(rootFull, path));
if (!fullPath.StartsWith(rootFull, StringComparison.OrdinalIgnoreCase))
throw new UnauthorizedAccessException($"越界: {fullPath}"); Type guard
static bool IsWithinRoot(string root, string fullPath)
{
var r = Path.GetFullPath(root).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
+ Path.DirectorySeparatorChar;
return fullPath.StartsWith(r, StringComparison.OrdinalIgnoreCase);
} Try / catch
catch (UnauthorizedAccessException ex) when (ex.Message.Contains("越界"))
{
_logger.LogWarning("拒绝越界路径访问: {Msg}", ex.Message);
throw;
} Prevention
- Use a trailing-separator containment check, not bare StartsWith.
- Strip leading separators and '..' from untrusted input.
- Be aware GetFullPath does not resolve symlinks — disallow them if untrusted.
When it happens
Trigger: Path.GetFullPath(Path.Combine(root, path)) yields a path outside root. Examples: path='../../../secrets' resolves above root; path='/etc/passwd' is absolute and ignores root (on the drive); path='..\\..' on Windows escapes; path contains symlink-resolved escapes (note: GetFullPath does NOT resolve symlinks, so this guard can be bypassed via symlinks).
Common situations: Malicious or malformed manifest/webview input attempting traversal; a legitimate relative path that accidentally steps above root due to too many '..'; cross-drive paths on Windows where StartsWith(root, OrdinalIgnoreCase) fails because root and resolved are on different drives.
Related errors
AI-assisted analysis of babalae/better-genshin-impact@a7cb36712d (2026-08-13).
Data as JSON: /api/errors/64c6f9681bbb7a91.
Report an issue: GitHub.