Eugeny/tabby · error · Error

Refusing access outside the target directory: ${relativePath

Error message

Refusing access outside the target directory: ${relativePath}

What it means

Thrown by `resolveInsideBase` (a path-traversal guard in the Electron platform service) when resolving `relativePath` against `basePath` would escape `basePath`. The check uses `path.relative`: if the result is `..`, starts with `..` + sep, or is absolute (e.g. `/etc/passwd` on POSIX, `C:\...` on Windows), access is refused. This is a security control preventing directory traversal / absolute-path injection.

Source

Thrown at tabby-electron/src/services/platform.service.ts:33

/* eslint-disable block-scoped-var */

try {
    // eslint-disable-next-line no-var
    var windowsProcessTreeNative = require('@tabby-gang/windows-process-tree/build/Release/windows_process_tree.node')
    // eslint-disable-next-line no-var
    var wnr = require('windows-native-registry')
} catch { }

/**
 * Resolve `relativePath` against `basePath` and ensure the result stays inside `basePath`.
 */
export function resolveInsideBase (basePath: string, relativePath: string): string {
    const base = path.resolve(basePath)
    const target = path.resolve(base, relativePath)
    const rel = path.relative(base, target)
    if (rel !== '' && (rel === '..' || rel.startsWith('..' + path.sep) || path.isAbsolute(rel))) {
        throw new Error(`Refusing access outside the target directory: ${relativePath}`)
    }
    return target
}

@Injectable({ providedIn: 'root' })
export class ElectronPlatformService extends PlatformService {
    supportsWindowControls = true
    private safeExternalSchemes = new Set(['http', 'https', 'ftp', 'mailto'])
    private configPath: string

    constructor (
        private hostApp: ElectronHostAppService,
        private hostWindow: ElectronHostWindow,
        private electron: ElectronService,
        private zone: NgZone,
        private shellIntegration: ShellIntegrationService,
        private translate: TranslateService,
    ) {

View on GitHub (pinned to 14e2d60b9b)

Solutions

  1. Sanitize/normalize the input before calling: strip leading slashes, reject `..` segments, and ensure the value is genuinely relative.
  2. Use `path.relative` yourself first and reject escapes before invoking the API, to fail early with a clearer message.
  3. If the user genuinely needs an out-of-base path, change the contract: pass an absolute trusted path and document it, rather than tunneling through relativePath.
  4. Resolve symlinks with `fs.realpath` and re-check containment if symlinks could escape base.

Example fix

// before
export function resolveInsideBase (basePath: string, relativePath: string): string {
    const base = path.resolve(basePath)
    const target = path.resolve(base, relativePath)
    const rel = path.relative(base, target)
    if (rel !== '' && (rel === '..' || rel.startsWith('..' + path.sep) || path.isAbsolute(rel)))
        throw new Error(`Refusing access outside the target directory: ${relativePath}`)
    return target
}

// caller - pre-sanitize
const safe = relativePath.replace(/^[\/\\]+/, '').replace(/\.{2,}/g, '.')
const resolved = resolveInsideBase(basePath, safe)
Defensive patterns

Strategy: validation

Validate before calling

function isSafeRelativePath (relativePath: string): boolean {
    if (!relativePath) return true
    if (path.isAbsolute(relativePath)) return false
    const segs = relativePath.split(/[\\/]/)
    if (segs.some(s => s === '..')) return false
    return true
}

if (!isSafeRelativePath(relativePath)) {
    throw new Error(`Unsafe relative path: ${relativePath}`)
}
const resolved = resolveInsideBase(basePath, relativePath)

Type guard

function isRelativeInside (basePath: string, relativePath: string): boolean {
    const rel = path.relative(path.resolve(basePath), path.resolve(basePath, relativePath))
    return rel === '' || (!rel.startsWith('..' + path.sep) && rel !== '..' && !path.isAbsolute(rel))
}

Try / catch

try {
    return resolveInsideBase(basePath, relativePath)
} catch (e) {
    if (e instanceof Error && /Refusing access outside the target directory/.test(e.message)) {
        // reject the input; never weaken the check
        return null
    }
    throw e
}

Prevention

When it happens

Trigger: Passing a `relativePath` like `../../etc/passwd`, `/absolute/path`, `..\\..\\secret`, or any value whose resolved position is outside `basePath`. Triggered by IPC handlers, plugin resource loaders, or any code that joins user-supplied input onto a trusted directory.

Common situations: A malicious or buggy profile/URL supplies `../../../`; symlink resolution that lands outside base; Windows absolute paths (`C:\...`) or UNC paths (`\\server\share`) passed as 'relative'; a plugin path that escaped its sandbox dir.

Related errors


AI-assisted analysis of Eugeny/tabby@14e2d60b9b (2026-08-12). Data as JSON: /api/errors/daf5eb3ca83ec138. Report an issue: GitHub.