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

  1. Inspect the offending ${relativePath} from the error message and rewrite the reference so it stays under the workspace root
  2. If the asset legitimately lives outside the workspace, copy it into the workspace (or an assets/ subfolder) before preview
  3. Verify the workspace value passed to the renderer matches where the HTML artifact actually resides
  4. 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

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.