babalae/better-genshin-impact · error · UnauthorizedAccessException

访问路径 '{relativePath}' 被拒绝

Error message

访问路径 '{relativePath}' 被拒绝

What it means

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.

Source

Thrown at BetterGenshinImpact/Core/Script/WebView/FileAccessBridge.cs:62

        try
        {
            var fullPath = Path.GetFullPath(path);
            var normalizedPath = fullPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
            return normalizedPath.StartsWith(_normalizedAllowedPath, StringComparison.OrdinalIgnoreCase);
        }
        catch
        {
            return false;
        }
    }

    public string ReadFile(string relativePath)
    {
        try
        {
            var fullPath = Path.Combine(_allowedDirectory, relativePath);
            if (!IsPathAllowed(fullPath))
                throw new UnauthorizedAccessException($"访问路径 '{relativePath}' 被拒绝");

            if (!File.Exists(fullPath))
                throw new FileNotFoundException($"文件 '{relativePath}' 不存在");

            return File.ReadAllText(fullPath, Encoding.UTF8);
        }
        catch (Exception ex)
        {
            throw new Exception($"读取文件失败: {ex.Message}");
        }
    }

    public void WriteFile(string relativePath, string content)
    {
        try
        {
            var fullPath = Path.Combine(_allowedDirectory, relativePath);
            if (!IsPathAllowed(fullPath))

View on GitHub (pinned to a7cb36712d)

Solutions

  1. Ensure webview callers pass strictly relative paths within the allowed directory; strip leading separators and '..' segments at the JS boundary.
  2. Strengthen IsPathAllowed with a trailing-separator check to prevent prefix collisions (see error 134).
  3. If the allowed directory moved, reconstruct FileAccessBridge with the new path.
  4. Log the offending relativePath and resolved fullPath when denied, for auditing.

Example fix

// before
private bool IsPathAllowed(string path)
{
    try
    {
        var fullPath = Path.GetFullPath(path);
        var normalizedPath = fullPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
        return normalizedPath.StartsWith(_normalizedAllowedPath, StringComparison.OrdinalIgnoreCase);
    }
    catch { return false; }
}

// after (trailing-separator containment)
private bool IsPathAllowed(string path)
{
    try
    {
        var fullPath = Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
                       + Path.DirectorySeparatorChar;
        var root = _normalizedAllowedPath + Path.DirectorySeparatorChar;
        return fullPath.StartsWith(root, StringComparison.OrdinalIgnoreCase);
    }
    catch { return false; }
}
Defensive patterns

Strategy: validation

Validate before calling

// Harden the sandbox check before calling ReadFile
relativePath = relativePath.Replace('\\', '/').TrimStart('/');
if (relativePath.Contains(".."))
    throw new UnauthorizedAccessException("路径含 .. 段");

Type guard

static bool IsPathWithinAllowed(string allowedRoot, string fullPath)
{
    var root = allowedRoot.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
               + Path.DirectorySeparatorChar;
    return fullPath.StartsWith(root, StringComparison.OrdinalIgnoreCase);
}

Try / catch

catch (UnauthorizedAccessException ex) when (ex.Message.Contains("被拒绝"))
{
    _logger.LogWarning("WebView 沙箱拒绝读取: {Msg}", ex.Message);
    throw;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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).

Related errors


AI-assisted analysis of babalae/better-genshin-impact@a7cb36712d (2026-08-13). Data as JSON: /api/errors/8e42a195df90d112. Report an issue: GitHub.