diff --git a/README.md b/README.md
index b66c186..74c6f2a 100644
--- a/README.md
+++ b/README.md
@@ -4,11 +4,13 @@ Catalog of plugins for [DataLab](https://datalab-platform.com/) desktop and [Dat
## Installing a plugin
+In DataLab 1.4 or later, open **Plugins > Configure plugins...**, select the **Available plugins** tab and click **Install**: DataLab downloads the plugin and checks it against the catalog.
+
+To install a downloaded file instead (also with the standalone version of DataLab):
+
1. Open and download the wheel (`.whl`) of the plugin.
2. In DataLab, open **Plugins > Configure plugins...**, go to the **Install plugins** tab and click **Install from file...**.
-This also works with the standalone (Windows installer) version of DataLab, from DataLab 1.4.
-
A plugin runs with the same rights as DataLab: it may read and modify your files. Install only plugins from authors you trust. Being listed here does not mean that a plugin was audited.
## Publishing a plugin
diff --git a/tests/test_catalog.py b/tests/test_catalog.py
index a8bece3..c5efe59 100644
--- a/tests/test_catalog.py
+++ b/tests/test_catalog.py
@@ -124,7 +124,12 @@ def test_github_release_is_mirrored_in_the_published_catalog(
assert (tmp_path / "site" / release["url"]).read_bytes() == wheel[1]
published = json.loads((tmp_path / "site" / "catalog.json").read_text("utf-8"))
assert published["schema_version"] == 1
- assert "Example" in (tmp_path / "site" / "index.html").read_text("utf-8")
+ page = (tmp_path / "site" / "index.html").read_text("utf-8")
+ assert 'data-tier="community" data-targets="desktop web"' in page
+ assert f'href="{release["url"]}" download>Download 1.0.0' in page
+ assert ">Community" in page
+ for name in catalog.PAGE_ASSETS:
+ assert (tmp_path / "site" / name).is_file()
def test_missing_digest_reports_the_value_to_declare(
@@ -221,6 +226,49 @@ def test_revoked_plugin_is_listed_without_downloading_its_wheels(
assert plugin["releases"] == [{"version": "1.0.0", "sha256": "a" * 64}]
assert not (tmp_path / "site" / "wheels").exists()
assert not served
+ page = (tmp_path / "site" / "index.html").read_text("utf-8")
+ assert '
Malicious code
' in page
+ assert "No installable release" in page
+ assert "download>" not in page
+
+
+def test_page_escapes_metadata_read_from_wheels() -> None:
+ """Wheel summaries are untrusted and must not inject markup."""
+ page = catalog.render_index(
+ {
+ "generated_at": "2026-10-08T13:29:23+00:00",
+ "plugins": [
+ {
+ "id": "io.github.someone.example",
+ "name": "Camera & Detector",
+ "tier": "community",
+ "status": "active",
+ "repository": "https://github.com/someone/example",
+ "summary": '',
+ "keywords": ['">
'],
+ "releases": [],
+ }
+ ],
+ }
+ )
+
+ assert "
+
+
+
+
+
+ How to install a plugin
+
+ -
+ In DataLab 1.4 or later
+ Open Plugins > Configure plugins..., select the Available plugins tab and click Install. DataLab downloads the plugin and checks it against this catalog.
+
+ -
+ From a downloaded file
+ Download a wheel below, then click Install from file... in the Install plugins tab of the same dialog. This also works with the standalone version of DataLab.
+
+
+ Plugins run with the same rights as DataLab: install only plugins from authors you trust. Official plugins are maintained by the DataLab team, community plugins by their authors. Being listed here does not mean that a plugin was audited.
+
+
+
+
+ $count
+$cards
+ No plugin matches your search.
+
+
+
+
+