## hermes/ 套件(5 個新模組) ### display_names.py - 12 agent 視覺識別表(emoji + hashtag + handle + short_name) - format_response_header() 產生 Telegram 前綴 ### agent_loader.py - 解析 .claude/agents/*.md frontmatter → system prompt - lru_cache 避免重複讀檔 ### safety_hooks.py - 移植 awoooi-guard.js 20 條 HARD BLOCK 規則(DENY_PATTERNS) - 5 條 MUTATE_PATTERNS → 須走審批流 ### nl_gateway.py - Layer 1: 關鍵字正則路由(12 條規則,<10ms) - Layer 3: DEFAULT_AGENT = "debugger" - Claude Agent SDK query() 非同步串流,取 ResultMessage.result - 安全降級:SDK error → 友好錯誤訊息 ### telegram_webhook.py - WS4 Hermes NL 接入(@tsenyangbot mention 或私訊觸發) - HERMES_NL_ENABLED=False(feature flag 保護,預設關閉) ## telegram_gateway.py - send_hermes_reply(text, chat_id, reply_to_message_id) 無 500 字截斷,支援 Agent 長回覆 ## config.py - HERMES_NL_ENABLED: bool = False - TELEGRAM_BOT_USERNAME: str = "tsenyangbot" Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
64 lines
3.6 KiB
Python
64 lines
3.6 KiB
Python
"""Hermes NL 輸入安全守門 — Python 重做 awoooi-guard.js(ADR-094)
|
||
|
||
從 .claude/hooks/awoooi-guard.js HARD BLOCK 規則移植,
|
||
涵蓋 K8s 生產刪除、docker volume 摧毀、force push、DROP TABLE 等。
|
||
|
||
2026-04-24 Claude Sonnet 4.6 (WS4 Hermes NL)
|
||
"""
|
||
from __future__ import annotations
|
||
import re
|
||
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
# DENY 正則:完整移植自 awoooi-guard.js HARD BLOCK 段落
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
_DENY_PATTERNS: list[re.Pattern] = [
|
||
# 基本破壞性指令
|
||
re.compile(r"rm\s+-rf", re.IGNORECASE),
|
||
re.compile(r"git\s+push\s+.*--force", re.IGNORECASE),
|
||
re.compile(r"DROP\s+TABLE", re.IGNORECASE),
|
||
re.compile(r"truncate\s+table", re.IGNORECASE),
|
||
re.compile(r"kubectl\s+delete\s+namespace", re.IGNORECASE),
|
||
re.compile(r"systemctl\s+(stop|disable)\s+", re.IGNORECASE),
|
||
re.compile(r"chmod\s+777", re.IGNORECASE),
|
||
re.compile(r"curl.*(sh|bash)\s*\|", re.IGNORECASE),
|
||
re.compile(r">\s*/etc/", re.IGNORECASE),
|
||
# K8s 生產命名空間(移植自 awoooi-guard.js HARD BLOCK)
|
||
re.compile(r"kubectl.*delete.*namespace.*awoooi-prod", re.IGNORECASE),
|
||
# K8s 生產 PVC/Secret 強制刪除
|
||
re.compile(r"kubectl.*delete.*(pvc|secret).*-n.*awoooi-prod", re.IGNORECASE),
|
||
re.compile(r"kubectl.*-n.*awoooi-prod.*delete.*(pvc|secret)", re.IGNORECASE),
|
||
# docker compose down -v(摧毀 volume)
|
||
re.compile(r"docker[\s-]?compose.*down.*(-v\b|--volumes)", re.IGNORECASE),
|
||
# docker system prune -f
|
||
re.compile(r"docker\s+system\s+prune.*(-f|--force)", re.IGNORECASE),
|
||
# Telegram logOut / deleteWebhook(先停後換原則)
|
||
re.compile(r"api\.telegram\.org/bot[^/]+/(logOut|deleteWebhook)", re.IGNORECASE),
|
||
# psql DROP TABLE/DATABASE(非 test/dev 環境)
|
||
re.compile(r"psql.*-c.*DROP\s+(TABLE|DATABASE|SCHEMA)", re.IGNORECASE),
|
||
# force push 到 gitea main
|
||
re.compile(r"git push.*(--force|-f).*gitea.*main", re.IGNORECASE),
|
||
re.compile(r"git push.*gitea.*main.*(--force|-f)", re.IGNORECASE),
|
||
# DROP DATABASE 直接語句
|
||
re.compile(r"DROP\s+DATABASE", re.IGNORECASE),
|
||
# 清除 /dev/null 覆蓋系統檔案
|
||
re.compile(r">\s*/dev/(mem|kmem|port)", re.IGNORECASE),
|
||
]
|
||
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
# MUTATE 正則:變更意圖偵測(需走審批流)
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
# 只允許 query/describe/summarize 類動作(mutate/deploy/approve 走審批流)
|
||
_MUTATE_PATTERNS: list[re.Pattern] = [
|
||
re.compile(r"\b(deploy|apply|create|delete|rollout|approve|execute)\b", re.IGNORECASE),
|
||
]
|
||
|
||
|
||
def is_dangerous_input(text: str) -> bool:
|
||
"""True → 拒絕,回 ⛔"""
|
||
return any(p.search(text) for p in _DENY_PATTERNS)
|
||
|
||
|
||
def is_mutate_intent(text: str) -> bool:
|
||
"""True → 需要走 ApprovalRecord 二次確認"""
|
||
return any(p.search(text) for p in _MUTATE_PATTERNS)
|