paperclipai/paperclip · error · Error
Refusing public report symlink ${absolute}
Error message
Refusing public report symlink ${absolute} What it means
Thrown by relativeFiles in the publish script while walking the report directory: symbolic links are refused outright so the published public report cannot leak or point at files outside the report root. Symlinks (including directory symlinks) abort the publish.
Source
Thrown at packages/paperclip-runner/scripts/publish-runner-protocol-eval-history.mjs:124
if (
relativePath.includes("\\") ||
relativePath.startsWith("/") ||
relativePath
.split("/")
.some((segment) => !segment || segment === "." || segment === "..")
) {
return false;
}
return SAFE_REPORT_PATHS.some((pattern) => pattern.test(relativePath));
}
async function relativeFiles(root, current = root) {
const entries = await readdir(current, { withFileTypes: true });
const files = [];
for (const entry of entries) {
const absolute = join(current, entry.name);
if (entry.isSymbolicLink())
throw new Error(`Refusing public report symlink ${absolute}`);
if (entry.isDirectory())
files.push(...(await relativeFiles(root, absolute)));
else if (entry.isFile())
files.push(relative(root, absolute).split(sep).join("/"));
else throw new Error(`Refusing unusual public report path ${absolute}`);
}
return files.sort();
}
function internalHtmlHrefs(content) {
return [...content.matchAll(/href\s*=\s*["']([^"']+)["']/giu)]
.map((match) => match[1])
.filter((href) => href && !href.startsWith("#"));
}
export async function validatePublicProtocolEvalReport(
reportRoot,
{ viewerRoot } = {},View on GitHub (pinned to 01ad858492)
Solutions
- Remove symlinks from the report directory or replace them with real copies before publishing.
- Build the report with real files only (e.g. cp -L to dereference when copying).
- Point the publish script at a clean report directory without symlinked entries.
Example fix
// before ln -s ./runs/v3 report/latest // after cp -L ./runs/v3/summary.json report/latest-summary.json
Defensive patterns
Strategy: validation
Validate before calling
for (const p of walk(reportDir)) {
const st = fs.lstatSync(p);
if (st.isSymbolicLink()) throw new Error(`symlink in report dir: ${p}`);
} Try / catch
try { await publish(reportDir); } catch (e) { if (e.message.startsWith('Refusing public report symlink')) { console.error('Replace symlinks in the report directory with real files (cp -L).'); process.exit(1); } throw e; } Prevention
- Build report artifacts with dereferenced copies (cp -L / rsync without --links).
- Scan the report directory for symlinks before publishing.
- Avoid creating 'latest' symlink shortcuts inside published folders.
When it happens
Trigger: Running the publish against a report directory that contains a symlinked file or subdirectory anywhere in the tree.
Common situations: A build step creates convenience symlinks (e.g. 'latest' -> 'v3') inside the report folder; checking out fixtures containing symlinks on macOS/Linux; copying directories with cp -s or rsync --links.
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
- Materialized OpenCode executable has unsafe permissions
- A trusted viewer build is required for public chat reports
- Trusted viewer must not use symlinks
- Unexpected trusted viewer asset
- native_runtime_context_entry_outside_bundle
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/b93a8e73f37e85d7.
Report an issue: GitHub.