+ html += `
@${escapeHtml(user.username)}
${user.display_name ? `${escapeHtml(user.display_name)}` : ''}
`;
@@ -1212,6 +1212,31 @@ document.addEventListener('DOMContentLoaded', function() {
el.innerHTML = highlightMentions(el.innerHTML);
}
});
+
+ // Event delegation for dynamically created elements
+ document.addEventListener('click', function(e) {
+ const target = e.target.closest('[data-action]');
+ if (!target) return;
+
+ const action = target.dataset.action;
+ switch (action) {
+ case 'remove-dependency':
+ removeDependency(target.dataset.dependencyId);
+ break;
+ case 'delete-attachment':
+ deleteAttachment(target.dataset.attachmentId);
+ break;
+ case 'select-mention':
+ selectMention(target.dataset.username);
+ break;
+ case 'save-edit-comment':
+ saveEditComment(parseInt(target.dataset.commentId));
+ break;
+ case 'cancel-edit-comment':
+ cancelEditComment(parseInt(target.dataset.commentId));
+ break;
+ }
+ });
});
// ========================================
@@ -1251,8 +1276,8 @@ function editComment(commentId) {
Markdown
`;
diff --git a/middleware/SecurityHeadersMiddleware.php b/middleware/SecurityHeadersMiddleware.php
index 5be8c20..3c920ce 100644
--- a/middleware/SecurityHeadersMiddleware.php
+++ b/middleware/SecurityHeadersMiddleware.php
@@ -26,12 +26,9 @@ class SecurityHeadersMiddleware {
$nonce = self::getNonce();
// Content Security Policy - restricts where resources can be loaded from
- // Currently using 'unsafe-inline' for scripts due to legacy onclick handlers throughout views
- // NOTE: Nonce infrastructure exists (getNonce method, nonce attributes in views) but is not
- // enforced in CSP until all inline handlers are refactored to use addEventListener.
- // TODO: Complete refactoring of inline handlers, then change to:
- // script-src 'self' 'nonce-{$nonce}' (removing unsafe-inline)
- header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self';");
+ // Using nonces for scripts to prevent XSS attacks while allowing inline scripts with valid nonces
+ // All inline event handlers have been refactored to use addEventListener with data-action attributes
+ header("Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{$nonce}'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self';");
// Prevent clickjacking by disallowing framing
header("X-Frame-Options: DENY");
diff --git a/views/CreateTicketView.php b/views/CreateTicketView.php
index 1d54aaa..e8bac56 100644
--- a/views/CreateTicketView.php
+++ b/views/CreateTicketView.php
@@ -77,7 +77,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();