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:
parent
3e229141af
commit
a10f511a13
2 changed files with 29 additions and 0 deletions
|
|
@ -253,6 +253,12 @@ def _read_manifest_urls(root: Path) -> Tuple[List[str], List[str]]:
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
_log.warning("Configured extension manifest is not valid JSON")
|
_log.warning("Configured extension manifest is not valid JSON")
|
||||||
return [], []
|
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):
|
except (OSError, UnicodeDecodeError):
|
||||||
_log.warning("Configured extension manifest could not be read")
|
_log.warning("Configured extension manifest could not be read")
|
||||||
return [], []
|
return [], []
|
||||||
|
|
|
||||||
|
|
@ -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):
|
def test_extension_manifest_path_must_stay_inside_extension_root(tmp_path, monkeypatch):
|
||||||
monkeypatch.delenv("HERMES_WEBUI_EXTENSION_STYLESHEET_URLS", raising=False)
|
monkeypatch.delenv("HERMES_WEBUI_EXTENSION_STYLESHEET_URLS", raising=False)
|
||||||
root = tmp_path / "extensions"
|
root = tmp_path / "extensions"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue