Files
awoooi/apps/api/src/hermes/safety_hooks.py
Your Name 2572ec46d2 feat(ws4): Hermes NL 自然語言介面 — 12-Agent Claude SDK 接入(ADR-094/095)
## 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>
2026-04-25 02:10:06 +08:00

64 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Hermes NL 輸入安全守門 — Python 重做 awoooi-guard.jsADR-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)