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>
5 files changed,
+15 insertions,
-5 deletions
| 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 |
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 |
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 |
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 |
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 |
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 %}
|