{"record":{"id":"702b01f463f3e484","repo":"different-ai/openwork","slug":"invalid-path-702b01","errorCode":"invalid_path","errorMessage":"Only supported text artifact files can be read inline","messagePattern":"Only supported text artifact files can be read inline","errorType":"http","errorClass":"ApiError","httpStatus":400,"severity":"error","filePath":"apps/server/src/routes/files.ts","lineNumber":842,"sourceCode":"        }\n\n        items.push({ ok: false, type, code: \"invalid_operation\", message: `Unsupported operation type: ${type}` });\n      } catch (error) {\n        const message = error instanceof Error ? error.message : \"Operation failed\";\n        items.push({ ok: false, type, code: \"operation_failed\", message });\n      }\n    }\n\n    const events = fileSessions.listWorkspaceEvents(workspace.id, Number.MAX_SAFE_INTEGER);\n    return jsonResponse({ items, cursor: events.cursor });\n  });\n\n  addRoute(routes, \"GET\", \"/workspace/:id/files/content\", \"client\", async (ctx) => {\n    const workspace = await resolveWorkspace(config, ctx.params.id);\n    const requested = ctx.url.searchParams.get(\"path\") ?? \"\";\n    const relativePath = normalizeWorkspaceRelativePath(requested, { allowSubdirs: true });\n    if (!isSupportedWorkspaceTextFilePath(relativePath)) {\n      throw new ApiError(400, \"invalid_path\", \"Only supported text artifact files can be read inline\");\n    }\n\n    const absPath = resolveSafeChildPath(workspace.path, relativePath);\n    if (!(await exists(absPath))) {\n      throw new ApiError(404, \"file_not_found\", \"File not found\");\n    }\n    const info = await stat(absPath);\n    if (!info.isFile()) {\n      throw new ApiError(404, \"file_not_found\", \"File not found\");\n    }\n\n    const maxBytes = FILE_SESSION_MAX_FILE_BYTES;\n    if (info.size > maxBytes) {\n      throw new ApiError(413, \"file_too_large\", \"File exceeds size limit\", { maxBytes, size: info.size });\n    }\n\n    const content = await readFile(absPath, \"utf8\");\n    return jsonResponse({ path: relativePath, content, bytes: info.size, updatedAt: info.mtimeMs });","sourceCodeStart":824,"sourceCodeEnd":860,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/apps/server/src/routes/files.ts#L824-L860","documentation":"The GET /workspace/:id/files/content endpoint reads a workspace file inline, but only for paths passing isSupportedWorkspaceTextFilePath() — i.e. recognized text artifact file types. Any other path (binary files, unknown extensions, dotfiles, no extension) is rejected with a 400 invalid_path ApiError before existence checks. This prevents dumping binaries or arbitrary files through the inline content API.","triggerScenarios":"Requesting content for a binary file (.png, .zip, .db), an unsupported extension (.exe, .pdf), a path with no extension, or a dotfile like .env; also a misspelled/mis-cased extension that fails the supported-type check.","commonSituations":"A UI tries to preview an uploaded image via the text-content endpoint; a script assumes any path under the workspace is readable; a symlink or generated file has an unexpected extension.","solutions":["Request only supported text artifact paths (e.g. *.md, *.ts, *.json — check isSupportedWorkspaceTextFilePath for the exact allowlist)","Use the appropriate download/asset endpoint for binary files instead of the inline content API","Fix the requested path's extension/case if it is actually a supported type","List workspace files first and filter to supported text paths"],"exampleFix":"// before\nconst res = await api.get(`/workspace/${id}/files/content?path=logo.png`); // 400\n// after\nif (/\\.(md|txt|ts|tsx|js|json|ya?ml|toml)$/.test(path)) {\n  const res = await api.get(`/workspace/${id}/files/content?path=${encodeURIComponent(path)}`);\n} else {\n  const res = await api.get(`/workspace/${id}/files/download?path=${encodeURIComponent(path)}`);\n}","handlingStrategy":"type-guard","validationCode":"const SUPPORTED_TEXT_RE = /\\.(md|txt|ts|tsx|js|jsx|json|ya?ml|toml|css|html|sh)$/i;\nif (!SUPPORTED_TEXT_RE.test(relativePath)) {\n  throw new Error(`use download endpoint for non-text file: ${relativePath}`);\n}","typeGuard":"function isSupportedTextPath(p: string): boolean {\n  return /\\.(md|txt|ts|tsx|js|jsx|json|ya?ml|toml|css|html|sh)$/i.test(p);\n}","tryCatchPattern":"try {\n  const res = await api.get(`/workspace/${id}/files/content?path=${encodeURIComponent(p)}`);\n} catch (e) {\n  if (e.code === \"invalid_path\") {\n    return api.get(`/workspace/${id}/files/download?path=${encodeURIComponent(p)}`); // binary route\n  }\n  throw e;\n}","preventionTips":["Route binary/asset files to the download endpoint, not the inline content API","Maintain a shared allowlist of supported text extensions matching the server's","Check the file extension before requesting inline content"," Beware dotfiles and extensionless paths — they fail the supported-type check"],"tags":["http-400","validation","file-type","workspace"],"backgroundTag":"unsupported-file-type","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}