fix(#4561): catch RecursionError on deeply-nested manifest (BRICK)

Codex+Opus ship-gate: a <=64KB but deeply-nested JSON manifest makes json.loads raise
RecursionError, which escaped _read_manifest_urls into the app-shell route -> every page
load 503s. Add RecursionError to the fail-safe except + regression test (verified
red-without/green-with). Co-authored-by: santastabber <santastabber@users.noreply.github.com>
This commit is contained in:
nesquena-hermes
2026-06-21 00:42:58 +00:00
parent 3e229141af
commit a10f511a13
2 changed files with 29 additions and 0 deletions
+6
View File
@@ -253,6 +253,12 @@ def _read_manifest_urls(root: Path) -> Tuple[List[str], List[str]]:
except json.JSONDecodeError:
_log.warning("Configured extension manifest is not valid JSON")
return [], []
except RecursionError:
# A <=64KB but deeply-nested manifest makes json.loads exceed the
# interpreter recursion limit. Without this, the RecursionError escapes
# into the app-shell route and every page load 503s. Fail safe.
_log.warning("Configured extension manifest is too deeply nested")
return [], []
except (OSError, UnicodeDecodeError):
_log.warning("Configured extension manifest could not be read")
return [], []
+23
View File
@@ -228,6 +228,29 @@ def test_extension_manifest_reuses_url_safety_rules(tmp_path, monkeypatch):
}
def test_extension_manifest_deeply_nested_json_fails_safe(tmp_path, monkeypatch):
"""A <=64KB but deeply-nested manifest makes json.loads raise RecursionError.
It must fail safe (empty lists) NOT escape and 503 every page load."""
monkeypatch.delenv("HERMES_WEBUI_EXTENSION_SCRIPT_URLS", raising=False)
monkeypatch.delenv("HERMES_WEBUI_EXTENSION_STYLESHEET_URLS", raising=False)
root = tmp_path / "extensions"
root.mkdir()
# ~6000 nested arrays: well under 64KB but blows the recursion limit in json.loads.
depth = 6000
payload = "[" * depth + "]" * depth
assert len(payload.encode("utf-8")) < 64 * 1024
(root / "deep.json").write_text(payload, encoding="utf-8")
monkeypatch.setenv("HERMES_WEBUI_EXTENSION_DIR", str(root))
monkeypatch.setenv("HERMES_WEBUI_EXTENSION_MANIFEST", "deep.json")
from api.extensions import get_extension_config
# Must not raise; must fall back to no manifest assets.
cfg = get_extension_config()
assert cfg["script_urls"] == []
assert cfg["stylesheet_urls"] == []
def test_extension_manifest_path_must_stay_inside_extension_root(tmp_path, monkeypatch):
monkeypatch.delenv("HERMES_WEBUI_EXTENSION_STYLESHEET_URLS", raising=False)
root = tmp_path / "extensions"