{"record":{"id":"daf5eb3ca83ec138","repo":"Eugeny/tabby","slug":"refusing-access-outside-the-target-directory-re","errorCode":null,"errorMessage":"Refusing access outside the target directory: ${relativePath}","messagePattern":"Refusing access outside the target directory: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"tabby-electron/src/services/platform.service.ts","lineNumber":33,"sourceCode":"\n/* eslint-disable block-scoped-var */\n\ntry {\n    // eslint-disable-next-line no-var\n    var windowsProcessTreeNative = require('@tabby-gang/windows-process-tree/build/Release/windows_process_tree.node')\n    // eslint-disable-next-line no-var\n    var wnr = require('windows-native-registry')\n} catch { }\n\n/**\n * Resolve `relativePath` against `basePath` and ensure the result stays inside `basePath`.\n */\nexport function resolveInsideBase (basePath: string, relativePath: string): string {\n    const base = path.resolve(basePath)\n    const target = path.resolve(base, relativePath)\n    const rel = path.relative(base, target)\n    if (rel !== '' && (rel === '..' || rel.startsWith('..' + path.sep) || path.isAbsolute(rel))) {\n        throw new Error(`Refusing access outside the target directory: ${relativePath}`)\n    }\n    return target\n}\n\n@Injectable({ providedIn: 'root' })\nexport class ElectronPlatformService extends PlatformService {\n    supportsWindowControls = true\n    private safeExternalSchemes = new Set(['http', 'https', 'ftp', 'mailto'])\n    private configPath: string\n\n    constructor (\n        private hostApp: ElectronHostAppService,\n        private hostWindow: ElectronHostWindow,\n        private electron: ElectronService,\n        private zone: NgZone,\n        private shellIntegration: ShellIntegrationService,\n        private translate: TranslateService,\n    ) {","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/Eugeny/tabby/blob/14e2d60b9b6dee84a53c37f05eefeb803787de04/tabby-electron/src/services/platform.service.ts#L15-L51","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nexport function resolveInsideBase (basePath: string, relativePath: string): string {\n    const base = path.resolve(basePath)\n    const target = path.resolve(base, relativePath)\n    const rel = path.relative(base, target)\n    if (rel !== '' && (rel === '..' || rel.startsWith('..' + path.sep) || path.isAbsolute(rel)))\n        throw new Error(`Refusing access outside the target directory: ${relativePath}`)\n    return target\n}\n\n// caller - pre-sanitize\nconst safe = relativePath.replace(/^[\\/\\\\]+/, '').replace(/\\.{2,}/g, '.')\nconst resolved = resolveInsideBase(basePath, safe)","handlingStrategy":"validation","validationCode":"function isSafeRelativePath (relativePath: string): boolean {\n    if (!relativePath) return true\n    if (path.isAbsolute(relativePath)) return false\n    const segs = relativePath.split(/[\\\\/]/)\n    if (segs.some(s => s === '..')) return false\n    return true\n}\n\nif (!isSafeRelativePath(relativePath)) {\n    throw new Error(`Unsafe relative path: ${relativePath}`)\n}\nconst resolved = resolveInsideBase(basePath, relativePath)","typeGuard":"function isRelativeInside (basePath: string, relativePath: string): boolean {\n    const rel = path.relative(path.resolve(basePath), path.resolve(basePath, relativePath))\n    return rel === '' || (!rel.startsWith('..' + path.sep) && rel !== '..' && !path.isAbsolute(rel))\n}","tryCatchPattern":"try {\n    return resolveInsideBase(basePath, relativePath)\n} catch (e) {\n    if (e instanceof Error && /Refusing access outside the target directory/.test(e.message)) {\n        // reject the input; never weaken the check\n        return null\n    }\n    throw e\n}","preventionTips":["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."],"tags":["security","path-traversal","electron","validation","filesystem"],"backgroundTag":null,"analyzedSha":"14e2d60b9b6dee84a53c37f05eefeb803787de04","analyzedAt":"2026-08-12T11:46:48.773Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}