You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
## Summary
CodeQL flagged 58 open `py/path-injection` alerts. Three were real gaps,
now fixed; the other 41 were false positives (paths already fully
validated upstream by an existing sanitizer CodeQL's dataflow engine
doesn't recognize) — dismissed individually with a per-alert
justification.
- **Hub agent install directory traversal**:
`agent_install_dir()`/`_backup_dir()` joined `agent_id` onto
`~/.gaia/agents/` for `rmtree`/`move`/write with no shape check. An id
like `../../evil` (reachable via `POST /api/agents/install` or a corrupt
sentinel) could point install/uninstall/rollback/configure at an
arbitrary directory. Now rejected with `InstallError` unless the id is a
single safe path component.
- **`safe_open_document` symlink-directory escape**: the home-directory
containment check used a lexical `os.path.abspath` comparison, which an
intermediate symlinked directory *inside* home (pointing outside) could
bypass — `O_NOFOLLOW` only blocks a symlinked *final* path component.
Added an `os.path.realpath`-based physical containment check.
- **EMR `compute_file_hash()` missing its own guard**: three call sites
in `gaia_agent_emr/agent.py` omitted the existing `allowed_dir`
parameter, so the hashing helper had no containment check at all if
reached with an untrusted path.
### Files fixed (with traversal tests)
- `src/gaia/hub/installer.py`, `src/gaia/hub/lifecycle.py`,
`src/gaia/ui/routers/hub.py` — new `_require_safe_agent_id()` guard +
sentinel `executable` field validation. Tests:
`tests/unit/test_hub_installer.py::TestAgentIdPathSafety` (traversal,
absolute path, null byte, oversized id, non-string id, sentinel with
unsafe executable).
- `src/gaia/ui/utils.py` — realpath containment check in
`safe_open_document()`. Test:
`tests/unit/chat/ui/test_toctou.py::test_safe_open_rejects_symlinked_directory_escape`.
- `hub/agents/python/emr/gaia_agent_emr/agent.py` — pass `allowed_dir`
at all three `compute_file_hash()` call sites. Tests:
`tests/unit/test_file_watcher.py::TestFileHashUtilities` (traversal,
absolute escape, contained-path-still-works).
### Alerts dismissed as false positive (41, listed by number — each has
a per-alert justification in its dismissal comment)
- `documents.py` upload-path lock key + `index_folder()`: 231, 184–188
(path already gated by `safe_open_document`/`ensure_within_home`
downstream)
- `files.py` browse/search/preview/image endpoints: 189–206, 244–248
(all gated by `ensure_within_home()`, or the search scan roots are
hardcoded to home subdirs with the user param only a filename substring
filter)
- `server.py` SPA static asset serving: 341, 342 (gated by
`sanitize_static_path()`, a real containment-checking sanitizer)
- `utils.py` sanitizer return: 209 (the sanitizer's own
already-validated return value)
- `file_watcher.py` `FileWatcher.__init__`: 315 (no caller passes
remote/HTTP-derived input)
- EMR dashboard/agent: 328–335 (watch_dir is set only via a
fully-validated setter — regex allowlist, normpath/abspath, realpath
symlink check, home-dir prefix, sensitive-dir denylist — before any of
these lines run)
## Test plan
- [ ] `PYTHONPATH=src python -m pytest tests/unit/test_hub_installer.py
tests/unit/test_hub_lifecycle.py tests/unit/test_hub_router.py
tests/unit/test_hub_security.py tests/unit/chat/ui/test_toctou.py
tests/unit/test_file_watcher.py -q` — 203 passed
- [ ] `python util/lint.py --all --fix` — all checks pass
0 commit comments