fix(ui): delete prompts use data-confirm, so a quote in a name no longer runs script (#5099)

#5099 "A name containing a quote runs script when an admin clicks Delete".
Inline handlers like onclick="return confirm('Delete {{ rule.name }}?')" are
HTML-decoded before the JavaScript is parsed, so autoescaping did not stop a
name such as  x');fetch(…);//  from running.

- Every prompt (22 handlers, plus the docker prune and token revoke ones)
  is now data-confirm="…", read through dataset by one submit listener in
  base.html; nothing rendered is parsed as JavaScript.
- The remaining inline handlers take integers only (|int), and the time
  range buttons pass this.dataset.range.
- tests/test_inline_handlers.py fails on any {{ }} in an inline handler that
  is not |int, with a self-test; tests/_templates.py is the one template
  walker the template guards now share.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-06 14:30:13 -04:00
co-authored by Claude Opus 5.5
parent 737d699308
commit f47fc23265
28 changed files with 97 additions and 49 deletions
+7
View File
@@ -10,6 +10,13 @@
document.addEventListener('htmx:configRequest', function (ev) {
ev.detail.headers['X-CSRF-Token'] = document.querySelector('meta[name="csrf-token"]').content;
});
// A form (or its submit button) with data-confirm="…" asks before it
// submits. The prompt is attribute text read through dataset, never
// parsed as JavaScript, so a name inside it cannot run as script (#5099).
document.addEventListener('submit', function (ev) {
var ask = (ev.submitter && ev.submitter.dataset.confirm) || ev.target.dataset.confirm;
if (ask && !window.confirm(ask)) { ev.preventDefault(); ev.stopPropagation(); }
}, true);
</script>
<title>{% block title %}Steward{% endblock %}</title>
{# Every asset is served by Steward itself: it runs with no outbound network (rule 164). #}