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))}

{rows}
resultcheckdetail
""" 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 "