iOfficeAI/AionUi · error · Error
Path traversal blocked: "${relativePath}" resolves outside w
Error message
Path traversal blocked: "${relativePath}" resolves outside workspace What it means
Thrown by resolveRelativePath in HTMLRenderer when a resource path referenced by previewed HTML, after normalization of '.'/'..' segments, resolves to an absolute location outside the trusted workspace root. It is a deliberate security guard against path traversal, not an unexpected failure.
Source
Thrown at packages/desktop/src/renderer/pages/conversation/Preview/components/renderers/HTMLRenderer.tsx:131
* @returns 绝对路径 / Absolute path
*/
export function resolveRelativePath(basePath: string, relativePath: string, workspace?: string): string {
// 去除协议前缀 / Remove protocol prefix
const cleanBasePath = basePath.replace(/^file:\/\//, '');
const baseDir =
cleanBasePath.substring(0, cleanBasePath.lastIndexOf('/') + 1) ||
cleanBasePath.substring(0, cleanBasePath.lastIndexOf('\\') + 1);
// 如果相对路径已经是绝对路径 / If relative path is already absolute
if (relativePath.startsWith('/') || /^[a-zA-Z]:/.test(relativePath)) {
// 先归一化 `.`/`..` 段再做边界检查,避免形如 `<workspace>/../../secret`
// 的字面遍历:它以工作区前缀开头却实际逃逸,未归一时会骗过 isWithinRoot。
// Normalize `.`/`..` before the boundary check: a literal
// `<workspace>/../../secret` starts with the workspace prefix yet escapes
// it, so an un-normalized string would slip past isWithinRoot.
const normalizedAbsolute = normalizeAbsolute(relativePath);
if (workspace && !isWithinRoot(workspace, normalizedAbsolute)) {
throw new Error(`Path traversal blocked: "${relativePath}" resolves outside workspace`);
}
return normalizedAbsolute;
}
// 处理 ./ 和 ../ / Handle ./ and ../
const parts = baseDir.replace(/\\/g, '/').split('/').filter(Boolean);
const relParts = relativePath.replace(/\\/g, '/').split('/');
for (const part of relParts) {
if (part === '..') {
parts.pop();
} else if (part !== '.') {
parts.push(part);
}
}
// 保留 Windows 盘符格式 / Preserve Windows drive letter format
const result = /^[a-zA-Z]:/.test(baseDir) ? parts.join('/') : '/' + parts.join('/');View on GitHub (pinned to 711aa0550e)
Solutions
- Inspect the offending ${relativePath} from the error message and rewrite the reference so it stays under the workspace root
- If the asset legitimately lives outside the workspace, copy it into the workspace (or an assets/ subfolder) before preview
- Verify the workspace value passed to the renderer matches where the HTML artifact actually resides
- Sanitize generated HTML: normalize and re-root relative URLs at generation time
Example fix
<!-- before --> <img src="../../../shared/logo.png" /> <!-- after: move asset into workspace and reference relatively --> <img src="./assets/logo.png" />
Defensive patterns
Strategy: validation
Validate before calling
const normalized = path.normalize(rawPath).replace(/\\/g, '/');
const root = path.normalize(workspace + '/');
if (normalized !== root && !normalized.startsWith(root)) {
// reject before calling absolutePath/resourcePath
throw new Error(`Unsafe resource path: ${rawPath}`);
} Type guard
const isWithinWorkspace = (p: string, root: string): boolean => {
const n = path.normalize(p).replace(/\\/g, '/');
const r = path.normalize(root + '/').replace(/\\/g, '/');
return n === r || n.startsWith(r);
}; Try / catch
try {
const abs = absolutePath(src);
} catch (e) {
if (e instanceof Error && e.message.startsWith('Path traversal blocked')) {
// drop the resource or serve a placeholder, never crash the preview
} else throw e;
} Prevention
- Normalize and re-root all URLs from generated HTML at generation time
- Keep preview artifacts self-contained under the workspace
- Treat traversal errors as resource-dropped, not fatal
When it happens
Trigger: Calling absolutePath/resourcePath with a value like '<workspace>/../../secret.txt' or a symlink-style relative chain that normalizes outside the workspace prefix; any <img src>/<link href> in the previewed HTML whose resolved target leaves the root.
Common situations: Generated HTML artifacts that reference assets via ../../ pointing above the artifact directory, hand-authored HTML with absolute paths from another machine, or a workspace root misconfigured (empty/incorrect) so legitimate paths fail the isWithinRoot check.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
AI-assisted analysis of iOfficeAI/AionUi@711aa0550e (2026-08-28).
Data as JSON: /api/errors/9e1b2aac718a8670.
Report an issue: GitHub.