{"record":{"id":"8e42a195df90d112","repo":"babalae/better-genshin-impact","slug":"relativepath","errorCode":null,"errorMessage":"访问路径 '{relativePath}' 被拒绝","messagePattern":"访问路径 '(.+?)' 被拒绝","errorType":"exception","errorClass":"UnauthorizedAccessException","httpStatus":null,"severity":"error","filePath":"BetterGenshinImpact/Core/Script/WebView/FileAccessBridge.cs","lineNumber":62,"sourceCode":"        try\n        {\n            var fullPath = Path.GetFullPath(path);\n            var normalizedPath = fullPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);\n            return normalizedPath.StartsWith(_normalizedAllowedPath, StringComparison.OrdinalIgnoreCase);\n        }\n        catch\n        {\n            return false;\n        }\n    }\n\n    public string ReadFile(string relativePath)\n    {\n        try\n        {\n            var fullPath = Path.Combine(_allowedDirectory, relativePath);\n            if (!IsPathAllowed(fullPath))\n                throw new UnauthorizedAccessException($\"访问路径 '{relativePath}' 被拒绝\");\n\n            if (!File.Exists(fullPath))\n                throw new FileNotFoundException($\"文件 '{relativePath}' 不存在\");\n\n            return File.ReadAllText(fullPath, Encoding.UTF8);\n        }\n        catch (Exception ex)\n        {\n            throw new Exception($\"读取文件失败: {ex.Message}\");\n        }\n    }\n\n    public void WriteFile(string relativePath, string content)\n    {\n        try\n        {\n            var fullPath = Path.Combine(_allowedDirectory, relativePath);\n            if (!IsPathAllowed(fullPath))","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/babalae/better-genshin-impact/blob/a7cb36712dcb409be610257d877fcea3597e9d6b/BetterGenshinImpact/Core/Script/WebView/FileAccessBridge.cs#L44-L80","documentation":"Thrown by FileAccessBridge.ReadFile when the resolved full path is not inside the configured allowed directory (IsPathAllowed returns false). This is the webview-facing sandbox guard preventing scripts from reading arbitrary files via the bridge.","triggerScenarios":"ReadFile(relativePath) computes Path.Combine(_allowedDirectory, relativePath); IsPathAllowed(fullPath) normalizes and checks StartsWith(_normalizedAllowedPath). If false, it throws UnauthorizedAccessException — path traversal attempt or absolute path that escapes the sandbox.","commonSituations":"A webview script passes '../../something' or an absolute path; relativePath uses '..' to leave the allowed dir; the allowed dir was moved/renamed since the bridge was constructed; case/prefix mismatch (the StartsWith check is OrdinalIgnoreCase so case is fine, but a sibling dir prefix collision can occur).","solutions":["Ensure webview callers pass strictly relative paths within the allowed directory; strip leading separators and '..' segments at the JS boundary.","Strengthen IsPathAllowed with a trailing-separator check to prevent prefix collisions (see error 134).","If the allowed directory moved, reconstruct FileAccessBridge with the new path.","Log the offending relativePath and resolved fullPath when denied, for auditing."],"exampleFix":"// before\nprivate bool IsPathAllowed(string path)\n{\n    try\n    {\n        var fullPath = Path.GetFullPath(path);\n        var normalizedPath = fullPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);\n        return normalizedPath.StartsWith(_normalizedAllowedPath, StringComparison.OrdinalIgnoreCase);\n    }\n    catch { return false; }\n}\n\n// after (trailing-separator containment)\nprivate bool IsPathAllowed(string path)\n{\n    try\n    {\n        var fullPath = Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)\n                       + Path.DirectorySeparatorChar;\n        var root = _normalizedAllowedPath + Path.DirectorySeparatorChar;\n        return fullPath.StartsWith(root, StringComparison.OrdinalIgnoreCase);\n    }\n    catch { return false; }\n}","handlingStrategy":"validation","validationCode":"// Harden the sandbox check before calling ReadFile\nrelativePath = relativePath.Replace('\\\\', '/').TrimStart('/');\nif (relativePath.Contains(\"..\"))\n    throw new UnauthorizedAccessException(\"路径含 .. 段\");","typeGuard":"static bool IsPathWithinAllowed(string allowedRoot, string fullPath)\n{\n    var root = allowedRoot.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)\n               + Path.DirectorySeparatorChar;\n    return fullPath.StartsWith(root, StringComparison.OrdinalIgnoreCase);\n}","tryCatchPattern":"catch (UnauthorizedAccessException ex) when (ex.Message.Contains(\"被拒绝\"))\n{\n    _logger.LogWarning(\"WebView 沙箱拒绝读取: {Msg}\", ex.Message);\n    throw;\n}","preventionTips":["Sanitize relativePath at the JS boundary (strip '..', leading separators).","Use trailing-separator containment in IsPathAllowed.","Audit denied reads by logging the resolved path."],"tags":["security","sandbox","path-traversal","webview","cwe-22"],"backgroundTag":null,"analyzedSha":"a7cb36712dcb409be610257d877fcea3597e9d6b","analyzedAt":"2026-08-13T16:44:57.548Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}