ultimate-jekyll-manager 1.1.2 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -14,6 +14,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
14
14
  - `Fixed` for any bug fixes.
15
15
  - `Security` in case of vulnerabilities.
16
16
 
17
+ ---
18
+ ## [1.1.3] - 2026-04-08
19
+ ### Security
20
+ - Escape all remaining unescaped innerHTML values (formatDate, formatDateTime, formatIncidentStatus, formatTimeAgo, statusLabels, dataStatusMap, numeric values) for defense-in-depth
21
+ - Add `https://` scheme validation to `window.open()` and `href` attributes for push notification URLs in calendar-events
22
+ - Remove `style` from DOMPurify `ALLOWED_ATTR` in campaign email preview to prevent CSS-based data exfiltration
23
+
17
24
  ---
18
25
  ## [1.1.2] - 2026-04-08
19
26
  ### Fixed
@@ -141,7 +141,7 @@ export default class CalendarEvents {
141
141
  return;
142
142
  }
143
143
  const url = $notification.dataset.clickAction;
144
- if (url) {
144
+ if (url && /^https?:\/\//i.test(url)) {
145
145
  window.open(url, '_blank', 'noopener,noreferrer');
146
146
  }
147
147
  });
@@ -688,10 +688,12 @@ export default class CalendarEvents {
688
688
 
689
689
  if (campaign.type === 'push') {
690
690
  if (settings.icon) {
691
- html += `<tr><td class="text-muted">Icon</td><td><a href="${webManager.utilities().escapeHTML(settings.icon)}" target="_blank" rel="noopener">${webManager.utilities().escapeHTML(settings.icon)}</a></td></tr>`;
691
+ const iconUrl = /^https?:\/\//i.test(settings.icon) ? webManager.utilities().escapeHTML(settings.icon) : '#';
692
+ html += `<tr><td class="text-muted">Icon</td><td><a href="${iconUrl}" target="_blank" rel="noopener">${webManager.utilities().escapeHTML(settings.icon)}</a></td></tr>`;
692
693
  }
693
694
  if (settings.clickAction) {
694
- html += `<tr><td class="text-muted">Click URL</td><td><a href="${webManager.utilities().escapeHTML(settings.clickAction)}" target="_blank" rel="noopener">${webManager.utilities().escapeHTML(settings.clickAction)}</a></td></tr>`;
695
+ const clickUrl = /^https?:\/\//i.test(settings.clickAction) ? webManager.utilities().escapeHTML(settings.clickAction) : '#';
696
+ html += `<tr><td class="text-muted">Click URL</td><td><a href="${clickUrl}" target="_blank" rel="noopener">${webManager.utilities().escapeHTML(settings.clickAction)}</a></td></tr>`;
695
697
  }
696
698
  }
697
699
 
@@ -30,7 +30,7 @@ async function renderEmailPreview(formData) {
30
30
  const renderedContent = content
31
31
  ? DOMPurify.sanitize(md.render(content), {
32
32
  ALLOWED_TAGS: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'br', 'hr', 'ul', 'ol', 'li', 'a', 'b', 'strong', 'i', 'em', 'u', 's', 'del', 'blockquote', 'pre', 'code', 'img', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'div', 'span', 'sup', 'sub'],
33
- ALLOWED_ATTR: ['href', 'src', 'alt', 'title', 'width', 'height', 'class', 'style', 'target', 'rel'],
33
+ ALLOWED_ATTR: ['href', 'src', 'alt', 'title', 'width', 'height', 'class', 'target', 'rel'],
34
34
  })
35
35
  : '<p class="text-muted">No content yet</p>';
36
36
 
@@ -327,7 +327,7 @@ async function loadRecentUsers() {
327
327
  $row.innerHTML = `
328
328
  <td class="text-truncate" style="max-width: 200px;">${webManager.utilities().escapeHTML(email)}</td>
329
329
  <td><span class="badge bg-body-secondary text-body">${webManager.utilities().escapeHTML(capitalize(plan))}</span></td>
330
- <td class="text-muted small">${timeAgo}</td>
330
+ <td class="text-muted small">${webManager.utilities().escapeHTML(timeAgo)}</td>
331
331
  `;
332
332
  $tbody.appendChild($row);
333
333
  });
