Harden CSP by removing unsafe-inline for scripts

Refactored all inline event handlers (onclick, onchange, onsubmit) to use
addEventListener with data-action attributes and event delegation pattern.

Changes:
- views/*.php: Replaced inline handlers with data-action attributes
- views/admin/*.php: Same refactoring for all admin views
- assets/js/dashboard.js: Added event delegation for bulk/quick action modals
- assets/js/ticket.js: Added event delegation for dynamic elements
- assets/js/markdown.js: Refactored toolbar button handlers
- assets/js/keyboard-shortcuts.js: Refactored modal close button
- SecurityHeadersMiddleware.php: Enabled strict CSP with nonces

The CSP now uses script-src 'self' 'nonce-{nonce}' instead of 'unsafe-inline',
significantly improving XSS protection.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-30 13:15:55 -05:00
parent 37be81b3e2
commit c3f7593f3c
13 changed files with 564 additions and 158 deletions

View File

@@ -77,7 +77,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<div class="ascii-frame-inner">
<div class="detail-group">
<label for="templateSelect">Use Template (Optional)</label>
<select id="templateSelect" class="editable" onchange="loadTemplate()">
<select id="templateSelect" class="editable" data-action="load-template">
<option value="">-- No Template --</option>
<?php if (isset($templates) && !empty($templates)): ?>
<?php foreach ($templates as $template): ?>
@@ -172,7 +172,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<div class="ascii-frame-inner">
<div class="detail-group">
<label for="visibility">Ticket Visibility</label>
<select id="visibility" name="visibility" class="editable" onchange="toggleVisibilityGroups()">
<select id="visibility" name="visibility" class="editable" data-action="toggle-visibility-groups">
<option value="public" selected>Public - All authenticated users</option>
<option value="internal">Internal - Specific groups only</option>
<option value="confidential">Confidential - Creator, assignee, admins only</option>
@@ -230,7 +230,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<div class="ascii-frame-inner">
<div class="ticket-footer">
<button type="submit" class="btn primary">Create Ticket</button>
<button type="button" onclick="window.location.href='<?php echo $GLOBALS['config']['BASE_URL']; ?>/'" class="btn back-btn">Cancel</button>
<button type="button" data-action="navigate" data-url="<?php echo $GLOBALS['config']['BASE_URL']; ?>/" class="btn back-btn">Cancel</button>
</div>
</div>
</div>
@@ -303,10 +303,32 @@ $nonce = SecurityHeadersMiddleware::getNonce();
groupsContainer.style.display = 'block';
} else {
groupsContainer.style.display = 'none';
// Uncheck all group checkboxes when hiding
document.querySelectorAll('.visibility-group-checkbox').forEach(cb => cb.checked = false);
}
}
// Event delegation for data-action handlers
document.addEventListener('click', function(event) {
const target = event.target.closest('[data-action]');
if (!target) return;
const action = target.dataset.action;
if (action === 'navigate') {
window.location.href = target.dataset.url;
}
});
document.addEventListener('change', function(event) {
const target = event.target.closest('[data-action]');
if (!target) return;
const action = target.dataset.action;
if (action === 'load-template') {
loadTemplate();
} else if (action === 'toggle-visibility-groups') {
toggleVisibilityGroups();
}
});
</script>
</body>
</html>