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

@@ -235,6 +235,7 @@ class AutoHealService:
action_type = playbook["action_type"]
params = playbook.get("action_params", {})
started_at = datetime.now()
if action_type == "DOCKER_RESTART":
result = self._docker_restart(params)
elif action_type == "WAIT_RETRY":
@@ -246,7 +247,8 @@ class AutoHealService:
else:
return AutoHealResult(success=False, action=action_type, message="Unhandled action_type")
self._alert_and_store(playbook, context)
duration_ms = (datetime.now() - started_at).total_seconds() * 1000
self._alert_and_store(playbook, context, result, duration_ms)
return result
def _docker_restart(self, params: Dict[str, Any]) -> AutoHealResult:
@@ -304,7 +306,49 @@ class AutoHealService:
out = result["stdout"] or result["stderr"]
return AutoHealResult(success=result["success"], action="SSH_CMD", message=out)
def _alert_and_store(self, playbook: Dict[str, Any], context: Dict[str, Any]) -> None:
def _alert_and_store(
self,
playbook: Dict[str, Any],
context: Dict[str, Any],
result: "AutoHealResult",
duration_ms: float = 0.0,
) -> None:
_store_escalation(playbook["action_type"])
# TODO: integrate triaged_alert if needed
self._log.info("[AutoHeal] Alert stored for: %s", playbook["action_type"])
self._write_heal_log(playbook, context, result, duration_ms)
def _write_heal_log(
self,
playbook: Dict[str, Any],
context: Dict[str, Any],
result: "AutoHealResult",
duration_ms: float,
) -> None:
"""Write heal execution record to heal_logs table (ADR-013 Step 6/7)."""
from database.autoheal_models import HealLog
incident_id = context.get("incident_id")
if not incident_id:
self._log.warning("[AutoHeal] _write_heal_log: incident_id missing in context, skipping DB write")
return
session = get_session()
try:
log_entry = HealLog(
incident_id=int(incident_id),
playbook_id=int(playbook["id"]),
action_type=playbook["action_type"],
action_detail=json.dumps(playbook.get("action_params", {}), ensure_ascii=False),
result="success" if result.success else "failed",
result_output=result.message[:2000] if result.message else None,
duration_ms=duration_ms,
)
session.add(log_entry)
session.commit()
self._log.info(
"[AutoHeal] heal_log written: incident_id=%s playbook=%s result=%s duration_ms=%.1f",
incident_id, playbook["id"], log_entry.result, duration_ms,
)
except Exception as exc:
self._log.error("[AutoHeal] _write_heal_log DB error: %s", exc)
session.rollback()
finally:
session.close()

View File

@@ -7,10 +7,10 @@ Google Drive 服務模組
import os
import io
import json
import logging
from typing import List, Optional, Dict, Any
from datetime import datetime
import pickle
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
@@ -27,7 +27,8 @@ SCOPES = ['https://www.googleapis.com/auth/drive']
# 認證檔案路徑
CREDENTIALS_FILE = 'config/google_credentials.json'
TOKEN_FILE = 'config/google_token.pickle'
TOKEN_FILE = 'config/google_token.json'
_LEGACY_PICKLE_FILE = 'config/google_token.pickle'
class GoogleDriveService:
@@ -46,10 +47,18 @@ class GoogleDriveService:
bool: 認證是否成功
"""
try:
# 舊版 pickle token 遷移提示(不自動刪除舊檔)
if os.path.exists(_LEGACY_PICKLE_FILE) and not os.path.exists(TOKEN_FILE):
logger.warning(
"[GoogleDrive] 偵測到舊版 token.pickle已改用 JSON 格式。"
"請重新執行認證流程以產生新 token舊 pickle 檔案不會被自動刪除。"
)
# 檢查是否已有 token
if os.path.exists(TOKEN_FILE):
with open(TOKEN_FILE, 'rb') as token:
self.credentials = pickle.load(token)
with open(TOKEN_FILE, 'r') as token:
token_data = json.load(token)
self.credentials = Credentials.from_authorized_user_info(token_data, SCOPES)
# 如果沒有有效憑證,進行認證流程
if not self.credentials or not self.credentials.valid:
@@ -70,9 +79,9 @@ class GoogleDriveService:
# 對於「電腦版應用程式」類型,使用預設行為讓 Google 自動選擇埠號
self.credentials = flow.run_local_server()
# 儲存憑證供下次使用
with open(TOKEN_FILE, 'wb') as token:
pickle.dump(self.credentials, token)
# 儲存憑證供下次使用JSON 格式,安全無 RCE 風險)
with open(TOKEN_FILE, 'w') as token:
token.write(self.credentials.to_json())
logger.info(f"憑證已儲存到: {TOKEN_FILE}")
# 建立 Drive API 服務

View File

@@ -176,17 +176,21 @@ class TrendTelegramBot:
async def cmd_start(self, update: Update, context):
"""開始指令 - 顯示主選單"""
user = update.effective_user
await update.message.reply_text(
f"👋 嗨,{user.first_name}\n\n"
f"我是 *MOMO 趨勢助手*,您的智能商業分析夥伴。\n"
f"請選擇下方功能,或直接輸入問題:",
reply_markup=self._get_main_menu_keyboard()
)
try:
user = update.effective_user
await update.message.reply_text(
f"👋 嗨,{user.first_name}\n\n"
f"我是 *MOMO 趨勢助手*,您的智能商業分析夥伴。\n"
f"請選擇下方功能,或直接輸入問題:",
reply_markup=self._get_main_menu_keyboard()
)
except Exception as e:
logger.error(f"[Bot] cmd_start 失敗: {e}", exc_info=True)
async def cmd_help(self, update: Update, context):
"""說明指令"""
help_text = """
try:
help_text = """
📖 *指令說明*
*趨勢查詢*
@@ -204,8 +208,10 @@ class TrendTelegramBot:
*每日摘要*
/daily - 查看今日趨勢摘要
"""
await update.message.reply_text(help_text, parse_mode='Markdown')
"""
await update.message.reply_text(help_text, parse_mode='Markdown')
except Exception as e:
logger.error(f"[Bot] cmd_help 失敗: {e}", exc_info=True)
async def cmd_trend(self, update: Update, context):
"""趨勢查詢指令"""