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
- Sanitize/normalize the input before calling: strip leading slashes, reject `..` segments, and ensure the value is genuinely relative.
- Use `path.relative` yourself first and reject escapes before invoking the API, to fail early with a clearer message.
- 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.
- 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
- Never join untrusted user input onto a trusted directory without this check.
- Strip leading slashes and reject `..` segments before calling the API.
- Resolve symlinks with fs.realpath and re-check containment when symlinks are possible.
- Treat this error as a security signal: log and investigate, do not silently retry.
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
- Vault not configured
- canceled
- Not supported
- Config sync host must use HTTPS (got: ${host})
- No private keys in profile
AI-assisted analysis of Eugeny/tabby@14e2d60b9b (2026-08-12).
Data as JSON: /api/errors/daf5eb3ca83ec138.
Report an issue: GitHub.