nothing to see here
" write_test_file_and_sync(os.path.join(datastore_path, "endpoint-content.txt"), baseline_html) test_notification_url = url_for('test_notification_endpoint', _external=True).replace('http://', 'post://') # Pass content_type=text/html so the watch records 'text/html' as its content-type # — this is the branch the previous gate skipped escaping for. test_url = url_for('test_endpoint', _external=True, content_type='text/html') # HTML-format notification body that embeds the snapshot directly. Operators do this # when they want the full changed content in the alert (e.g. an email digest). res = client.post( url_for("settings.settings_page"), data={ "application-fetch_backend": "html_requests", "application-minutes_between_check": 180, "application-notification_body": 'Watch had changes:\n{{current_snapshot}}', "application-notification_format": "html", "application-notification_urls": test_notification_url, "application-notification_title": "Change detected", }, follow_redirects=True ) assert b'Settings updated' in res.data res = client.post( url_for("ui.ui_views.form_quick_watch_add"), data={"url": test_url, "tags": ''}, follow_redirects=True ) assert b"Watch added" in res.data wait_for_all_checks(client) # Now flip the page to something whose *visible* text contains entity-encoded # angle brackets — exactly the pattern a forum / pastebin / code-sample site uses # to display literal HTML on the page. Inscriptis will decode </> back to # literal < / > in the stored snapshot. attacker_html = ( ''
'<a href="https://attacker.example/payment">ACTION REQUIRED</a>'
'<img src="https://attacker.example/track" width="1" height="1">'
''
)
write_test_file_and_sync(os.path.join(datastore_path, "endpoint-content.txt"), attacker_html)
res = client.get(url_for("ui.form_watch_checknow"), follow_redirects=True)
assert b'Queued 1 watch for rechecking.' in res.data
wait_for_all_checks(client)
wait_for_notification_endpoint_output(datastore_path=datastore_path)
with open(os.path.join(datastore_path, "notification.txt"), 'r') as f:
body = f.read()
# Sanity: the snapshot really did contain the decoded markup (otherwise the test
# would pass for the wrong reason). The escaped form must appear somewhere.
assert '<a href=' in body or '<a href=' in body, \
f"Expected escaped attacker markup in notification body, got: {body!r}"
# The bug: a live ends up in the HTML notification.
assert '` / `