paperclipai/paperclip · error · HttpError
outside_workspace
outside_workspace
Error message
Workspace file path is outside the workspace
What it means
normalizeWorkspaceRelativePath rejected a candidate workspace path before any filesystem access: it is empty, over MAX_RELATIVE_PATH_BYTES, contains NUL, or uses a file:// URL (each reported as code invalid_path). This is pure input validation on the caller-supplied relative path string, not a filesystem condition.
Source
Thrown at server/src/services/workspace-file-resources.ts:303
}
function normalizeWorkspaceRelativePath(input: string): NormalizedPath {
const trimmed = input.trim();
if (!trimmed) throw unprocessable("Workspace file path is required", { code: "invalid_path" });
if (Buffer.byteLength(trimmed, "utf8") > MAX_RELATIVE_PATH_BYTES) {
throw unprocessable("Workspace file path is too long", { code: "invalid_path" });
}
if (trimmed.includes("\0")) throw unprocessable("Workspace file path contains an invalid character", { code: "invalid_path" });
if (/^file:\/\//i.test(trimmed)) throw unprocessable("File URLs are not supported", { code: "invalid_path" });
if (/^[a-zA-Z]:/.test(trimmed)) throw unprocessable("Windows drive paths are not supported", { code: "invalid_path" });
if (trimmed.startsWith("~")) throw unprocessable("Home-relative paths are not supported", { code: "invalid_path" });
if (trimmed.includes("\\")) throw unprocessable("Workspace file paths must use forward slashes", { code: "invalid_path" });
if (path.posix.isAbsolute(trimmed)) throw unprocessable("Workspace file path must be relative", { code: "invalid_path" });
const normalizedRaw = path.posix.normalize(trimmed);
const normalized = normalizedRaw.endsWith("/") ? normalizedRaw.replace(/\/+$/, "") : normalizedRaw;
if (normalized === "." || normalized === ".." || normalized.startsWith("../")) {
throw new HttpError(403, "Workspace file path is outside the workspace", { code: "outside_workspace" });
}
return {
relativePath: normalized,
segments: normalized.split("/").filter(Boolean),
};
}
function denyReasonForPathSegments(segments: string[]): string | null {
const lowerSegments = segments.map((segment) => segment.toLowerCase());
if (lowerSegments.some((segment) => DENIED_SEGMENTS.has(segment))) return "denied_path_segment";
const fileName = lowerSegments.at(-1) ?? "";
if (fileName === ".env" || fileName.startsWith(".env.")) return "denied_secret";
if (fileName.endsWith(".pem") || fileName.endsWith(".key") || fileName.endsWith(".p12") || fileName.endsWith(".pfx")) {
return "denied_secret";
}
if (["id_rsa", "id_ed25519", ".npmrc", ".pypirc", ".netrc", "kubeconfig"].includes(fileName)) return "denied_secret";View on GitHub (pinned to 120ae5428f)
Solutions
- Use a path inside the workspace root; path traversal outside the workspace is rejected.
Defensive patterns
Strategy: validation
When it happens
Trigger: Thrown at server/src/services/workspace-file-resources.ts:303 when the library encounters an invalid state.
Common situations: See trigger scenarios.
AI-assisted analysis of paperclipai/paperclip@120ae5428f (2026-08-18).
Data as JSON: /api/errors/e5f15ccd6c14b4ca.
Report an issue: GitHub.