diff --git a/src/answerproof/api.py b/src/answerproof/api.py
index 20c50c5..428a554 100644
--- a/src/answerproof/api.py
+++ b/src/answerproof/api.py
@@ -10,6 +10,7 @@
from __future__ import annotations
+import html
from typing import Any
try:
@@ -77,13 +78,13 @@ def _render_page(receipt: Receipt, verdict) -> str:
color = "#137333" if verdict.valid else "#c5221f"
status = "VALID" if verdict.valid else "INVALID"
rows = "".join(
- f"
| {'ok' if c.passed else 'FAIL'} | {c.name} | "
- f"{c.detail or ''} |
"
+ f"| {'ok' if c.passed else 'FAIL'} | {html.escape(str(c.name))} | "
+ f"{html.escape(str(c.detail or ''))} |
"
for c in verdict.checks
)
return f"""
-answerproof receipt {p.receipt_id}
+answerproof receipt {html.escape(p.receipt_id)}
answerproof receipt
{status}
-Receipt: {p.receipt_id}
-Query: {p.query}
-Answer: {p.answer}
-Signer: {receipt.signature.public_key}
-Merkle root: {p.merkle_root}
-Grounding score: {p.grounding.grounding_score}
+Receipt: {html.escape(p.receipt_id)}
+Query: {html.escape(p.query)}
+Answer: {html.escape(p.answer)}
+Signer: {html.escape(receipt.signature.public_key)}
+Merkle root: {html.escape(p.merkle_root)}
+Grounding score: {html.escape(str(p.grounding.grounding_score))}
"""
diff --git a/tests/test_api.py b/tests/test_api.py
index e8c14f6..a7aab9b 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -5,6 +5,7 @@
fastapi = pytest.importorskip("fastapi")
from fastapi.testclient import TestClient # noqa: E402
+from answerproof import ReceiptBuilder, SigningKey # noqa: E402
from answerproof.api import create_app # noqa: E402
@@ -45,3 +46,29 @@ def test_verify_page_renders_html(client, receipt, sources):
assert r.status_code == 200
assert "text/html" in r.headers["content-type"]
assert "VALID" in r.text
+
+
+def test_verify_page_escapes_receipt_fields(client):
+ query = ""
+ answer = "
"
+ receipt = (
+ ReceiptBuilder(SigningKey.generate())
+ .set_query(query)
+ .set_answer(answer)
+ .add_source("s1", content="safe source content")
+ .finalize(receipt_id="xss-test")
+ )
+
+ response = client.post(
+ "/verify/page",
+ json={
+ "receipt": json.loads(receipt.to_json()),
+ "source_contents": {"s1": "safe source content"},
+ },
+ )
+
+ assert response.status_code == 200
+ assert "