2026-03-23 重大事故修復與治理: 1. ADR-010: Secrets 集中管理 (Bitwarden + Sealed Secrets) 2. ADR-011: NetworkPolicy 變更治理 (偵測 + 告警 + 人工決策) 3. ADR-012: 危險操作治理 (Tier 分級 + CI/CD 攔截 + 審計) 4. UX-001: 告警疲勞解決方案 (時間衰減 + 智慧分組) CLAUDE.md 更新: - 新增最高優先級鐵律 (禁止 ClawBot、OpenClaw 核心、禁止危險 API) - 新增任務開始前必讀 Memory 對照表 事故教訓: - Telegram Token 連續三次被 logOut 失效 - AWOOOI API 程式碼呼叫 logOut 導致災難 - 已停用 AWOOOI API Telegram,OpenClaw 為唯一 Gateway Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
17 KiB
17 KiB
ADR-012: 危險操作治理架構
狀態: 提案 日期: 2026-03-23 決策者: 統帥 觸發事件: Telegram Token 連續三次被 logOut 災難
背景
事故摘要
2026-03-23,AWOOOI API 程式碼中的 logOut API 呼叫導致 Telegram Bot Token 連續三次被永久失效:
時間線:
11:09 - 第一個 Token 被 logOut (舊版 AWOOOI API)
19:31 - 第二個 Token 被 logOut (舊 Pod 未完全終止)
19:39 - 第三個 Token 成功 (AWOOOI API 已停用 Telegram)
問題根因
| 問題 | 說明 |
|---|---|
| 危險程式碼進入生產 | logOut 永久失效 Token,無人攔截 |
| 無 Code Review | 涉及外部服務的危險操作未被審查 |
| 無執行時告警 | 危險操作執行時沒有任何通知 |
| 無審計日誌 | 無法追溯誰/何時執行了什麼 |
| 變更順序錯誤 | 給新 Token 時舊服務還在運行 |
決策
建立三層危險操作治理架構:
- 預防層 - CI/CD 攔截危險模式
- 執行層 - 危險操作需人工確認
- 審計層 - 所有操作記錄可追溯
架構設計
┌─────────────────────────────────────────────────────────────────────┐
│ 危險操作治理架構 (Dangerous Operations Governance) │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Layer 1: 預防層 (Prevention) │ │
│ │ ════════════════════════════ │ │
│ │ │ │
│ │ CI/CD Pipeline │ │
│ │ ├── 危險模式掃描 (grep logOut, deleteWebhook, etc.) │ │
│ │ ├── 外部服務 API 白名單檢查 │ │
│ │ └── PR 標記 (涉及危險操作需額外審核) │ │
│ │ │ │
│ │ Code Review │ │
│ │ ├── CODEOWNERS: 危險檔案需 CTO/CISO 審核 │ │
│ │ └── PR Template: 勾選「是否涉及外部服務」 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Layer 2: 執行層 (Execution Control) │ │
│ │ ═══════════════════════════════════ │ │
│ │ │ │
│ │ 危險操作分級 │ │
│ │ ├── Tier 0 (禁止): logOut, revokeToken │ │
│ │ ├── Tier 1 (需審批): deleteWebhook, setWebhook │ │
│ │ ├── Tier 2 (需告警): sendMessage (批量) │ │
│ │ └── Tier 3 (自動): getMe, getUpdates │ │
│ │ │ │
│ │ 執行前檢查 │ │
│ │ ├── 產生簽核卡片 (Tier 1 操作) │ │
│ │ ├── 發送 Telegram 告警 (Tier 1-2 操作) │ │
│ │ └── 記錄到活躍事件 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Layer 3: 審計層 (Audit Trail) │ │
│ │ ═════════════════════════════ │ │
│ │ │ │
│ │ 記錄內容 │ │
│ │ ├── 操作類型、時間、執行者 │ │
│ │ ├── 影響範圍 (哪些服務/Token) │ │
│ │ ├── 執行結果 (成功/失敗) │ │
│ │ └── 關聯的 Incident ID │ │
│ │ │ │
│ │ 儲存位置 │ │
│ │ ├── PostgreSQL: audit_logs 表 │ │
│ │ ├── SignOz: Trace 追蹤 │ │
│ │ └── 活躍事件: 顯示在 Dashboard │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
危險 API 清單
Telegram API
| API | 分級 | 說明 | 處理方式 |
|---|---|---|---|
logOut |
Tier 0 (禁止) | 永久失效 Token | CI/CD 攔截,禁止進入生產 |
close |
Tier 0 (禁止) | 關閉所有 session | CI/CD 攔截 |
deleteWebhook |
Tier 1 (審批) | 可能影響其他服務 | 需簽核卡片 |
setWebhook |
Tier 1 (審批) | 可能覆蓋其他設定 | 需簽核卡片 |
sendMessage (批量) |
Tier 2 (告警) | 可能 spam 用戶 | 發送告警 |
getUpdates |
Tier 3 (自動) | 只能單一實例 | 限定 OpenClaw |
getMe |
Tier 3 (自動) | 無副作用 | 允許 |
K8s API
| 操作 | 分級 | 說明 | 處理方式 |
|---|---|---|---|
kubectl delete namespace |
Tier 0 (禁止) | 刪除整個 namespace | 絕對禁止 |
kubectl delete pvc |
Tier 1 (審批) | 刪除持久化資料 | 需簽核 |
kubectl rollout restart |
Tier 2 (告警) | 重啟服務 | 發送告警 |
kubectl scale |
Tier 2 (告警) | 調整副本數 | 發送告警 |
kubectl get |
Tier 3 (自動) | 只讀操作 | 允許 |
Database
| 操作 | 分級 | 說明 | 處理方式 |
|---|---|---|---|
DROP DATABASE |
Tier 0 (禁止) | 刪除資料庫 | 絕對禁止 |
TRUNCATE TABLE |
Tier 1 (審批) | 清空表格 | 需簽核 |
DELETE FROM (無 WHERE) |
Tier 1 (審批) | 刪除所有資料 | 需簽核 |
ALTER TYPE |
Tier 2 (告警) | 修改 schema | 發送告警 |
實施計畫
Phase 1: CI/CD 攔截 (Week 1)
# .github/workflows/ci.yml
- name: Scan for Dangerous Operations
run: |
echo "🔍 掃描危險操作..."
# Tier 0: 禁止的操作
BANNED_PATTERNS="logOut|\.close\(\)|revokeToken|DROP DATABASE|TRUNCATE TABLE"
if grep -rn --include="*.py" -E "$BANNED_PATTERNS" apps/; then
echo "❌ 發現 Tier 0 禁止操作!"
echo "請移除以下危險程式碼後重新提交"
exit 1
fi
# Tier 1: 需要審批的操作
REVIEW_PATTERNS="deleteWebhook|setWebhook|DELETE FROM"
if grep -rn --include="*.py" -E "$REVIEW_PATTERNS" apps/; then
echo "⚠️ 發現 Tier 1 操作,需要 CTO/CISO 審核"
echo "::warning::此 PR 包含危險操作,請確認已通過審核"
fi
echo "✅ 危險操作掃描完成"
Phase 2: CODEOWNERS (Week 1)
# .github/CODEOWNERS
# 涉及外部服務的檔案需要額外審核
apps/api/src/services/telegram_*.py @awoooi/cto @awoooi/ciso
apps/api/src/services/executor.py @awoooi/cto @awoooi/ciso
k8s/**/02-network-policy.yaml @awoooi/cio @awoooi/sre
# 危險操作相關
apps/api/src/services/*gateway*.py @awoooi/cto
Phase 3: 執行時控制 (Week 2)
# apps/api/src/core/dangerous_ops.py
from enum import Enum
from typing import Callable, Any
import structlog
logger = structlog.get_logger()
class OperationTier(Enum):
FORBIDDEN = 0 # 絕對禁止
REQUIRES_APPROVAL = 1 # 需要簽核
REQUIRES_ALERT = 2 # 需要告警
AUTOMATIC = 3 # 自動允許
DANGEROUS_OPERATIONS = {
"telegram.logOut": OperationTier.FORBIDDEN,
"telegram.close": OperationTier.FORBIDDEN,
"telegram.deleteWebhook": OperationTier.REQUIRES_APPROVAL,
"telegram.setWebhook": OperationTier.REQUIRES_APPROVAL,
"k8s.deleteNamespace": OperationTier.FORBIDDEN,
"k8s.deletePVC": OperationTier.REQUIRES_APPROVAL,
"k8s.rolloutRestart": OperationTier.REQUIRES_ALERT,
}
async def execute_dangerous_operation(
operation_name: str,
operation_fn: Callable,
*args,
**kwargs
) -> Any:
"""執行危險操作的統一入口"""
tier = DANGEROUS_OPERATIONS.get(operation_name, OperationTier.AUTOMATIC)
if tier == OperationTier.FORBIDDEN:
logger.error(
"forbidden_operation_blocked",
operation=operation_name,
)
raise PermissionError(f"操作 {operation_name} 被禁止執行")
if tier == OperationTier.REQUIRES_APPROVAL:
# 產生簽核卡片,等待 Y/n
approval = await create_approval_request(
title=f"危險操作: {operation_name}",
description=f"即將執行 {operation_name},需要人工確認",
tier="Tier 1",
)
if not approval.approved:
raise PermissionError("操作被拒絕")
if tier in [OperationTier.REQUIRES_APPROVAL, OperationTier.REQUIRES_ALERT]:
# 發送告警
await send_telegram_alert(
f"⚠️ 危險操作執行中: {operation_name}"
)
# 記錄審計日誌
await log_audit_trail(
operation=operation_name,
tier=tier.name,
args=str(args),
kwargs=str(kwargs),
)
# 執行操作
result = await operation_fn(*args, **kwargs)
# 記錄結果
await log_audit_trail(
operation=operation_name,
result="success",
)
return result
Phase 4: 審計日誌 (Week 2)
-- 新增審計日誌表
CREATE TABLE IF NOT EXISTS audit_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
operation VARCHAR(255) NOT NULL,
tier VARCHAR(50) NOT NULL,
actor VARCHAR(255), -- 執行者 (系統/用戶)
target VARCHAR(255), -- 影響目標
args JSONB,
result VARCHAR(50), -- success/failure/blocked
error_message TEXT,
incident_id VARCHAR(50), -- 關聯的 Incident
trace_id VARCHAR(50), -- SignOz Trace ID
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_audit_logs_operation ON audit_logs(operation);
CREATE INDEX idx_audit_logs_timestamp ON audit_logs(timestamp);
CREATE INDEX idx_audit_logs_tier ON audit_logs(tier);
變更管理流程
Token/Secret 更新 SOP
┌─────────────────────────────────────────────────────────────────┐
│ Token/Secret 更新 SOP │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Step 1: 停止所有使用該 Token 的服務 │
│ ──────────────────────────────────── │
│ - kubectl scale deployment --replicas=0 │
│ - docker stop <container> │
│ - 等待所有實例完全終止 │
│ │
│ Step 2: 驗證沒有殘留實例 │
│ ──────────────────────────── │
│ - kubectl get pods (確認沒有 Running/Terminating) │
│ - docker ps (確認沒有相關容器) │
│ │
│ Step 3: 取得新 Token │
│ ──────────────────────── │
│ - @BotFather → Revoke current token │
│ - 複製新 Token │
│ │
│ Step 4: 更新到唯一的服務 │
│ ──────────────────────── │
│ - 更新 .env 或 K8s Secret │
│ - 記錄到 memory/reference_telegram_token.md │
│ │
│ Step 5: 啟動服務並驗證 │
│ ──────────────────────── │
│ - 啟動服務 │
│ - 檢查日誌確認無錯誤 │
│ - 測試 Telegram 連線 │
│ │
│ Step 6: 更新文檔 │
│ ──────────────── │
│ - 更新 memory 相關 MD │
│ - 發送 Telegram 確認訊息 │
│ │
└─────────────────────────────────────────────────────────────────┘
驗收標準
| 項目 | 狀態 |
|---|---|
| CI/CD 危險模式掃描 | ⬜ |
| CODEOWNERS 危險檔案審核 | ⬜ |
| Tier 0 操作絕對禁止 | ⬜ |
| Tier 1 操作需簽核卡片 | ⬜ |
| Tier 2 操作發送告警 | ⬜ |
| 審計日誌表建立 | ⬜ |
| Token 更新 SOP 文檔 | ✅ |
| Memory 文檔更新 | ✅ |
附錄: 2026-03-23 事故完整時間線
11:09:47 - AWOOOI API 啟動,呼叫 logOut (第一個 Token 失效)
11:09:52 - OpenClaw 嘗試啟動失敗 "Logged out"
...
19:31:16 - 給第二個 Token
19:31:27 - 舊 AWOOOI Pod 呼叫 logOut (第二個 Token 失效)
19:31:31 - OpenClaw 啟動失敗 "Logged out"
19:33:00 - 新 AWOOOI Pod 部署完成 (但 Token 已死)
...
19:35:00 - 清空 AWOOOI API Telegram Token
19:35:30 - 重啟 AWOOOI API (不再碰 Telegram)
19:39:01 - 給第三個 Token
19:39:17 - OpenClaw 啟動成功
19:39:36 - 所有系統健康