diff --git a/CHANGELOG.md b/CHANGELOG.md index b4ac9d14..4f4984dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ ## [Unreleased] +## [v0.51.545] — 2026-06-20 — Release TD (extension manifest asset bundles) + +### Added + +- **Bundle extension assets with a manifest instead of long comma-separated URL lists (#4505).** A new `HERMES_WEBUI_EXTENSION_MANIFEST` env points to a small JSON manifest (capped at 64 KB) that lists an extension's `scripts`/`stylesheets`, so self-hosters running multiple local extensions no longer have to maintain unwieldy `HERMES_WEBUI_EXTENSION_SCRIPT_URLS` strings. Per-extension `enabled: false` skips a bundle. The manifest path is traversal-guarded (must resolve inside the extension dir; absolute/`~` paths rejected), and every asset URL still goes through the exact same same-origin allowlist validator as before (`/extensions/` or `/static/` only — never third-party fetch), capped at 32 entries and deduped. Fully opt-in and default-off; with no manifest env set, behavior is unchanged. Thanks @santastabber. + ## [v0.51.544] — 2026-06-20 — Release TC (opt-in: render markdown in your own messages) ### Added diff --git a/README.md b/README.md index e5348d96..40fcec79 100644 --- a/README.md +++ b/README.md @@ -344,8 +344,9 @@ Full list of environment variables: | `HERMES_WEBUI_CSP_CONNECT_EXTRA` | *(unset)* | Optional space-separated `http(s)://` or `ws(s)://` origins to append to the report-only CSP `connect-src` directive for reverse-proxy or tunnel deployments | | `HERMES_WEBUI_SSE_CHUNKED` | *(unset)* | Set truthy (`1`/`true`/`yes`/`on`) to send SSE with `Transfer-Encoding: chunked`. Needed behind buffering reverse proxies (e.g. `jupyter-server-proxy`) that otherwise buffer the whole stream; harmless but unnecessary for directly-served deployments | | `HERMES_WEBUI_EXTENSION_DIR` | *(unset)* | Optional local directory served at `/extensions/`; must point to an existing directory before extension injection is enabled | -| `HERMES_WEBUI_EXTENSION_SCRIPT_URLS` | *(unset)* | Optional comma-separated same-origin script URLs to inject; see [WebUI Extensions](docs/EXTENSIONS.md) | -| `HERMES_WEBUI_EXTENSION_STYLESHEET_URLS` | *(unset)* | Optional comma-separated same-origin stylesheet URLs to inject; see [WebUI Extensions](docs/EXTENSIONS.md) | +| `HERMES_WEBUI_EXTENSION_MANIFEST` | *(unset)* | Optional relative JSON manifest inside `HERMES_WEBUI_EXTENSION_DIR` listing bundled scripts/styles to inject; see [WebUI Extensions](docs/EXTENSIONS.md) | +| `HERMES_WEBUI_EXTENSION_SCRIPT_URLS` | *(unset)* | Optional comma-separated same-origin script URLs to inject; appended after manifest scripts; see [WebUI Extensions](docs/EXTENSIONS.md) | +| `HERMES_WEBUI_EXTENSION_STYLESHEET_URLS` | *(unset)* | Optional comma-separated same-origin stylesheet URLs to inject; appended after manifest stylesheets; see [WebUI Extensions](docs/EXTENSIONS.md) | | `HERMES_HOME` | Windows: `%LOCALAPPDATA%\hermes`; POSIX: `~/.hermes` | Base directory for Hermes state (affects all paths) | | `HERMES_CONFIG_PATH` | `$HERMES_HOME/config.yaml` | Path to Hermes config file | | `HERMES_WEBUI_SERVER_CWD` | *(unset)* | Working directory for the server process. Defaults to the agent dir; point it at a writable workspace when the agent dir is read-only so fallback relative writes land somewhere writable | diff --git a/api/extensions.py b/api/extensions.py index 5a862b70..17932957 100644 --- a/api/extensions.py +++ b/api/extensions.py @@ -6,10 +6,11 @@ It is disabled by default and never executes or fetches third-party URLs. """ import html +import json import logging import os from pathlib import Path -from typing import Dict, List, Optional +from typing import Dict, List, Optional, Tuple from urllib.parse import unquote, urlsplit from api.helpers import _security_headers, j @@ -22,14 +23,24 @@ _log = logging.getLogger(__name__) # avoids rendering tens of thousands of