Repository navigation
fix(api): reject environment folder paths outside the workspace volume #651
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,7 @@ import ( | |
| "io" | ||
| "net/http" | ||
| "net/url" | ||
| pathpkg "path" | ||
| "regexp" | ||
| "strings" | ||
| "time" | ||
|
|
@@ -981,6 +982,11 @@ func (s *Service) ListFolders(ctx context.Context, projectID, environmentID uuid | |
| return s.repo.ListFolders(ctx, env.ID, filter, cursorAfter, limit) | ||
| } | ||
|
|
||
| // environmentHomeRoot is the one directory that survives a container/Pod | ||
| // recreation — the mounted workspace volume every environment folder must | ||
| // live under. Same value agent-runner uses for its own Browse/mkdir guards. | ||
| const environmentHomeRoot = "/home/paca/workspaces" | ||
|
|
||
| // AddFolder creates the folder row and asks agent-runner to mkdir it into | ||
| // place inside the environment's container. Folders are path-only — no | ||
| // name/repo-clone/branch fields (dropped before ever shipping; see | ||
|
|
@@ -991,8 +997,16 @@ func (s *Service) AddFolder(ctx context.Context, projectID, environmentID uuid.U | |
| return nil, err | ||
| } | ||
|
|
||
| path := strings.TrimSpace(in.Path) | ||
| if !strings.HasPrefix(path, "/") { | ||
| path := pathpkg.Clean(strings.TrimSpace(in.Path)) | ||
| // Every folder must live under the mounted workspace volume. A path | ||
| // outside it is accepted by the mkdir below but written to the | ||
| // container's own writable layer, so it disappears on the next | ||
| // Pod/container recreation and every later attach fails with ACP | ||
| // "session/new: Invalid params: invalid directory path". | ||
| // Mirrors agent-runner's environmentHomeRoot | ||
| // (internal/acpbridge/environment_handlers.go), which scopes Browse and | ||
| // the mkdir/delete guards to the same root. | ||
| if path != environmentHomeRoot && !strings.HasPrefix(path, environmentHomeRoot+"/") { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Allowing the workspace root here is inconsistent with agent-runner, which refuses to Technical details# Root carve-out vs. agent-runner's mkdir guard
## Affected sites
- `services/api/internal/service/environment/environment_service.go:1009` — accepts `path == environmentHomeRoot`.
- `services/api/internal/service/environment/environment_service.go:1033-1041` — for a `StatusRunning` env the row is committed (`CreateFolder`, line 1022) *before* the agent-runner call, so the 400 leaves a persisted row.
- `services/agent-runner/internal/acpbridge/environment_handlers.go:597-618` — `forbiddenFolderTargets` includes `environmentHomeRoot`.
- `services/agent-runner/internal/acpbridge/environment_handlers.go:675-679` — `handleCreateEnvironmentFolder` calls `safeFolderTarget` unconditionally, which refuses it.
- `services/api/internal/service/environment/environment_service_test.go:1622` — pins `"root"` as valid; the test's env has `BackendRef == nil`, so the agent-runner leg is never exercised.
## Required outcome
- Root is either rejected consistently at the api boundary, or the full create flow (including the mkdir leg for a running env) succeeds for it.
## Also in this comment block
- `environment_service.go:1006-1008` states agent-runner "scopes Browse and the mkdir/delete guards to the same root." `safeFolderTarget` only refuses a fixed list of top-level directories; it does not scope mkdir to `environmentHomeRoot`. And there is no delete guard/endpoint — `DeleteFolder` is row-only (`environment_service.go:1110`).
## Open questions for the human
- Is a folder whose path is the workspace root a supported object (e.g. as a conversation workdir), or is the dialog default meant to force a subfolder? |
||
| return nil, environmentdom.ErrFolderPathInvalid | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not quite "the same root check the server now enforces": a raw
startsWith(ENVIRONMENT_HOME_ROOT)also accepts prefix siblings like/home/paca/workspaces-otherand uncleaned..segments, both of which the server rejects afterpath.Clean. The server is authoritative so nothing breaks, but the comment overstates the equivalence andEnvironmentFolderPathInvalidis reachable from a client-valid input — consider softening the wording rather than tightening the check.