CVE-2026-49957
Description
Hermes WebUI 0.51.269 allows authenticated users to bypass path restrictions and read local system files via remote terminal profiles.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Hermes WebUI 0.51.269 allows authenticated users to bypass path restrictions and read local system files via remote terminal profiles.
Vulnerability
Hermes WebUI versions before 0.51.269 contain a workspace boundary bypass vulnerability. This issue arises from an early return in the _remote_terminal_workspace_candidate() function, which handles SSH/remote terminal profile workspace resolution. When an attacker configures a remote terminal working directory to a sensitive system directory like /etc, the workspace resolution logic accepts it as a trusted local workspace before the _is_blocked_workspace_path() guard can execute.
Exploitation
An authenticated attacker needs to configure an SSH or remote terminal profile within Hermes WebUI. The attacker must set the target-side working directory (remote_terminal_cwd) for this profile to a system directory, such as /etc. Once this configuration is in place, the attacker can initiate a session. The vulnerability allows the system to treat this remote directory as a trusted local workspace, enabling subsequent file-read operations through workspace helpers.
Impact
Successful exploitation allows an attacker to read arbitrary local system files. By leveraging the compromised workspace resolution, an attacker can use workspace file-read paths to access sensitive information. For example, an attacker could read the /etc/hosts file, gaining insight into the system's network configuration. The scope of the compromise is limited to the privileges of the authenticated user running Hermes WebUI.
Mitigation
The vulnerability is fixed in Hermes WebUI version 0.51.296, released on 2026-06-06 [2]. The fix involves applying the _is_blocked_workspace_path() guard earlier in the _remote_terminal_workspace_candidate() function to properly reject blocked system roots for remote workspaces [1]. No workarounds are disclosed in the available references.
AI Insight generated on Jun 9, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2(expand)+ 1 more
- (no CPE)
- (no CPE)range: <0.51.269
Patches
291a89fb5d5c0Release v0.51.296 — stage-3731 (remote-workspace blocked-root security fix #3731) (#3744)
3 files changed · +23 −0
api/workspace.py+2 −0 modified@@ -93,6 +93,8 @@ def _remote_terminal_workspace_candidate(path: str | Path) -> Path | None: return None candidate = Path(raw).expanduser().resolve() base = Path(cwd).expanduser().resolve() + if _is_blocked_workspace_path(candidate, raw) or _is_blocked_workspace_path(base, cwd): + return None if candidate == base or _is_within(candidate, base): return candidate return None
CHANGELOG.md+10 −0 modified@@ -3,6 +3,16 @@ ## [Unreleased] +## [v0.51.296] — 2026-06-06 — Release JL (stage-3731 — remote-workspace blocked-root security fix) + +### Security +- **Remote-terminal workspace resolution now rejects blocked system roots.** `_remote_terminal_workspace_candidate()` returned early for paths under the configured remote terminal cwd *before* the blocked-root guard ran, so an SSH/remote terminal profile whose target-side cwd was a system directory (e.g. `/etc`) could have that root accepted as a local workspace — after which workspace file helpers (which treat `s.workspace` as a local `Path`) could read local system files. The blocked-root guard now runs for both the candidate and the base path, so registration and trusted-workspace resolution reject these roots consistently. (#3731, @Hinotoi-agent) + +## [v0.51.295] — 2026-06-06 — Release JK (stage-3739/3742 — model-pick revert fix + session-status revert) + +### Fixed +- **The composer model picker no longer silently reverts your selection on send.** When you explicitly picked a model whose family differed from the active profile's provider (e.g. a `gpt-*` model under an `anthropic`-bound profile), the server's profile-aware resolution (v0.51.290, #3448) rewrote it to the profile default and the dropdown snapped back with no warning. An explicit pick is now honored across both resolution paths (the profile-provider branch and the legacy bare-prefix branch), and if the model genuinely must change (a real provider mismatch) a toast explains it instead of silently swapping. The legitimate stale-session repair path is preserved. (#3739 fixes #3737, @someaka) + ### Removed - **Reverted the manual per-session status labels (Todo / In Progress / Done).** The feature added in v0.51.284 (#3570) stored the chosen status only in browser `localStorage`, keyed by session id, with no server-side backing — so labels silently did not persist across browsers or devices (a user who labeled sessions on one machine saw none of them after switching to a laptop). It also rendered the three statuses as flat top-level entries in the session context menu alongside Copy/Rename/Pin/etc., which crowded the root menu. Removed entirely for now (JS state + cycle logic, context-menu entries, sidebar badge render, CSS, and all locale strings); the feature can be reintroduced later with proper server-side persistence and a less intrusive menu treatment. (reverts #3570)
tests/test_remote_terminal_workspace.py+11 −0 modified@@ -55,3 +55,14 @@ def test_remote_terminal_workspace_paths_outside_cwd_still_reject(monkeypatch): with pytest.raises(ValueError, match="Path does not exist"): workspace.resolve_trusted_workspace("/Users/other/projects/demo") + + +@pytest.mark.parametrize("workspace_path", ["/etc", "/etc/ssh"]) +def test_remote_terminal_workspace_system_roots_still_reject(monkeypatch, workspace_path): + monkeypatch.setattr(api_config, "get_config", lambda: _remote_config(terminal={"backend": "ssh", "cwd": "/etc"})) + + with pytest.raises(ValueError, match="Path points to a system directory"): + workspace.validate_workspace_to_add(workspace_path) + + with pytest.raises(ValueError, match="Path points to a system directory"): + workspace.resolve_trusted_workspace(workspace_path)
753d09b12e61Merge 184892b0bcfc63e54f481e85a45f3babfbff60d8 into 32d46f44503df91d0c2493950e298f10f8b35afe
2 files changed · +13 −0
api/workspace.py+2 −0 modified@@ -93,6 +93,8 @@ def _remote_terminal_workspace_candidate(path: str | Path) -> Path | None: return None candidate = Path(raw).expanduser().resolve() base = Path(cwd).expanduser().resolve() + if _is_blocked_workspace_path(candidate, raw) or _is_blocked_workspace_path(base, cwd): + return None if candidate == base or _is_within(candidate, base): return candidate return None
tests/test_remote_terminal_workspace.py+11 −0 modified@@ -55,3 +55,14 @@ def test_remote_terminal_workspace_paths_outside_cwd_still_reject(monkeypatch): with pytest.raises(ValueError, match="Path does not exist"): workspace.resolve_trusted_workspace("/Users/other/projects/demo") + + +@pytest.mark.parametrize("workspace_path", ["/etc", "/etc/ssh"]) +def test_remote_terminal_workspace_system_roots_still_reject(monkeypatch, workspace_path): + monkeypatch.setattr(api_config, "get_config", lambda: _remote_config(terminal={"backend": "ssh", "cwd": "/etc"})) + + with pytest.raises(ValueError, match="Path points to a system directory"): + workspace.validate_workspace_to_add(workspace_path) + + with pytest.raises(ValueError, match="Path points to a system directory"): + workspace.resolve_trusted_workspace(workspace_path)
Vulnerability mechanics
Root cause
"The remote terminal workspace resolution logic did not properly validate blocked system roots before accepting them as trusted local workspaces."
Attack vector
An authenticated attacker can configure a remote terminal profile with a system directory, such as `/etc`, as its working directory. The `_remote_terminal_workspace_candidate()` function would prematurely return, bypassing the `_is_blocked_workspace_path()` check. This allowed the system directory to be accepted as a trusted local workspace root. Subsequently, workspace file-read helpers could be used to access sensitive local system files.
Affected code
The vulnerability resides in the `_remote_terminal_workspace_candidate()` function within `api/workspace.py`. Specifically, the logic that resolves remote terminal workspace candidates did not consistently apply the blocked-root path checks before accepting a workspace. The fix involves adding checks for both the candidate path and the base path against blocked roots within this function [patch_id=5390388].
What the fix does
The patch modifies the `_remote_terminal_workspace_candidate()` function to ensure that the `_is_blocked_workspace_path()` guard is applied to both the candidate workspace path and the base path (remote terminal cwd) before accepting them. This prevents system directories from being registered as trusted workspaces, thereby closing the vulnerability that allowed unauthorized local file access [patch_id=5390388]. The commit also adds regression tests to cover these scenarios [ref_id=2].
Preconditions
- authThe attacker must be authenticated to the system.
- configThe attacker must be able to configure a remote terminal profile.
Generated on Jun 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/nesquena/hermes-webui/commit/91a89fb5d5c0bf87932917f9914ad0150ea62fe4nvd
- github.com/nesquena/hermes-webui/pull/3731nvd
- github.com/nesquena/hermes-webui/pull/3744nvd
- github.com/nesquena/hermes-webui/releases/tag/v0.51.269nvd
- www.vulncheck.com/advisories/hermes-webui-workspace-boundary-bypass-via-api-workspace-pynvd
News mentions
0No linked articles in our index yet.