paperclipai/paperclip · error · Error

Unsafe report navigation in ${file}

Error message

Unsafe report navigation in ${file}

What it means

Thrown by validatePublicProtocolEvalReport when any href found in a report file (or a rich-attempt navigation href) fails the safe-navigation regex: it contains ?, \, starts with /, or uses an absolute scheme like http:. All internal links must be plain relative paths (optionally with #fragment) so the published site cannot navigate off-host or escape the report root via path tricks.

Source

Thrown at packages/paperclip-runner/scripts/publish-runner-protocol-eval-history.mjs:205

      }
      if (extname(file) !== ".html") continue;
      for (const pattern of ACTIVE_HTML_PATTERNS) {
        if (pattern.test(content))
          throw new Error(
            `Public report contains active or remote HTML: ${file}`,
          );
      }
    }
    const navigation = payload
      ? [
          payload.navigation?.suiteHref,
          payload.navigation?.previous?.href,
          payload.navigation?.next?.href,
        ].filter(Boolean)
      : [];
    for (const href of [...internalHtmlHrefs(content), ...navigation]) {
      if (typeof href !== "string" || /[?:\\]|^\/|^[a-z]+:/i.test(href))
        throw new Error(`Unsafe report navigation in ${file}`);
      const clean = href.split("#", 1)[0].split("?", 1)[0];
      const target = resolve(
        root,
        ...file.split("/").slice(0, -1),
        ...clean.split("/"),
      );
      const rel = relative(root, target).split(sep).join("/");
      if (
        !isPublicProtocolEvalPath(rel) ||
        !(await lstat(target).catch(() => null))?.isFile()
      ) {
        throw new Error(
          `Public report contains a broken or unsafe link in ${file}: ${href}`,
        );
      }
    }
  }
  if (viewer) {

View on GitHub (pinned to 01ad858492)

Solutions

  1. Rewrite the offending link to a relative path without query string, backslashes, or leading slash (e.g. ../index.html instead of /index.html).
  2. Fix the report generator to emit root-relative-free, forward-slash, query-free hrefs.
  3. Strip fragments/queries at generation time and encode external references as plain text rather than anchors.
  4. For rich attempts, fix the navigation payload construction so suiteHref/previous.href/next.href are plain relative paths.

Example fix

// before
<a href="/tests/foo.html?run=7">Foo</a>
// after
<a href="../tests/foo.html">Foo</a>
Defensive patterns

Strategy: validation

Validate before calling

const isSafeHref = (href) =>
  typeof href === "string" && !/[?:\\]|^\/|^[a-z]+:/i.test(href);
export const assertSafeHrefs = (html) => {
  for (const m of html.matchAll(/href\s*=\s*["']([^"']+)["']/giu)) {
    if (m[1] && !m[1].startsWith("#") && !isSafeHref(m[1]))
      throw new Error(`Unsafe href: ${m[1]}`);
  }
};

Type guard

const isSafeHref = (href) =>
  typeof href === "string" && !/[?:\\]|^\/|^[a-z]+:/i.test(href);

Try / catch

try {
  await validatePublicProtocolEvalReport(reportRoot, { viewerRoot });
} catch (err) {
  if (String(err.message).startsWith("Unsafe report navigation in")) {
    console.error("Rewrite the offending link as a plain relative path (no scheme, query, backslash, or leading slash).");
  }
  throw err;
}

Prevention

When it happens

Trigger: A report page links with href="https://...", href="/index.html", href="page.html?x=1", or a backslash-containing path; a rich attempt's navigation.suiteHref/previous/next values built by the viewer payload contain query strings or absolute paths.

Common situations: Template generator emits absolute site-root URLs assuming a server rather than a static bundle; links carry UTM or cache-busting query parameters; Windows-built generator emits backslash separators in hrefs; an agent-authored transcript contains raw markdown links to external sites that get converted to <a href> unmodified.

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 paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/c7b566ab6439bf31. Report an issue: GitHub.