{"record":{"id":"e5f15ccd6c14b4ca","repo":"paperclipai/paperclip","slug":"outside-workspace","errorCode":"outside_workspace","errorMessage":"Workspace file path is outside the workspace","messagePattern":"Workspace file path is outside the workspace","errorType":"http","errorClass":"HttpError","httpStatus":403,"severity":"error","filePath":"server/src/services/workspace-file-resources.ts","lineNumber":303,"sourceCode":"}\n\nfunction normalizeWorkspaceRelativePath(input: string): NormalizedPath {\n  const trimmed = input.trim();\n  if (!trimmed) throw unprocessable(\"Workspace file path is required\", { code: \"invalid_path\" });\n  if (Buffer.byteLength(trimmed, \"utf8\") > MAX_RELATIVE_PATH_BYTES) {\n    throw unprocessable(\"Workspace file path is too long\", { code: \"invalid_path\" });\n  }\n  if (trimmed.includes(\"\\0\")) throw unprocessable(\"Workspace file path contains an invalid character\", { code: \"invalid_path\" });\n  if (/^file:\\/\\//i.test(trimmed)) throw unprocessable(\"File URLs are not supported\", { code: \"invalid_path\" });\n  if (/^[a-zA-Z]:/.test(trimmed)) throw unprocessable(\"Windows drive paths are not supported\", { code: \"invalid_path\" });\n  if (trimmed.startsWith(\"~\")) throw unprocessable(\"Home-relative paths are not supported\", { code: \"invalid_path\" });\n  if (trimmed.includes(\"\\\\\")) throw unprocessable(\"Workspace file paths must use forward slashes\", { code: \"invalid_path\" });\n  if (path.posix.isAbsolute(trimmed)) throw unprocessable(\"Workspace file path must be relative\", { code: \"invalid_path\" });\n\n  const normalizedRaw = path.posix.normalize(trimmed);\n  const normalized = normalizedRaw.endsWith(\"/\") ? normalizedRaw.replace(/\\/+$/, \"\") : normalizedRaw;\n  if (normalized === \".\" || normalized === \"..\" || normalized.startsWith(\"../\")) {\n    throw new HttpError(403, \"Workspace file path is outside the workspace\", { code: \"outside_workspace\" });\n  }\n\n  return {\n    relativePath: normalized,\n    segments: normalized.split(\"/\").filter(Boolean),\n  };\n}\n\nfunction denyReasonForPathSegments(segments: string[]): string | null {\n  const lowerSegments = segments.map((segment) => segment.toLowerCase());\n  if (lowerSegments.some((segment) => DENIED_SEGMENTS.has(segment))) return \"denied_path_segment\";\n\n  const fileName = lowerSegments.at(-1) ?? \"\";\n  if (fileName === \".env\" || fileName.startsWith(\".env.\")) return \"denied_secret\";\n  if (fileName.endsWith(\".pem\") || fileName.endsWith(\".key\") || fileName.endsWith(\".p12\") || fileName.endsWith(\".pfx\")) {\n    return \"denied_secret\";\n  }\n  if ([\"id_rsa\", \"id_ed25519\", \".npmrc\", \".pypirc\", \".netrc\", \"kubeconfig\"].includes(fileName)) return \"denied_secret\";","sourceCodeStart":285,"sourceCodeEnd":321,"githubUrl":"https://github.com/paperclipai/paperclip/blob/120ae5428fa29bee300bcf806491cd4d965fbb7c/server/src/services/workspace-file-resources.ts#L285-L321","documentation":"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.","triggerScenarios":"Thrown at server/src/services/workspace-file-resources.ts:303 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Use a path inside the workspace root; path traversal outside the workspace is rejected."],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"120ae5428fa29bee300bcf806491cd4d965fbb7c","analyzedAt":"2026-08-18T22:49:45.177Z","contentChangedAt":"2026-08-18T22:49:45.177Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}