Skip to content

fix(server): restrict HTTP file read/list to allowed paths#46

Merged
xiami762 merged 5 commits intoAgentFlocks:mainfrom
y4sol:fix/http-file-read-guard
Apr 17, 2026
Merged

fix(server): restrict HTTP file read/list to allowed paths#46
xiami762 merged 5 commits intoAgentFlocks:mainfrom
y4sol:fix/http-file-read-guard

Conversation

@y4sol
Copy link
Copy Markdown
Contributor

@y4sol y4sol commented Apr 3, 2026

Summary

Restricts filesystem access through GET /api/file/content and GET /api/file/list by validating paths only in the HTTP routes. Internal File.read() callers (e.g. memory under user data directories) are unchanged.

Problem

Clients that could reach the API could read arbitrary local files the server process could open.

Solution

  • Add flocks/utils/http_file_read_guard.py with resolve_path_for_http_file_access():
    • Allowed roots: Flocks project directory (ancestor containing .flocks/, without falling back to cwd as a project root when none exists), configured data directory, workspace directory, optional allowReadPaths entries (validated), and a small built-in allowlist for common safe host files (e.g. /etc/hosts when they exist).
    • Denied: Flocks config tree, ~/.ssh, and on POSIX /proc, /sys, /dev.
    • Uses assert_sandbox_path for containment and symlink checks.
  • Wire the guard into flocks/server/routes/file.py for /content and /list; PermissionError → HTTP 403.
  • Add ConfigInfo.allow_read_paths with Pydantic alias allowReadPaths for JSON config.
  • Add find_flocks_project_root() (returns None if no .flocks ancestor); find_project_root() keeps backward-compatible behavior for non-security callers.

Configuration

Optional extra paths in user config, e.g. ~/.flocks/config/flocks.json:

{
  "allowReadPaths": ["/opt/myapp/config"]
}

Validate paths only at GET /api/file/content and /api/file/list via resolve_path_for_http_file_access.

- Add http_file_read_guard: project root (no unsafe cwd fallback), data, workspace, allowReadPaths, small safe system file allowlist; block config, ~/.ssh, /proc|/sys|/dev; use assert_sandbox_path for symlink-safe containment.
- ConfigInfo.allow_read_paths with alias allowReadPaths.
- find_flocks_project_root() for strict discovery; find_project_root() delegates for backward compat.
- Map PermissionError to HTTP 403.
@y4sol y4sol force-pushed the fix/http-file-read-guard branch from 972b351 to e8a493b Compare April 3, 2026 11:46
…ocks#35)

Replace broken `flocks.sandbox.paths.assert_sandbox_path` import (module
does not exist) with a self-contained `_assert_path_contained()` that
uses `Path.resolve()` + `is_relative_to()` to prevent symlink-escape
attacks.

- http_file_read_guard: remove non-existent sandbox dependency, add
  path-length limit (4096), keep deny-list (config dir, ~/.ssh,
  /proc, /sys, /dev) and safe-system-file allowlist
- CORS: replace `allow_origins=["*"]` with `allow_origin_regex` that
  only matches localhost; users can override via `server.cors` config
- /find/text: pin search to project root, add `-F` (fixed-string) and
  `--` (end-of-options) to grep to prevent injection
- /search: reject queries containing shell metacharacters
- Log denied paths for security auditing

Closes AgentFlocks#35

Made-with: Cursor
Follow-up to the initial HTTP file read guard (8735758).

CORS:
- Auto-detect --webui-host/--webui-port via env vars so remote access
  works without manual server.cors configuration.
- Fix: 0.0.0.0 + empty port would generate a regex matching any origin;
  now requires both host and port to enter auto-detection branch.
- Always include localhost regex as fallback alongside explicit origins.

Path guard:
- Add explicit null-byte check before os.path.normpath (which silently
  replaces \x00 with space instead of rejecting).
- Remove redundant Path.resolve() in _blocked_for_http_read since the
  input is already a resolved absolute path.

Made-with: Cursor
Defer CORS initialization, tighten 0.0.0.0 handling, and relax
over-aggressive input validation that regressed legitimate callers.

* server/app.py: read `server.cors` synchronously from `flocks.json` to
  avoid `asyncio.run(Config.get())` inside a running event loop (which
  crashed when pytest-asyncio fixtures imported the app). Wrap
  `CORSMiddleware` with `_DeferredCORSMiddleware` so the config lookup
  — and `Config.get_global()`'s HOME caching — is deferred until the
  first request, letting test harnesses monkey-patch HOME beforehand.
  Remove the auto-whitelist branch for `_FLOCKS_WEBUI_HOST=0.0.0.0`: it
  produced `^https?://[^/]+:<port>$`, which accepted every host on the
  matching port and effectively disabled CORS. Remote deployments that
  run `--webui-host 0.0.0.0` must now set `server.cors` explicitly.
* server/routes/file.py: drop the `;|\`$` metacharacter blacklist on
  `/api/file/search` — `File.search` uses `subprocess.run(shell=False)`
  with an argv list, so these characters never reach a shell and the
  check only rejected legitimate filenames. Keep the length cap and
  null-byte guard. Revert `/api/file/find/text` from `grep -F` back to
  regex mode to preserve the historical contract; retain the `--`
  sentinel to block option injection.

Made-with: Cursor
@xiami762 xiami762 merged commit 273897c into AgentFlocks:main Apr 17, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants