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
- Rewrite the offending link to a relative path without query string, backslashes, or leading slash (e.g. ../index.html instead of /index.html).
- Fix the report generator to emit root-relative-free, forward-slash, query-free hrefs.
- Strip fragments/queries at generation time and encode external references as plain text rather than anchors.
- 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
- Generate hrefs with a helper that normalizes to forward-slash relative paths.
- Never emit query strings or cache-busting parameters in report links.
- Test report generation on both POSIX and Windows so separators stay forward slashes.
- Convert external references in transcripts to plain text, not anchors.
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
- Public report contains active or remote HTML: ${file}
- Public report contains a broken or unsafe link in ${file}: $
- Invalid GitHub launcher run ID
- codex auth cache: account_id is not a valid account handle
- Public viewer page differs from the trusted shell
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/c7b566ab6439bf31.
Report an issue: GitHub.