Files
openhop_repeater/tests/test_plugin_static_security.py

61 lines
2.5 KiB
Python

"""Public plugin serving must never expose manager/runtime release files."""
from types import SimpleNamespace
import cherrypy
import pytest
from repeater.plugins.storage import PluginStorage
from repeater.web.http_server import StatsApp
@pytest.fixture
def plugin_ui(tmp_path, monkeypatch):
storage = PluginStorage(tmp_path / "plugins")
paths = storage.ensure_plugin_layout("audit.ui", "1.0.0")
release = paths.release_dir("1.0.0")
storage.set_current("audit.ui", "1.0.0")
storage.write_state("audit.ui", {"version": "1.0.0", "enabled": True})
(release / "config.default.json").write_text("PRIVATE-DEFAULT-FIXTURE")
(release / "venv").mkdir()
(release / "venv" / "pyvenv.cfg").write_text("PRIVATE-RUNTIME-FIXTURE")
manifest = SimpleNamespace(ui=SimpleNamespace(entry="ui/index.html"))
# Exercise serving's own boundary independently of manifest validation.
monkeypatch.setattr(PluginStorage, "load_current_manifest", lambda *_: manifest)
monkeypatch.setattr(cherrypy, "response", SimpleNamespace(headers={}))
app = object.__new__(StatsApp)
app.config = {"plugins": {"root": str(storage.root)}}
return app, release, manifest
@pytest.mark.parametrize("entry", ["index.html", "./index.html", "venv/index.html"])
def test_public_plugin_rejects_release_or_runtime_document_root(plugin_ui, entry):
app, _, manifest = plugin_ui
manifest.ui.entry = entry
requested = "pyvenv.cfg" if entry.startswith("venv/") else "config.default.json"
with pytest.raises(cherrypy.HTTPError) as exc:
app._serve_plugin_ui("audit.ui", (requested,))
assert exc.value.status == 404
def test_public_plugin_rejects_ui_symlink_to_runtime(plugin_ui):
app, release, _ = plugin_ui
(release / "ui").symlink_to("venv", target_is_directory=True)
with pytest.raises(cherrypy.HTTPError) as exc:
app._serve_plugin_ui("audit.ui", ("pyvenv.cfg",))
assert exc.value.status == 404
def test_public_plugin_keeps_assets_and_spa_fallback(plugin_ui):
app, release, _ = plugin_ui
ui = release / "ui"
ui.mkdir()
(ui / "index.html").write_text("PUBLIC-INDEX")
(ui / "app.js").write_text("PUBLIC-SCRIPT")
assert app._serve_plugin_ui("audit.ui", ("app.js",)) == b"PUBLIC-SCRIPT"
assert app._serve_plugin_ui("audit.ui", ("client-route",)) == b"PUBLIC-INDEX"
assert app._serve_plugin_ui("audit.ui", ("config.default.json",)) == b"PUBLIC-INDEX"
with pytest.raises(cherrypy.HTTPError) as exc:
app._serve_plugin_ui("audit.ui", ("..", "config.default.json"))
assert exc.value.status == 400