Files
openchamber/packages/web/server/lib/fs/DOCUMENTATION.md
T
Serhii Dziupin 73c2f7bf23 fix(server): allow reading files through workspace-internal symlinks
Read-family fs routes (stat/read/raw/serve) rejected files whose canonical
(realpath) target escaped the project root, so a symlinked folder inside the
workspace (e.g. ~/test_folder -> /shared/test_folder) listed fine but every
file open failed with "Failed to open file".

Resolve symlinks before the containment check: paths that are lexically
inside the active workspace stay readable even when their realpath target
lives outside it, while direct paths outside the workspace (including
traversal and canonical-path requests) remain rejected and write/exec keep
the strict canonical boundary. The directory listing now returns entry
paths under the requested (user-visible) directory so the file tree hands
back addressable paths instead of canonical ones.

Refs OPE-235
2026-08-21 01:40:11 +02:00

44 lines
3.7 KiB
Markdown

# FS Module Documentation
## Purpose
Own filesystem API behavior for the web server runtime, including workspace-bound file operations, directory listing, reveal, and background command execution jobs.
## Entrypoints and structure
- `packages/web/server/lib/fs/routes.js`: route registration and runtime-owned state for `/api/fs/*` endpoints.
- `packages/web/server/lib/fs/search.js`: fuzzy filesystem search runtime used by non-FS routes (for example project icon discovery).
## Public exports
- `registerFsRoutes(app, dependencies)` from `routes.js`
- Registers all filesystem routes:
- `GET /api/fs/home`
- `POST /api/fs/mkdir`
- `GET /api/fs/read`
- `GET /api/fs/raw`
- `GET /api/fs/serve/:path(*)`
- `POST /api/fs/write`
- `POST /api/fs/upload`
- `POST /api/fs/delete`
- `POST /api/fs/rename`
- `POST /api/fs/reveal`
- `POST /api/fs/exec`
- `GET /api/fs/exec/:jobId`
- `GET /api/fs/list`
- Owns exec job queue state (`execJobs`) and lifecycle/TTL pruning.
- Enforces workspace boundary checks with active project + worktree fallback support.
- Read-family endpoints (`stat`, `read`, `raw`, `serve`) resolve symlinks before serving and allow paths that are lexically inside the active workspace even when their canonical (realpath) target lives outside it through a workspace-internal symlink (for example `~/test_folder -> /shared/test_folder`). Paths that are not inside the workspace lexically are still rejected before any symlink resolution, and write/exec operations keep the strict canonical containment check.
- `createFsSearchRuntime({ fsPromises, path, spawn, resolveGitBinaryForSpawn })` from `search.js`
- Returns `{ searchFilesystemFiles(rootPath, options) }`.
- Supports fuzzy matching, hidden-file handling, and optional `git check-ignore` filtering.
## Composition contract with `index.js`
- `index.js` provides composition-time dependencies only (platform primitives + callbacks such as `resolveProjectDirectory`, `normalizeDirectoryPath`, and `buildAugmentedPath`).
- `index.js` no longer owns FS route handlers or FS exec job state.
## Notes for contributors
- Keep filesystem policy (workspace root checks, error mapping, exec timeout behavior) inside this module, not in the composition root.
- Filesystem `EPERM`/`EACCES` failures use the stable `reason: "os-permission"` response marker. Policy denials such as workspace-boundary or missing-grant failures must not use that marker because a native folder picker cannot remediate them.
- Read-only routes authorize the requested path against the workspace before resolving symlinks. A symlink reached through the workspace may therefore target a file outside it, while a directly requested outside path still requires an exact-path grant. Write routes keep canonical-target boundary checks.
- If adding new `/api/fs/*` endpoints, add them in `routes.js` and extend this document.
- `GET /api/fs/list` may resolve symlinks with `realpath` to read directory contents, but the response `path` and each entry `path` must stay in the caller's requested path space (`path.join(requestedPath, name)`). Returning real paths breaks file-tree expansion for directories reached through workspace symlinks.
- `POST /api/fs/upload` accepts one `application/octet-stream` body with `path` and optional `overwrite=true` query parameters. The body streams into a same-directory temp file with a 100 MiB default cap configurable through `OPENCHAMBER_FS_UPLOAD_MAX_BYTES`; failed and oversized uploads clean up that temp file. New files commit through an atomic no-replace link, existing files return `409` unless overwrite is explicit, directory targets are rejected, and the destination parent resolves before writing so uploads cannot escape through workspace symlinks.