paperclipai/paperclip · error · Error
${label} escapes its declared root
Error message
${label} escapes its declared root What it means
inside() resolves a candidate path against a declared root and rejects any path that escapes that root: empty relative result (candidate equals root), '..', or any path starting with '../'. It is a path-traversal guard applied to campaign roster and config paths so untrusted JSON cannot reference files outside the eval program directory.
Source
Thrown at packages/paperclip-runner/scripts/runner-protocol-eval-campaign.mjs:52
async function loadObject(path) {
const value = JSON.parse(await readFile(path, "utf8"));
if (value === null || Array.isArray(value) || typeof value !== "object") {
throw new Error(`Expected a JSON object: ${path}`);
}
return value;
}
function safeId(value, label = "identifier") {
const result = String(value ?? "");
if (!SAFE_ID.test(result)) throw new Error(`Unsafe ${label}: ${result}`);
return result;
}
function inside(root, candidate, label) {
const rel = relative(resolve(root), resolve(candidate));
if (!rel || rel === ".." || rel.startsWith(`..${sep}`)) {
throw new Error(`${label} escapes its declared root`);
}
return resolve(candidate);
}
export function credentialForConfig(config) {
if (config.provider === "opencode") return "OPENROUTER_API_KEY";
if (config.provider === "claude_managed") return "ANTHROPIC_API_KEY";
if (config.provider === "aws_agentcore") return "AWS_AGENTCORE_OIDC";
if (config.provider === "codex" || config.provider === undefined) {
return "OPENAI_API_KEY";
}
if (config.provider === "acpx") {
if (config.acpxAgent === "pi") return "OPENROUTER_API_KEY";
if (config.acpxAgent === "claude") return "ANTHROPIC_API_KEY";
if (config.acpxAgent === "codex") return "OPENAI_API_KEY";
}
throw new Error(
`No credential policy for ${config.provider ?? "codex"}/${config.acpxAgent ?? "default"}`,View on GitHub (pinned to 01ad858492)
Solutions
- Move the referenced file inside the declared root directory and update the JSON to a relative path within it.
- Remove any '..' segments or absolute paths from roster/config fields in the campaign file.
- If a file must be shared, symlink or copy it into the program root instead of referencing it across the boundary.
Example fix
// before "roster": "../../shared/roster.json" // after: copy roster.json into evals/paperclip-runner/rosters/ and use "roster": "rosters/roster.json"
Defensive patterns
Strategy: validation
Validate before calling
const rel = relative(resolve(root), resolve(candidate));
if (!rel || rel === ".." || rel.startsWith(`..${sep}`)) throw new Error(`${label} escapes its declared root`); Type guard
const staysInside = (root, candidate) => {
const rel = relative(resolve(root), resolve(candidate));
return Boolean(rel) && rel !== ".." && !rel.startsWith(`..${require("path").sep}`);
}; Try / catch
try {
rosterPath = inside(programRoot, lane.roster, "Campaign roster");
} catch (err) {
if (err.message.endsWith("escapes its declared root")) console.error(`Use a path relative to and inside the program root for: ${lane.roster}`);
throw err;
} Prevention
- Always reference roster/config files with paths relative to the program root.
- Never use '..' or absolute paths in campaign JSON path fields.
- Re-verify relative paths after moving or renaming campaign files.
When it happens
Trigger: Calling inside(root, candidate, label) where candidate is absolute outside root, uses '..' segments, or equals the root itself — e.g. lane.roster = '../secrets/roster.json' or an absolute path to another directory.
Common situations: A roster or config field in campaign JSON referencing a shared file via '../'; moving the campaign file so relative paths now resolve outside the root; typos like '../../rosters/x.json'; malicious or accidentally absolute paths.
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
- sandbox runtime asset key is not a simple path segment: ${ke
- Unsafe protocol eval campaign ID
- Invalid canonical workspace path
- Access denied
- UI parser path escapes package directory — skipping
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/3b90734388d7f744.
Report an issue: GitHub.