fix: Phase 2 P0 全清零 — 14 項安全與功能修復完成
Some checks failed
CD Pipeline / deploy (push) Failing after 2m59s
Some checks failed
CD Pipeline / deploy (push) Failing after 2m59s
P0-06: google_drive_service.py — pickle.load() 改 JSON token(消除 RCE 風險) P0-07: bot_api_routes.py:30 — BOT_API_TOKEN 移除硬編碼預設值 clawdbot_momo_2026 P0-08: auto_import_index.html — showAlert innerHTML 改 createTextNode(XSS 修復) P0-09: abc_analysis_detail.html + dashboard.html + daily_sales.html — Jinja2 | e 轉義 P0-10: openclaw_bot_routes.py:2634 — vendor PPT 補 return ppt_path(廠商報告恢復) P0-11: telegram_bot_service.py:177-214 — cmd_start/cmd_help 補 try/except P0-12: app.py:689-712 — 10 個 Blueprint 補齊 register(消滅 404 路由) P0-13: auto_heal_service.py — 實作 _write_heal_log(),AIOps 稽核閉環補完 P0-14: monitoring/prometheus.yml — 取消 alert_rules comment;新增 alert_rules.yml Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1343,22 +1343,39 @@
|
||||
if (data.products && data.products.length > 0) {
|
||||
document.getElementById('modalProductCount').innerText = `${data.products.length} 件商品`;
|
||||
|
||||
// XSS 防護:對 API 回傳的字串欄位進行 HTML 轉義
|
||||
function escapeHtml(str) {
|
||||
if (str == null) return '';
|
||||
return String(str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
let html = '';
|
||||
data.products.forEach(p => {
|
||||
const changeClass = p.change > 0 ? 'text-danger' : (p.change < 0 ? 'text-success' : 'text-muted');
|
||||
const changeIcon = p.change > 0 ? '↑' : (p.change < 0 ? '↓' : '');
|
||||
const changeText = p.change > 0 ? `+$${Math.abs(p.change).toLocaleString()}` : (p.change < 0 ? `-$${Math.abs(p.change).toLocaleString()}` : '$0');
|
||||
const safeImageUrl = escapeHtml(p.image_url);
|
||||
const safeUrl = escapeHtml(p.url);
|
||||
const safeProductId = escapeHtml(p.product_id);
|
||||
const safeName = escapeHtml(p.name);
|
||||
const safeCategory = escapeHtml(p.category);
|
||||
const safeUpdateTime = escapeHtml(p.update_time);
|
||||
|
||||
html += `
|
||||
<tr>
|
||||
<td><img src="${p.image_url}" class="product-thumb" onerror="this.src='/static/placeholder.png'" style="width: 60px; height: 60px; object-fit: cover; border-radius: 8px;"></td>
|
||||
<td><a href="${p.url}" target="_blank" class="text-decoration-none">${p.product_id}</a></td>
|
||||
<td><a href="${p.url}" target="_blank" class="text-decoration-none" title="${p.name}">${p.name}</a></td>
|
||||
<td><span class="badge bg-secondary">${p.category || '未分類'}</span></td>
|
||||
<td><img src="${safeImageUrl}" class="product-thumb" onerror="this.src='/static/placeholder.png'" style="width: 60px; height: 60px; object-fit: cover; border-radius: 8px;"></td>
|
||||
<td><a href="${safeUrl}" target="_blank" class="text-decoration-none">${safeProductId}</a></td>
|
||||
<td><a href="${safeUrl}" target="_blank" class="text-decoration-none" title="${safeName}">${safeName}</a></td>
|
||||
<td><span class="badge bg-secondary">${safeCategory || '未分類'}</span></td>
|
||||
<td>$${(p.old_price || 0).toLocaleString()}</td>
|
||||
<td><strong>$${(p.current_price || 0).toLocaleString()}</strong></td>
|
||||
<td><strong class="${changeClass}">${changeIcon} ${changeText}</strong></td>
|
||||
<td class="small text-muted">${p.update_time}</td>
|
||||
<td class="small text-muted">${safeUpdateTime}</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user