The picker derived a single browse root from `Path.home().anchor`, so a
user whose profile lives on `C:` could not reach `D:` at all. That was
structural, not a policy filter: `host_fs_tree_root()` returns one path,
`/api/filesystem/defaults` hands it over as `tree_root`, and
`rootDirTree.sanitizeTree` keeps exactly one tree and returns
`[cleaned]` - every sibling of that root was dropped on each update, so
even a hand-injected `D:/` node could not survive. `/filesystem/dirs`
only ever listed children of `C:/`, and the router exposed no endpoint
that enumerates drives.
Enumerate ready drive roots instead:
* `host_dirs.host_browse_roots()` returns `/` on POSIX and every ready
drive on Windows, falling back to the home anchor when none respond so
the picker never renders an empty tree. Probing uses `os.path.isdir`
rather than `GetLogicalDrives` so mapped network drives are covered
too, with no ctypes call.
* `GET /api/filesystem/roots` exposes that list, and
`/api/filesystem/defaults` now also returns `browse_roots`. A
`workspace_root_dir` policy jail still collapses both to that one path,
which is the point of the policy.
* `RootDirSelect` takes `treeRoots` and renders a forest.
`sanitizeTree` keeps one tree per root while still deduping globally -
that set is what fixes antd#37228 duplicate rows, and drive roots are
distinct paths, so global uniqueness is still the right rule.
`ancestorDirPaths` now takes the longest containing root, so `D:/x/y`
expands under `D:/` even though `C:/` is listed first. Single-string
roots keep working, so the existing tests stayed valid.
Second, the hint text promised a sandbox that does not exist off Linux:
`ensure_bubblewrap` returns `not_linux` with "bubblewrap jail is
Linux-only; execute uses plain local_shell", so a non-root `root_dir`
only bounds the agent's *tool* paths while the agent process keeps
running with the server account's own filesystem access. That is a weaker
guarantee than the copy claimed, and worth correcting on its own.
`/api/filesystem/defaults` now reports `jail_enforced`, and
`backendRootDirJailHint` splits into a real-sandbox variant and
`backendRootDirPathLimitHint` for hosts without one. `backendRootDirDesc`
no longer says `/` unconditionally, which was only true on POSIX.
Drive enumeration and `shutil.which` sit behind `_ready_drive_roots()` /
`_bwrap_on_path()` seams: patching `os.path.isdir` or `shutil.which`
globally breaks pytest's own path handling mid-test.
* feat: admin user batch policies, token UX, default FS root, and media push hardening
Add admin user batch enable/disable/delete and resource policies (token quota,
max experts), improve token inputs with K/M presets, default unrestricted
local backends to host filesystem root, and tighten gateway tool-media push
with clearer path-outside-root stream errors.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(workspace): treat Windows drive roots as host-root sentinels
Default FS-root backends use C:/ on Windows; treating that as a scoped
jail put workspaces under the drive root and broke bootstrap/invite tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: jubaoliang <jubaoliang@tencent.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Add a `user_policies` table (migration v14) backing per-user `workspace_root_dir`
(restricts local backend roots) and `token_quota` (lifetime token limit). Token
quota is enforced in the agent runtime via TokenQuotaMiddleware. Extract slash
command parsing into infra/gateway/slash/parser.py and expose policies on the
users API. Frontend adds admin policy UI, storage/chat/experts restyle, and a
shared TabLabel / PageLoading component.
* feat: workspace backend sandboxing, execute-env injection, and ephemeral image refs
Workspace file-I/O hardening across backend and dashboard:
- Host directory sandboxing via root_dir allow-list and denied prefixes
- Execute-environment defaults injected into harness backend specs
- Ephemeral workspace image rematerialization for model calls
- Attachment hint expansion and inbound store improvements
- Portable memory backend and skill workspace catalog updates
- Dashboard UI for agent backend fields and root-dir selection
- Bump harness-agent 0.9.23 / harness-memory 0.9.7; docs + install notes
* feat: add user invitation codes with admin management and redeem flow
- Add user_invites table (migration 009) plus SQLite/Postgres reconcile paths
- Add InviteRepo and InviteService for create/revoke/list/redeem
- Expose public validate/redeem endpoints and admin create/revoke/list routers
- Extract default-agent bootstrap into octop.infra.agents.default_agent
- Add invite error codes and en/zh i18n strings
- Dashboard: invite drawer, login redeem UI, users list, and locales
* fix: add Windows cross-platform guards for pre-existing sandbox/rootfs tests
- Guard POSIX/container-only tests (rootfs-absolute /.octop/... workspace
paths) with posix_only in test_thread_artifacts.py and test_agent_manager.py
- Normalize the rendered workspace path assertion in test_octop_builtin_skills.py
(forward slashes on Windows, not str(tmp_path) backslashes)
- Canonicalize ws_dir before the prefix check in attachment hint/path tests
(tempfile may yield an 8.3 short path on Windows while the resolved
attachment path uses the long form)
These failures predate the user-invites commit and are unrelated to it; the
product path logic is correct.
---------
Co-authored-by: jubaoliang <jubaoliang@tencent.com>
Expose /api/filesystem for lazy directory listing and write probes, wire
RootDirSelect into expert/agent forms, and validate non-/ paths before save.
Co-authored-by: Cursor <cursoragent@cursor.com>