@@ -376,7 +376,7 @@ async function loadRecentOrders() {
376
376
  <td class="font-monospace small text-truncate" style="max-width: 120px;" title="${webManager.utilities().escapeHTML(orderId)}">${webManager.utilities().escapeHTML(orderId)}</td>
377
377
  <td><span class="badge bg-body-secondary text-body">${webManager.utilities().escapeHTML(capitalize(product))}</span></td>
378
378
  <td class="small">${webManager.utilities().escapeHTML(capitalize(processor))}</td>
379
- <td class="text-muted small">${timeAgo}</td>
379
+ <td class="text-muted small">${webManager.utilities().escapeHTML(timeAgo)}</td>
380
380
  `;
381
381
  $tbody.appendChild($row);
382
382
  });
@@ -408,7 +408,7 @@ function renderCellValue(value) {
408
408
  if (typeof value === 'number') {
409
409
  // Check if it looks like a UNIX timestamp (reasonable range)
410
410
  if (value > 1000000000 && value < 10000000000) {
411
- return `<span title="${value}">${new Date(value * 1000).toLocaleDateString()}</span>`;
411
+ return `<span title="${webManager.utilities().escapeHTML(String(value))}">${webManager.utilities().escapeHTML(new Date(value * 1000).toLocaleDateString())}</span>`;
412
412
  }
413
413
  return webManager.utilities().escapeHTML(value.toLocaleString());
414
414
  }
@@ -135,10 +135,10 @@ function showUptimeTooltip(e, $bar, index, totalDays) {
135
135
  });
136
136
 
137
137
  $tooltip.innerHTML = `
138
- <div class="fw-semibold mb-1">${dateStr}</div>
138
+ <div class="fw-semibold mb-1">${webManager.utilities().escapeHTML(dateStr)}</div>
139
139
  <div class="d-flex align-items-center gap-2">
140
140
  <span class="status-dot rounded-circle ${config.statusClasses[status]}"></span>
141
- <span>${config.statusLabels[status]}</span>
141
+ <span>${webManager.utilities().escapeHTML(config.statusLabels[status])}</span>
142
142
  </div>
143
143
  <div class="text-muted small">Uptime: ${webManager.utilities().escapeHTML(uptime)}</div>
144
144
  `;
@@ -531,7 +531,7 @@ function updateMaintenance(maintenanceItems) {
531
531
  <div class="card-body p-4">
532
532
  <div class="d-flex justify-content-between align-items-start mb-2">
533
533
  <h4 class="h5 fw-semibold mb-0">${webManager.utilities().escapeHTML(item.title)}</h4>
534
- <span class="text-muted small">${formatDate(item.scheduled_for)}</span>
534
+ <span class="text-muted small">${webManager.utilities().escapeHTML(formatDate(item.scheduled_for))}</span>
535
535
  </div>
536
536
  <p class="text-muted mb-0">${webManager.utilities().escapeHTML(item.description)}</p>
537
537
  ${item.affected_services ? `
@@ -581,15 +581,15 @@ function updateIncidents(incidents) {
581
581
  <div class="d-flex justify-content-between align-items-start mb-2 flex-wrap gap-2">
582
582
  <h4 class="h5 fw-semibold mb-0">${webManager.utilities().escapeHTML(incident.title)}</h4>
583
583
  <div class="d-flex align-items-center gap-2">
584
- <span class="badge ${getIncidentBadgeClasses(incident.status)}">${formatIncidentStatus(incident.status)}</span>
585
- <span class="text-muted small">${formatDate(incident.created_at)}</span>
584
+ <span class="badge ${getIncidentBadgeClasses(incident.status)}">${webManager.utilities().escapeHTML(formatIncidentStatus(incident.status))}</span>
585
+ <span class="text-muted small">${webManager.utilities().escapeHTML(formatDate(incident.created_at))}</span>
586
586
  </div>
587
587
  </div>
588
588
  ${incident.updates && incident.updates.length > 0 ? `
589
589
  <div class="incident-timeline mt-3">
590
590
  ${incident.updates.map(update => `
591
- <div class="timeline-item pb-3" data-status="${config.dataStatusMap[update.status] || ''}">
592
- <div class="small text-muted mb-1">${formatDateTime(update.created_at)}</div>
591
+ <div class="timeline-item pb-3" data-status="${webManager.utilities().escapeHTML(config.dataStatusMap[update.status] || '')}">
592
+ <div class="small text-muted mb-1">${webManager.utilities().escapeHTML(formatDateTime(update.created_at))}</div>
593
593
  <div class="small">${webManager.utilities().escapeHTML(update.message)}</div>
594
594
  </div>
595
595
  `).join('')}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ultimate-jekyll-manager",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "Ultimate Jekyll dependency manager",
5
5
  "main": "dist/index.js",
6
6
  "exports": {