fix: remove CSP-blocked inline event handlers (onerror, onclick)

- Remove all onerror="this.style.display='none'" from avatar imgs in
  layout_header.php, DashboardView.php, and TicketView.php (PHP + JS)
- Replace onclick SLA dismiss with data-action="dismiss-priority-banner"
  attribute; handler wired via existing click delegation in TicketView.php
- Global capture-phase error delegation in layout_footer.php handles all
  avatar image failures by adding .lt-avatar-img-err class (CSS display:none)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-04 22:15:45 -04:00
parent 1ab374531c
commit ff109a710c
3 changed files with 11 additions and 12 deletions
+1 -1
View File
@@ -331,7 +331,7 @@ include __DIR__ . '/layout_header.php';
<div class="workload-item"> <div class="workload-item">
<div class="lt-avatar lt-avatar--sm <?= $avatarColor ?>" aria-hidden="true" title="<?= htmlspecialchars($name) ?>"> <div class="lt-avatar lt-avatar--sm <?= $avatarColor ?>" aria-hidden="true" title="<?= htmlspecialchars($name) ?>">
<?php if ($userId > 0): ?> <?php if ($userId > 0): ?>
<img src="/api/user_avatar.php?user_id=<?= $userId ?>" alt="" class="lt-avatar-img" onerror="this.style.display='none'"> <img src="/api/user_avatar.php?user_id=<?= $userId ?>" alt="" class="lt-avatar-img">
<?php endif ?> <?php endif ?>
<span class="lt-avatar-initials"><?= htmlspecialchars($initials) ?></span> <span class="lt-avatar-initials"><?= htmlspecialchars($initials) ?></span>
</div> </div>
+10 -10
View File
@@ -212,12 +212,7 @@ $progressClass = $slaBreached ? 'lt-progress--red' : ($slaPct >= 75 ? 'lt-progr
</div> </div>
</div> </div>
</div> </div>
<button type="button" class="lt-alert-close" aria-label="Dismiss" <button type="button" class="lt-alert-close" data-action="dismiss-priority-banner" aria-label="Dismiss">&#x2715;</button>
onclick="(function(btn){
var id=btn.closest('[data-alert-id]').dataset.alertId;
try{sessionStorage.setItem('lt_dismissed_'+id,'1');}catch(e){}
btn.closest('.lt-alert').classList.add('dismissed');
})(this)">&#x2715;</button>
</div> </div>
<script nonce="<?= htmlspecialchars($nonce, ENT_QUOTES, 'UTF-8') ?>"> <script nonce="<?= htmlspecialchars($nonce, ENT_QUOTES, 'UTF-8') ?>">
(function(){ (function(){
@@ -564,8 +559,7 @@ $progressClass = $slaBreached ? 'lt-progress--red' : ($slaPct >= 75 ? 'lt-progr
<?php if ($commentUserId > 0): ?> <?php if ($commentUserId > 0): ?>
<img src="/api/user_avatar.php?user_id=<?= $commentUserId ?>" <img src="/api/user_avatar.php?user_id=<?= $commentUserId ?>"
alt="" alt=""
class="lt-avatar-img" class="lt-avatar-img">
onerror="this.style.display='none'">
<?php endif ?> <?php endif ?>
<span class="lt-avatar-initials"><?= htmlspecialchars($initials) ?></span> <span class="lt-avatar-initials"><?= htmlspecialchars($initials) ?></span>
</div> </div>
@@ -978,7 +972,7 @@ document.addEventListener('DOMContentLoaded', function () {
for (var i = 0; i < (w.display_name || '').length; i++) hash = ((hash << 5) - hash + (w.display_name || '').charCodeAt(i)) | 0; for (var i = 0; i < (w.display_name || '').length; i++) hash = ((hash << 5) - hash + (w.display_name || '').charCodeAt(i)) | 0;
var color = avatarColors[Math.abs(hash) % 4]; var color = avatarColors[Math.abs(hash) % 4];
html += '<div class="lt-avatar lt-avatar--xs ' + color + '" title="' + lt.escHtml(w.display_name) + '" aria-label="' + lt.escHtml(w.display_name) + '">' + html += '<div class="lt-avatar lt-avatar--xs ' + color + '" title="' + lt.escHtml(w.display_name) + '" aria-label="' + lt.escHtml(w.display_name) + '">' +
'<img src="/api/user_avatar.php?user_id=' + w.user_id + '" alt="" class="lt-avatar-img" onerror="this.style.display=\'none\'">' + '<img src="/api/user_avatar.php?user_id=' + w.user_id + '" alt="" class="lt-avatar-img">' +
'<span class="lt-avatar-initials">' + lt.escHtml(initials) + '</span>' + '<span class="lt-avatar-initials">' + lt.escHtml(initials) + '</span>' +
'</div>'; '</div>';
}); });
@@ -1121,6 +1115,12 @@ document.addEventListener('DOMContentLoaded', function () {
if (typeof editComment === 'function') editComment(parseInt(commentId, 10)); if (typeof editComment === 'function') editComment(parseInt(commentId, 10));
} else if (action === 'delete-comment' && commentId) { } else if (action === 'delete-comment' && commentId) {
if (typeof deleteComment === 'function') deleteComment(parseInt(commentId, 10)); if (typeof deleteComment === 'function') deleteComment(parseInt(commentId, 10));
} else if (action === 'dismiss-priority-banner') {
var banner = target.closest('[data-alert-id]');
if (banner) {
try { sessionStorage.setItem('lt_dismissed_' + banner.dataset.alertId, '1'); } catch(ex) {}
banner.classList.add('dismissed');
}
} }
}); });
@@ -1237,7 +1237,7 @@ document.addEventListener('DOMContentLoaded', function () {
' data-action="delete-comment" data-comment-id="' + commentId + '" aria-label="Delete comment">Del</button>' : ''; ' data-action="delete-comment" data-comment-id="' + commentId + '" aria-label="Delete comment">Del</button>' : '';
var threadLine = parentId ? '<div class="thread-line" aria-hidden="true"></div>' : ''; var threadLine = parentId ? '<div class="thread-line" aria-hidden="true"></div>' : '';
var avatarImg = userId > 0 ? var avatarImg = userId > 0 ?
'<img src="/api/user_avatar.php?user_id=' + userId + '" alt="" class="lt-avatar-img" onerror="this.style.display=\'none\'">' : ''; '<img src="/api/user_avatar.php?user_id=' + userId + '" alt="" class="lt-avatar-img">' : '';
var div = document.createElement('div'); var div = document.createElement('div');
div.className = 'comment ' + depthClass + ' ' + threadClass; div.className = 'comment ' + depthClass + ' ' + threadClass;
-1
View File
@@ -166,7 +166,6 @@ $_lt_assetVer = $GLOBALS['config']['ASSET_VERSION'] ?? '20260329';
<img src="/api/user_avatar.php?user_id=<?= $_lt_userId ?>" <img src="/api/user_avatar.php?user_id=<?= $_lt_userId ?>"
alt="" alt=""
class="lt-avatar-img" class="lt-avatar-img"
onerror="this.style.display='none'">
<?php endif ?> <?php endif ?>
<span class="lt-avatar-initials"><?= htmlspecialchars($_lt_initials) ?></span> <span class="lt-avatar-initials"><?= htmlspecialchars($_lt_initials) ?></span>
</div> </div>