diff --git a/backend/app/api/routes/archives.py b/backend/app/api/routes/archives.py
index d943d6d5e..0fb041dce 100644
--- a/backend/app/api/routes/archives.py
+++ b/backend/app/api/routes/archives.py
@@ -3518,7 +3518,9 @@ def _render_confirm_prompt_page(request: Request, archive: PrintArchive, verdict
with JavaScript off -- gets the same page and presses the button.
"""
name = html_escape(archive.print_name or archive.filename or "")
- label = _VERDICT_LABELS.get(verdict, verdict)
+ # Escaped although the route only lets good/reject through: the page must
+ # not depend on a check made in another function.
+ label = html_escape(_VERDICT_LABELS.get(verdict, verdict))
# No action attribute: the form posts back to the URL the page was loaded
# from, so it works behind a reverse proxy and on a host external_url does
# not name.
diff --git a/backend/tests/unit/test_confirm_page_escaping.py b/backend/tests/unit/test_confirm_page_escaping.py
new file mode 100644
index 000000000..db51ea777
--- /dev/null
+++ b/backend/tests/unit/test_confirm_page_escaping.py
@@ -0,0 +1,48 @@
+"""The one-tap outcome prompt page escapes what it echoes (#1898).
+
+The routes only let ``good`` / ``reject`` through as the verdict, but the page
+renderer must not rely on that: it is served unauthenticated, and a check made
+in another function can be loosened without anyone looking at this one.
+"""
+
+from __future__ import annotations
+
+from types import SimpleNamespace
+
+from starlette.requests import Request
+
+from backend.app.api.routes.archives import _render_confirm_prompt_page
+
+PAYLOAD = ""
+
+
+def _request() -> Request:
+ return Request(
+ {
+ "type": "http",
+ "method": "GET",
+ "path": "/api/v1/archives/confirm/tok/x",
+ "query_string": b"",
+ "headers": [],
+ }
+ )
+
+
+def test_an_unknown_verdict_is_escaped():
+ archive = SimpleNamespace(print_name="Benchy", filename="benchy.3mf")
+ page = _render_confirm_prompt_page(_request(), archive, PAYLOAD)
+ assert PAYLOAD not in page
+ assert "<script>alert(1)</script>" in page
+
+
+def test_the_print_name_is_escaped():
+ archive = SimpleNamespace(print_name=PAYLOAD, filename="x.3mf")
+ page = _render_confirm_prompt_page(_request(), archive, "good")
+ assert PAYLOAD not in page
+ assert "Good part" in page
+
+
+def test_known_verdicts_keep_their_labels():
+ archive = SimpleNamespace(print_name="Benchy", filename="benchy.3mf")
+ assert "Good part" in _render_confirm_prompt_page(_request(), archive, "good")
+ assert "Rejected" in _render_confirm_prompt_page(_request(), archive, "reject")