fix: CSRF on ticket create form, DOM-safe duplicate list, audit-log param validation
- TicketController::create: validate csrf_token from POST before processing - CreateTicketView: emit hidden csrf_token field; replace innerHTML duplicate list with DOM methods to prevent any XSS path; guard checkDuplicates() with lt.api availability check - index.php audit-log: allowlist action_type; validate date_from/date_to as YYYY-MM-DD before passing to query Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,9 @@ include __DIR__ . '/layout_header.php';
|
||||
class="create-ticket-form"
|
||||
novalidate>
|
||||
|
||||
<input type="hidden" name="csrf_token"
|
||||
value="<?= htmlspecialchars(CsrfMiddleware::getToken(), ENT_QUOTES, 'UTF-8') ?>">
|
||||
|
||||
<?php if (isset($error)): ?>
|
||||
<div class="lt-msg lt-msg-danger lt-mb-md" role="alert">
|
||||
<strong>Error:</strong> <?= htmlspecialchars($error, ENT_QUOTES, 'UTF-8') ?>
|
||||
@@ -255,26 +258,41 @@ include __DIR__ . '/layout_header.php';
|
||||
});
|
||||
|
||||
function checkDuplicates(title) {
|
||||
if (!window.lt || typeof lt.api === 'undefined') return;
|
||||
lt.api.get('/api/check_duplicates.php?title=' + encodeURIComponent(title))
|
||||
.then(function (data) {
|
||||
var warn = document.getElementById('duplicateWarning');
|
||||
var list = document.getElementById('duplicatesList');
|
||||
if (data.success && data.duplicates && data.duplicates.length > 0) {
|
||||
var html = '<ul class="duplicate-list lt-text-sm">';
|
||||
var ul = document.createElement('ul');
|
||||
ul.className = 'duplicate-list lt-text-sm';
|
||||
data.duplicates.forEach(function (dup) {
|
||||
html += '<li><a href="/ticket/' + lt.escHtml(dup.ticket_id) + '" target="_blank">#' +
|
||||
lt.escHtml(dup.ticket_id) + '</a> — ' + lt.escHtml(dup.title) +
|
||||
' <span class="lt-text-muted">(' + dup.similarity + '% match, ' +
|
||||
lt.escHtml(dup.status) + ')</span></li>';
|
||||
var li = document.createElement('li');
|
||||
var a = document.createElement('a');
|
||||
a.href = '/ticket/' + encodeURIComponent(dup.ticket_id);
|
||||
a.target = '_blank';
|
||||
a.textContent = '#' + dup.ticket_id;
|
||||
var dash = document.createTextNode(' \u2014 ' + dup.title + ' ');
|
||||
var badge = document.createElement('span');
|
||||
badge.className = 'lt-text-muted';
|
||||
badge.textContent = '(' + dup.similarity + '% match, ' + dup.status + ')';
|
||||
li.appendChild(a);
|
||||
li.appendChild(dash);
|
||||
li.appendChild(badge);
|
||||
ul.appendChild(li);
|
||||
});
|
||||
html += '</ul><p class="lt-text-xs lt-text-muted lt-mt-sm">Check these before creating a new ticket.</p>';
|
||||
list.innerHTML = html;
|
||||
var hint = document.createElement('p');
|
||||
hint.className = 'lt-text-xs lt-text-muted lt-mt-sm';
|
||||
hint.textContent = 'Check these before creating a new ticket.';
|
||||
list.innerHTML = '';
|
||||
list.appendChild(ul);
|
||||
list.appendChild(hint);
|
||||
warn.classList.remove('is-hidden');
|
||||
} else {
|
||||
warn.classList.add('is-hidden');
|
||||
}
|
||||
})
|
||||
.catch(function () { /* silent */ });
|
||||
.catch(function () { /* silent — duplicate check is non-critical */ });
|
||||
}
|
||||
|
||||
// ── Visibility groups toggle ──────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user