{"id":"PYSEC-2026-3875","summary":"Omnigent: Unvalidated os_env.cwd in agent bundle yields arbitrary host filesystem access on runners without OMNIGENT_RUNNER_WORKSPACE","details":"### Summary\n\nAn authenticated, non-admin user can obtain **arbitrary host-filesystem read/write** (and host environment-secret disclosure) on an Omnigent **runner** by uploading an agent bundle whose `os_env.cwd` points outside any intended workspace (e.g. `/` or `/home/\u003cvictim\u003e`). The `cwd` field is taken **verbatim** from the bundle with no validation, normalization, or boundary check anywhere in the spec pipeline.\n\nThis is a **different sink** from GHSA-jrrm-9hc7-2v3h (CWE-94, shared-agent bundle overwrite -\u003e stdio MCP RCE). It shares the bundle-upload *vector* but is reached through the user's **own** session-scoped agent and is **not** addressed by that advisory's proposed shared-agent guard.\n\n### Preconditions\n\n- Runner realizes a session-scoped uploaded bundle **without** `OMNIGENT_RUNNER_WORKSPACE` set. When that env var is set (CLI- and host-launched sessions set it), the spec `cwd` is overridden and the attack is neutralized — so this is deployment-gated, not universal.\n- Attacker is any authenticated user (no admin scope; `_require_user` only checks identity). No shared-agent overwrite needed.\n\n### Details (verified against code)\n\n1. **Parse — no validation.** `omnigent/spec/parser.py:696` stores `cwd=str(cwd_raw)` verbatim. Absolute paths (`/`, `/etc`), `../..`, etc. are all accepted. The `sandbox.type` is likewise author-chosen and `\"none\"` is legal.\n2. **Validate — cwd unconstrained.** `omnigent/spec/validator.py` `_validate_os_env` checks only fork/scratch/egress combinations; it never references `cwd` (the sole mention, line ~526, is a comment). No boundary is applied to the cwd itself. The boundary in `server/schemas.py` validates a *caller-supplied workspace against* the spec cwd (treating the author cwd as trusted) and only for host-launched sessions — it does not bound the cwd.\n3. **Sink.** `omnigent/inner/os_env.py:890` sets `cwd = Path(spec.cwd or os.getcwd()).resolve(strict=False)` as the environment root; `os_env.py:934` does `shutil.copytree(src=cwd, ...)` when `fork=true`. All agent file/shell tools are bounded by `_assert_within_cwd` (`os_env.py:1040`), which checks `resolved.relative_to(cwd)` — but since **cwd is attacker-controlled**, `cwd=/` makes the entire host filesystem in-bounds for read and write; `fork=true` with `cwd=/home/victim` copies that tree into the agent-readable workspace.\n4. **Decisive gate.** `omnigent/runner/resource_registry.py:648-654`: `cwd = default_cwd` only when `self._runner_workspace is not None or spec_os_env.cwd in (None, \".\", \"./\")`; otherwise `cwd = spec_os_env.cwd` (the attacker's absolute path). So `OMNIGENT_RUNNER_WORKSPACE` is the only thing standing between the spec and the host FS — and it is an operational control, not an in-code guard. A code comment at `tool_dispatch.py:~4207` claims cwd \"is treated as a boundary at session-create time,\" which is not true on this path.\n\n### Attack path\n\n1. Authenticated user sends `POST /v1/sessions` (multipart) with an agent bundle whose `config.yaml` contains:\n   ```yaml\n   os_env:\n     cwd: \"/\"            # or /home/\u003cvictim\u003e, with fork: true for one-shot exfil\n     sandbox: { type: none }\n   ```\n2. On a runner without `OMNIGENT_RUNNER_WORKSPACE`, the agent's `sys_os_read`/`write`/`edit`/`shell` tools now operate over the whole host filesystem, and (sandbox inactive) inherit the runner's full environment — exposing host secrets via e.g. `sys_os_shell(\"env\")`.\n\n### Impact\n\nArbitrary host-filesystem read/write and runner-environment secret disclosure, available to any authenticated user on an affected deployment. High severity (deployment-conditional).\n\n### Suggested fix\n\nAdd a control on the `cwd` field itself in `omnigent/spec/_validate_os_env` (and/or at parse): reject absolute paths and `..` traversal, and require `cwd` to resolve within the runner workspace / an allow-listed root. Do not rely on `OMNIGENT_RUNNER_WORKSPACE` being set as the sole defense. Consider also disallowing bundle-author `sandbox.type: none` for server-realized (non-CLI) sessions.\n\n### Related\n\nGHSA-jrrm-9hc7-2v3h (same bundle-upload vector, different sink; that fix does not cover this).","aliases":["CVE-2026-62677","GHSA-p8rw-8qj3-hf33"],"modified":"2026-09-10T12:15:10.115299270Z","published":"2026-09-10T09:44:59.689355Z","references":[{"type":"WEB","url":"https://github.com/omnigent-ai/omnigent/security/advisories/GHSA-p8rw-8qj3-hf33"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-62677"},{"type":"WEB","url":"https://github.com/omnigent-ai/omnigent/pull/1417"},{"type":"WEB","url":"https://github.com/omnigent-ai/omnigent/commit/7ca0cca3c9a65c04c489edf68f0e080424a26868"},{"type":"PACKAGE","url":"https://github.com/omnigent-ai/omnigent"},{"type":"WEB","url":"https://github.com/omnigent-ai/omnigent/releases/tag/v0.3.0"},{"type":"PACKAGE","url":"https://pypi.org/project/omnigent"},{"type":"ADVISORY","url":"https://github.com/advisories/GHSA-p8rw-8qj3-hf33"}],"affected":[{"package":{"name":"omnigent","ecosystem":"PyPI","purl":"pkg:pypi/omnigent"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0.3.0"}]}],"versions":["0.0.1rc1","0.0.1rc2","0.1.0","0.1.0rc1","0.1.0rc2","0.1.0rc3","0.1.0rc4","0.1.1","0.1.1rc2","0.2.0","0.2.0rc1","0.3.0rc1"],"database_specific":{"source":"https://github.com/pypa/advisory-database/blob/main/vulns/omnigent/PYSEC-2026-3875.yaml"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"}]}