Fix Hermes desktop launch settings and sandbox consistency

Release 2026.8.31-3 preserves upstream desktop settings and explicit launch environment without invoking the CLI sandbox setup. Keep incomplete runtimes from blocking the packaged fallback, and use the namespace sandbox consistently in both locations.

Co-Authored-By: Codex XHigh <noreply@openai.com>
This commit is contained in:
Spencer BullandCodex XHigh committed 2026-09-07 04:10:07 -05:00
1 parent bf842f5a6f
commit f69f6ce364
3 files changed
+158 -67

No files matched your search

+98 -21
View File
@@ -1,4 +1,5 @@
"""Check native launch and the release updater without running Hermes or sudo."""
"""Check launch settings and sandbox gates without running Hermes or sudo."""
import ast
import json
import os
from pathlib import Path
@@ -26,6 +27,9 @@ Path(os.environ["TEST_OUTPUT"]).write_text(json.dumps({
"args": sys.argv[1:], "home": os.environ["HERMES_HOME"],
"store": os.environ["HERMES_DESKTOP_PASSWORD_STORE"],
"gpu": os.environ.get("HERMES_DESKTOP_DISABLE_GPU"),
"ozone": os.environ.get("ELECTRON_OZONE_PLATFORM_HINT"),
"cwd": os.environ.get("HERMES_DESKTOP_CWD"),
"inherited": [name for name in ("ELECTRON_RUN_AS_NODE", "PYTHONPATH", "PYTHONHOME") if name in os.environ],
}))
''')
executable.chmod(0o755)
@@ -33,6 +37,30 @@ Path(os.environ["TEST_OUTPUT"]).write_text(json.dumps({
sandbox.write_text("fixture")
sandbox.chmod(0o755)
# Execute the release's real option parser, isolating config I/O and avoiding
# unrelated CLI imports, startup hooks and third-party dependencies.
module = runtime / "hermes_cli"
module.mkdir()
(module / "__init__.py").touch()
upstream = ast.parse((source / "hermes_cli/main.py").read_text())
option_parser = next(node for node in upstream.body
if isinstance(node, ast.FunctionDef) and node.name == "_desktop_launch_options")
stores = next(node for node in upstream.body if isinstance(node, ast.Assign)
and any(isinstance(target, ast.Name) and target.id == "_LINUX_PASSWORD_STORES"
for target in node.targets))
helper = "import os, shlex\n" + ast.unparse(stores) + "\n" + ast.unparse(option_parser) + "\n"
(module / "main.py").write_text(helper)
(module / "config.py").write_text('''import json, os
from pathlib import Path
def load_config():
path = Path(os.environ["HERMES_HOME"], "config.yaml")
return json.loads(path.read_text()) if path.exists() else {}
''')
(runtime / "hermes_constants.py").write_text('''import os
def with_hermes_node_path(env=None):
return (os.environ if env is None else env).copy()
''')
mock_bin = root / "bin"
mock_bin.mkdir()
unshare = mock_bin / "unshare"
@@ -43,6 +71,7 @@ Path(os.environ["TEST_OUTPUT"]).write_text(json.dumps({
cli.parent.mkdir(parents=True)
cli.write_text(forbidden)
cli.chmod(0o755)
(cli.parent / "python").symlink_to(sys.executable)
for command in ("sudo", "omarchy-install-hermes-cli"):
target = mock_bin / command
target.write_text(forbidden if command == "sudo" else '#!/bin/bash\nexit 0\n')
@@ -53,33 +82,81 @@ Path(os.environ["TEST_OUTPUT"]).write_text(json.dumps({
env = {"HOME": str(home), "PATH": f"{mock_bin}:/usr/bin:/bin",
"TEST_OUTPUT": str(output), "TEST_FORBIDDEN": str(forbidden_output)}
launch = ["bash", str(launcher.resolve())]
for args, overrides, expected_args, expected_store in (
([], {"WAYLAND_DISPLAY": "wayland-1"}, ["--ozone-platform=wayland"], "gnome-libsecret"),
(["--ozone-platform=x11", "hermes://open?text=a%20b"],
{"WAYLAND_DISPLAY": "wayland-1", "HERMES_DESKTOP_PASSWORD_STORE": "kwallet6"},
["--ozone-platform=x11", "hermes://open?text=a%20b"], "kwallet6"),
([], {"HERMES_HOME": str(home / ".hermes/profiles/work"), "HERMES_DESKTOP_DISABLE_GPU": "1"},
[], "gnome-libsecret"),
):
subprocess.run(launch + args, env={**env, **overrides}, check=True)
def check_launch(args=(), overrides=None, expected_args=(), store="gnome-libsecret", gpu=None, ozone=None):
subprocess.run(launch + list(args), env={**env, **(overrides or {})}, cwd=home, check=True)
result = json.loads(output.read_text())
assert result["args"] == ["--disable-setuid-sandbox", *expected_args], result
assert result["home"] == str(home / ".hermes"), result
assert result["store"] == expected_store, result
assert result["gpu"] == overrides.get("HERMES_DESKTOP_DISABLE_GPU"), result
assert result == {"args": ["--disable-setuid-sandbox", *expected_args],
"home": str(home / ".hermes"), "store": store, "gpu": gpu,
"ozone": ozone, "cwd": str(home), "inherited": []}, result
assert not forbidden_output.exists(), "launcher invoked CLI or sudo"
output.unlink()
for code in ("1", "127"):
for args in ([], ["hermes://open"]):
result = subprocess.run(launch + args, env={**env, "TEST_NAMESPACE_RESULT": code},
capture_output=True, text=True)
assert result.returncode != 0 and "user namespaces" in result.stderr, result
assert not output.exists() and not forbidden_output.exists()
wayland = {"WAYLAND_DISPLAY": "wayland-1"}
check_launch(overrides=wayland, expected_args=["--ozone-platform=wayland"])
url = "hermes://open?text=a%20b"
check_launch(["--ozone-platform=x11", url], {**wayland, "HERMES_DESKTOP_PASSWORD_STORE": "kwallet6"},
["--ozone-platform=x11", url], store="kwallet6")
check_launch(overrides={"HERMES_HOME": str(home / ".hermes/profiles/work"),
"HERMES_DESKTOP_DISABLE_GPU": "1"}, gpu="1")
check_launch(overrides={"ELECTRON_RUN_AS_NODE": "1", "PYTHONPATH": "/invalid", "PYTHONHOME": "/invalid"})
config = home / ".hermes/config.yaml"
config.write_text(json.dumps({"desktop": {"disable_gpu": True, "password_store": "kwallet5",
"ozone_platform_hint": "x11",
"electron_flags": '--force-device-scale-factor=1.5 "--test=a b"'}}))
config_flags = ["--force-device-scale-factor=1.5", "--test=a b"]
for args in ([], [url]):
check_launch(args, wayland, config_flags + args, store="kwallet5", gpu="1", ozone="x11")
check_launch(overrides={**wayland, "HERMES_DESKTOP_DISABLE_GPU": "0",
"HERMES_DESKTOP_PASSWORD_STORE": "basic", "ELECTRON_OZONE_PLATFORM_HINT": "wayland"},
expected_args=config_flags, store="basic", gpu="0", ozone="wayland")
config.write_text(json.dumps({"desktop": {"electron_flags": ["--ozone-platform=x11"], "disable_gpu": False}}))
check_launch(overrides=wayland, expected_args=["--ozone-platform=x11"], gpu="0")
# New upstream revisions expose the same helper from main_desktop instead.
(module / "main_desktop.py").write_text(helper)
(module / "main.py").write_text('raise AssertionError("old helper import after update")\n')
check_launch([url], wayland, ["--ozone-platform=x11", url], gpu="0")
config.unlink()
(module / "main_desktop.py").write_text(helper + '\nimport os\nos.environ.update(' + repr({
"ELECTRON_RUN_AS_NODE": "1", "PYTHONPATH": "/invalid", "PYTHONHOME": "/invalid",
"HERMES_HOME": "/invalid", "HERMES_DESKTOP_DISABLE_GPU": "1",
"HERMES_DESKTOP_PASSWORD_STORE": "basic", "ELECTRON_OZONE_PLATFORM_HINT": "x11",
}) + ')\n')
check_launch(overrides={**wayland, "HERMES_DESKTOP_DISABLE_GPU": "0"},
expected_args=["--ozone-platform=wayland"], gpu="0")
(module / "main_desktop.py").write_text(helper)
def check_namespace_failure():
for code in ("1", "127"):
for args in ([], [url]):
result = subprocess.run(launch + args, env={**env, "TEST_NAMESPACE_RESULT": code},
capture_output=True, text=True)
assert result.returncode != 0 and "user namespaces" in result.stderr, result
assert not output.exists() and not forbidden_output.exists()
check_namespace_failure()
# Exercise the real fallback branch with only its absolute app path redirected.
fallback = root / "packaged-Hermes"
shutil.copyfile(executable, fallback)
fallback.chmod(0o755)
fallback_launcher = root / "fallback-launcher"
fallback_launcher.write_text(launcher.read_text().replace("/opt/hermes-desktop/Hermes", str(fallback)))
launch = ["bash", str(fallback_launcher)]
executable.unlink()
check_launch([url], wayland, ["--ozone-platform=wayland", url])
(module / "main_desktop.py").write_text('raise ImportError("incomplete Python dependencies")\n')
check_launch([url], wayland, ["--ozone-platform=wayland", url])
shutil.rmtree(runtime / "venv")
check_launch(overrides={"ELECTRON_RUN_AS_NODE": "1", "PYTHONPATH": "/invalid", "PYTHONHOME": "/invalid"})
check_namespace_failure()
gate = ["bash", str(destination), "--self-test-gate", "--install-root", str(runtime),
"--relaunch-target", str(executable)]
executable.touch()
executable.chmod(0o755)
assert subprocess.check_output(gate, env=env, text=True).strip() == "relaunch"
assert subprocess.check_output(gate, env={**env, "TEST_NAMESPACE_RESULT": "1"}, text=True).startswith("manual:")
gate[-1] = "/opt/hermes-desktop/Hermes"
assert subprocess.check_output(gate, env=env, text=True).startswith("skew:")
print("PASS: direct native launches fail closed; release updater accepts the namespace sandbox")
print("PASS: desktop settings, environment, native/fallback namespace sandbox and updater gate")