Fix JS SyntaxError breaking tabs, textarea scrolling, and XSS escaping

Bug fixes:
- ticket.js: Remove duplicate const textarea declaration inside showMentionSuggestions()
  (was redeclaring a parameter, causing SyntaxError that broke all tab switching)
- ticket.css: Add overflow:hidden + resize:none to disabled textarea so description
  shows full height without internal scrollbar (page scrolls instead)
- ticket.js: Trigger height recalculation when entering edit mode on description

XSS/escaping fixes:
- TicketView.php: htmlspecialchars() on description textarea content (closes </textarea> injection risk)
- TicketView.php: htmlspecialchars() on ticket status and workflow transition status strings
- DashboardView.php: htmlspecialchars() on $cat/$type in input value= attributes
- RecurringTicketsView.php: htmlspecialchars() on composed schedule string

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-20 20:34:55 -04:00
parent 7695c6134c
commit 31aa7d1b81
5 changed files with 12 additions and 9 deletions

View File

@@ -584,6 +584,8 @@ textarea.editable {
background: var(--bg-secondary); background: var(--bg-secondary);
cursor: default; cursor: default;
border-color: transparent; border-color: transparent;
overflow: hidden;
resize: none;
} }
/* Button Styles */ /* Button Styles */

View File

@@ -86,6 +86,8 @@ function toggleEditMode() {
// Enable description (textarea) // Enable description (textarea)
if (descriptionField) { if (descriptionField) {
descriptionField.disabled = false; descriptionField.disabled = false;
descriptionField.style.height = 'auto';
descriptionField.style.height = descriptionField.scrollHeight + 'px';
} }
// Enable metadata fields (priority, category, type) // Enable metadata fields (priority, category, type)
@@ -1036,7 +1038,6 @@ function showMentionSuggestions(query, textarea) {
mentionAutocomplete.innerHTML = html; mentionAutocomplete.innerHTML = html;
mentionAutocomplete.classList.add('active'); mentionAutocomplete.classList.add('active');
const textarea = document.getElementById('newComment');
if (textarea) textarea.setAttribute('aria-expanded', 'true'); if (textarea) textarea.setAttribute('aria-expanded', 'true');
selectedMentionIndex = 0; selectedMentionIndex = 0;

View File

@@ -144,7 +144,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<label> <label>
<input type="checkbox" <input type="checkbox"
name="category" name="category"
value="<?php echo $cat; ?>" value="<?php echo htmlspecialchars($cat); ?>"
<?php echo in_array($cat, $currentCategories) ? 'checked' : ''; ?>> <?php echo in_array($cat, $currentCategories) ? 'checked' : ''; ?>>
<?php echo htmlspecialchars($cat); ?> <?php echo htmlspecialchars($cat); ?>
</label> </label>
@@ -161,7 +161,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<label> <label>
<input type="checkbox" <input type="checkbox"
name="type" name="type"
value="<?php echo $type; ?>" value="<?php echo htmlspecialchars($type); ?>"
<?php echo in_array($type, $currentTypes) ? 'checked' : ''; ?>> <?php echo in_array($type, $currentTypes) ? 'checked' : ''; ?>>
<?php echo htmlspecialchars($type); ?> <?php echo htmlspecialchars($type); ?>
</label> </label>

View File

@@ -246,14 +246,14 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<div class="header-controls"> <div class="header-controls">
<div class="status-priority-group"> <div class="status-priority-group">
<select id="statusSelect" class="editable status-select status-<?php echo str_replace(' ', '-', strtolower($ticket["status"])); ?>" data-field="status" data-action="update-ticket-status" aria-label="Change ticket status"> <select id="statusSelect" class="editable status-select status-<?php echo str_replace(' ', '-', strtolower($ticket["status"])); ?>" data-field="status" data-action="update-ticket-status" aria-label="Change ticket status">
<option value="<?php echo $ticket['status']; ?>" selected> <option value="<?php echo htmlspecialchars($ticket['status']); ?>" selected>
<?php echo $ticket['status']; ?> (current) <?php echo htmlspecialchars($ticket['status']); ?> (current)
</option> </option>
<?php foreach ($allowedTransitions as $transition): ?> <?php foreach ($allowedTransitions as $transition): ?>
<option value="<?php echo $transition['to_status']; ?>" <option value="<?php echo htmlspecialchars($transition['to_status']); ?>"
data-requires-comment="<?php echo $transition['requires_comment'] ? '1' : '0'; ?>" data-requires-comment="<?php echo $transition['requires_comment'] ? '1' : '0'; ?>"
data-requires-admin="<?php echo $transition['requires_admin'] ? '1' : '0'; ?>"> data-requires-admin="<?php echo $transition['requires_admin'] ? '1' : '0'; ?>">
<?php echo $transition['to_status']; ?> <?php echo htmlspecialchars($transition['to_status']); ?>
<?php if ($transition['requires_comment']): ?> *<?php endif; ?> <?php if ($transition['requires_comment']): ?> *<?php endif; ?>
<?php if ($transition['requires_admin']): ?> (Admin)<?php endif; ?> <?php if ($transition['requires_admin']): ?> (Admin)<?php endif; ?>
</option> </option>
@@ -295,7 +295,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<div class="ascii-subsection-header">Description</div> <div class="ascii-subsection-header">Description</div>
<div class="detail-group full-width"> <div class="detail-group full-width">
<label>Description</label> <label>Description</label>
<textarea class="editable" data-field="description" disabled><?php echo $ticket["description"]; ?></textarea> <textarea class="editable" data-field="description" disabled><?php echo htmlspecialchars($ticket["description"] ?? ''); ?></textarea>
</div> </div>
</div> </div>

View File

@@ -81,7 +81,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
$schedule .= ' (Day ' . $rt['schedule_day'] . ')'; $schedule .= ' (Day ' . $rt['schedule_day'] . ')';
} }
$schedule .= ' @ ' . substr($rt['schedule_time'], 0, 5); $schedule .= ' @ ' . substr($rt['schedule_time'], 0, 5);
echo $schedule; echo htmlspecialchars($schedule);
?> ?>
</td> </td>
<td><?php echo htmlspecialchars($rt['category']); ?></td> <td><?php echo htmlspecialchars($rt['category']); ?></td>