Skip to main content

max / makenotwork

mt: restore destructive-action confirmations (F1) Inline onsubmit="return confirm(...)" never ran under our CSP (no script-src 'unsafe-inline'), and the global submit listener in mt.js fired the fetch without checking e.defaultPrevented — so delete-thread, remove-post, delete-tag, and stop-tracking-all submitted instantly with no prompt and no way to cancel. The two defects masked each other: relaxing the CSP alone would still ship broken. Drive the prompt from a data-confirm attribute read in the mt.js submit handler, and bail early when another handler has already prevented the submit. Ultra-fuzz Run #2 finding F1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-06-18 20:09 UTC
Commit: a19e4df6936a64999b26474767d3cbc609027e36
Parent: 77e8471
5 files changed, +15 insertions, -5 deletions
@@ -238,8 +238,18 @@ document.addEventListener('click', function(e) {
238 238 =========================================== */
239 239
240 240 document.addEventListener('submit', function(e) {
241 + // Another handler may have already cancelled this submit — respect it.
242 + if (e.defaultPrevented) return;
241 243 var form = e.target;
242 244 if (form.method && form.method.toUpperCase() === 'POST') {
245 + // Destructive-action guard. Inline `onsubmit="return confirm(...)"`
246 + // cannot run under our CSP (no script-src 'unsafe-inline'), so the
247 + // prompt lives here, driven by a `data-confirm` attribute on the form.
248 + var confirmMsg = form.getAttribute('data-confirm');
249 + if (confirmMsg && !window.confirm(confirmMsg)) {
250 + e.preventDefault();
251 + return;
252 + }
243 253 var token = document.querySelector('meta[name="csrf-token"]')?.content;
244 254 if (!token) return;
245 255 e.preventDefault();
@@ -130,7 +130,7 @@
130 130 <td>{{ tag.name }}</td>
131 131 <td class="settings-mono">{{ tag.slug }}</td>
132 132 <td class="settings-actions">
133 - <form method="post" action="/p/{{ community_slug }}/settings/tags/delete" class="form-inline" onsubmit="return confirm('Delete this tag?')">
133 + <form method="post" action="/p/{{ community_slug }}/settings/tags/delete" class="form-inline" data-confirm="Delete this tag?">
134 134 <input type="hidden" name="tag_id" value="{{ tag.id }}">
135 135 <button type="submit" class="post-action-link post-action-delete">delete</button>
136 136 </form>
@@ -106,7 +106,7 @@
106 106 <form method="post" action="/p/{{ community_slug }}/moderation/flags/{{ flag.flag_id }}/dismiss" class="form-inline">
107 107 <button type="submit" class="post-action-link">dismiss</button>
108 108 </form>
109 - <form method="post" action="/p/{{ community_slug }}/moderation/flags/{{ flag.flag_id }}/remove" class="form-inline" onsubmit="return confirm('Remove this post and resolve all flags on it?')">
109 + <form method="post" action="/p/{{ community_slug }}/moderation/flags/{{ flag.flag_id }}/remove" class="form-inline" data-confirm="Remove this post and resolve all flags on it?">
110 110 <button type="submit" class="post-action-link post-action-delete">remove post</button>
111 111 </form>
112 112 </td>
@@ -47,7 +47,7 @@
47 47 {% endif %}
48 48 {% if can_mod_thread %}
49 49 <a href="/p/{{ community_slug }}/{{ category_slug }}/{{ thread_id }}/edit" class="post-action-link">edit title</a>
50 - <form method="post" action="/p/{{ community_slug }}/{{ category_slug }}/{{ thread_id }}/delete" class="form-inline" onsubmit="return confirm('Delete this thread? This cannot be undone.')">
50 + <form method="post" action="/p/{{ community_slug }}/{{ category_slug }}/{{ thread_id }}/delete" class="form-inline" data-confirm="Delete this thread? This cannot be undone.">
51 51 <button type="submit" class="post-action-link post-action-delete">delete thread</button>
52 52 </form>
53 53 {% endif %}
@@ -86,7 +86,7 @@
86 86 </details>
87 87 {% endif %}
88 88 {% if post.can_remove %}
89 - <form method="post" action="/p/{{ community_slug }}/{{ category_slug }}/{{ thread_id }}/posts/{{ post.id }}/remove" class="form-inline" onsubmit="return confirm('Remove this post? The content will be hidden but preserved for audit.')">
89 + <form method="post" action="/p/{{ community_slug }}/{{ category_slug }}/{{ thread_id }}/posts/{{ post.id }}/remove" class="form-inline" data-confirm="Remove this post? The content will be hidden but preserved for audit.">
90 90 <button type="submit" class="post-action-link post-action-delete">remove</button>
91 91 </form>
92 92 {% endif %}
@@ -16,7 +16,7 @@
16 16 <div class="page-header">
17 17 <h2>Tracked Threads</h2>
18 18 {% if !threads.is_empty() %}
19 - <form method="post" action="/tracked/stop-all" onsubmit="return confirm('Stop tracking all threads?')">
19 + <form method="post" action="/tracked/stop-all" data-confirm="Stop tracking all threads?">
20 20 <button type="submit" class="btn-secondary">Stop tracking all</button>
21 21 </form>
22 22 {% endif %}