review-fix(comments): restore lost #NNNN rationale comments across non-test source (mechanical sweep, condensed, code unchanged)

For each issue anchor present in BASE 63279301bc non-test .py and absent on HEAD, the BASE comment/docstring block was re-attached at the HEAD location of the code it explained (matched by the distinctive code line / enclosing def). Sentences already covered by an existing HEAD comment were deduped; the issue number always survives. Insert-only: no code lines changed.
This commit is contained in:
Teknium
2026-09-03 09:44:26 -07:00
parent ad62a15e94
commit e83816a4d1
586 changed files with 13883 additions and 829 deletions
+39
View File
@@ -119,6 +119,7 @@ def mount_spa(application: FastAPI):
# path, a renderer whose spawn token no longer matched (e.g. after `hermes update`)
# white-screened. Serve a token-only page at the exact root, but ONLY when the auth
# gate is off: on a gated serve the token must never be readable without auth.
# See #94227, #95575.
gated = bool(getattr(application.state, "auth_required", False))
if full_path == "" and not gated:
return HTMLResponse(
@@ -131,6 +132,14 @@ def mount_spa(application: FastAPI):
return JSONResponse({"error": _HEADLESS_MSG}, status_code=404)
return
# A missing WEB_DIST is deliberately NOT a mount-time terminal state (#82614): a long-lived `hermes
# dashboard --skip-build` process that survives a `git pull` (or starts before the first build) used to
# install a permanent no_frontend catch-all here and could never recover — every route answered 404
# "Frontend not built" until the process was restarted, even after `npm run build` completed. The SPA
# routes below all cope with a missing dist per-request (`_serve_index` returns the same 404 JSON when
# index.html is unreadable; the asset mounts use check_dir=False and 404 on missing files), so mounting
# them unconditionally makes the dashboard recover the moment a build appears on disk — no restart
# needed.
def _serve_index(prefix: str = ""):
"""index.html with the session token + base-path injected.
@@ -427,6 +436,10 @@ def _safe_plugin_api_relpath(api_field: Any, *, dashboard_dir: Path) -> Optional
``/tmp/evil.py``) and ``../`` could climb out of it (GHSA-5qr3-c538-wm9j). Returns the
original string when the resolved path stays under ``dashboard_dir``, else ``None`` so
the plugin still loads its static JS/CSS but its backend ``api`` is rejected.
The web server later imports this file as a Python module via ``importlib.util.spec_from_file_location``
(arbitrary code execution by design — that's how plugins extend the backend). Pre-#29156 the field was
used as-is, which meant:
"""
if not isinstance(api_field, str) or not api_field.strip():
return None
@@ -455,12 +468,29 @@ def _dashboard_plugin_search_dirs() -> List[tuple]:
from hermes_constants import get_default_hermes_root
bundled_root = get_bundled_plugins_dir()
# User dashboard plugins are a dashboard-owned asset (same category as theme YAML): resolve them from
# the process launch home so they don't vanish when a request is scoped to another profile via a
# context-local HERMES_HOME override (e.g. embedded /chat under --open-profile). #87197: when the
# process itself is profile-scoped (``--profile <name>`` sets ``HERMES_HOME=<root>/profiles/<name>``),
# the launch home is the profile directory, which has no ``plugins/`` — user plugins are installed in
# the hermes root (``~/.hermes/plugins``). Scan the default root as well (``get_default_hermes_root()``
# unwraps ``<root>/profiles/<name>`` → ``<root>`` and returns a custom ``HERMES_HOME`` unchanged when it
# *is* the root), mirroring how ``hermes_cli.plugins`` resolves plugin install locations. The
# ``seen_names`` dedupe below keeps profile-local plugins (if any) authoritative over same-named root
# plugins.
user_plugin_roots = [get_process_hermes_home() / "plugins"]
root_plugins = get_default_hermes_root() / "plugins"
if root_plugins.resolve(strict=False) != user_plugin_roots[0].resolve(strict=False):
user_plugin_roots.append(root_plugins)
search_dirs = [(d, "user") for d in user_plugin_roots]
search_dirs += [(bundled_root / "memory", "bundled"), (bundled_root, "bundled")]
# GHSA-5qr3-c538-wm9j (#29156): the previous ``os.environ.get(...)`` check treated *any* non-empty
# string as truthy, so ``=0``, ``=false``, and ``=no`` — all of which the agent loader and operators
# correctly read as "disabled" — silently *enabled* the untrusted project source in the web server.
# Combined with the absolute-path RCE primitive on the manifest's ``api`` field (now patched below),
# this turned the opt-in into a sticky always-on switch. Use the shared truthy semantics (``1`` /
# ``true`` / ``yes`` / ``on``) so the gate matches ``hermes_cli/plugins.py`` and the documented user
# contract.
if env_var_enabled("HERMES_ENABLE_PROJECT_PLUGINS"):
search_dirs.append((Path.cwd() / ".hermes" / "plugins", "project"))
return search_dirs
@@ -738,6 +768,15 @@ def _mount_plugin_api_routes():
Each plugin's ``api`` file must expose a ``router`` (FastAPI APIRouter), mounted under
``/api/plugins/<name>/``. See ``_plugin_api_mount_skip_reason`` for the trust gates.
Backend import is restricted to ``bundled`` and ``user`` sources. Project plugins
(``./.hermes/plugins/``) ship with the CWD and are therefore attacker-controlled in any threat model
where the user opens a malicious repo; they can extend the dashboard UI via static JS/CSS but their
Python ``api`` file is never auto-imported by the web server. See GHSA-5qr3-c538-wm9j (#29156).
Additionally, user plugins must be explicitly enabled via the ``plugins.enabled`` allow-list in
config.yaml before their backend code is imported. Without this gate, an installed-but-not-enabled
plugin's Python code would execute at dashboard startup — a code execution vector that bypasses the
user's intent. (#46435, GHSA-mcfc-hp25-cjv7)
"""
from hermes_cli.web_server import _get_dashboard_plugins, app
try: