fix: Phase 2 P0 全清零 — 14 項安全與功能修復完成
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:
ogt
2026-04-27 21:11:52 +08:00
parent f59b23f969
commit 237d3af76f
12 changed files with 178 additions and 42 deletions

View File

@@ -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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
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>
`;
});