ForcePilot/backend/package/yuxi/channels/adapters/feishu/security_audit.py

200 lines
7.2 KiB
Python
Raw Normal View History

from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any
@dataclass
class SecurityWarning:
level: str
code: str
message: str
suggestion: str = ""
@dataclass
class SecurityAuditResult:
warnings: list[SecurityWarning] = field(default_factory=list)
@property
def has_critical(self) -> bool:
return any(w.level == "critical" for w in self.warnings)
@property
def has_warnings(self) -> bool:
return len(self.warnings) > 0
def audit_feishu_security(config: dict[str, Any]) -> SecurityAuditResult:
warnings: list[SecurityWarning] = []
group_policy = config.get("group_policy", config.get("groupPolicy", "allowlist"))
if group_policy == "allowall":
group_policy = "open"
warnings.append(
SecurityWarning(
level="info",
code="FEISHU_GROUP_POLICY_LEGACY_ALIAS",
message="groupPolicy 使用了已废弃的 'allowall' 值,已自动转换为 'open'",
suggestion="建议将配置从 'allowall' 更新为 'open'",
)
)
dm_policy = config.get("dm_policy", config.get("dmPolicy", "pairing"))
allowlist = config.get("allowlist", config.get("allowFrom", []))
default_account = config.get("defaultAccount", "")
if group_policy == "open":
warnings.append(
SecurityWarning(
level="warning",
code="FEISHU_GROUP_POLICY_OPEN",
message="群聊策略设置为 'open',所有群聊中的用户都可以与机器人交互",
suggestion="建议设置为 'allowlist' 并使用 allowFrom 限制可访问的群聊",
)
)
if group_policy == "disabled":
warnings.append(
SecurityWarning(
level="info",
code="FEISHU_GROUP_POLICY_DISABLED",
message="群聊功能已禁用",
suggestion="如需启用群聊功能,请修改 groupPolicy 为 'open''allowlist'",
)
)
if not allowlist:
if group_policy == "allowlist" or dm_policy == "allowlist":
warnings.append(
SecurityWarning(
level="critical",
code="FEISHU_EMPTY_ALLOWLIST",
message="allowFrom 白名单为空,但策略要求白名单验证",
suggestion="请在 allowFrom 中配置至少一个允许的用户或群组 ID",
)
)
if dm_policy == "open":
warnings.append(
SecurityWarning(
level="info",
code="FEISHU_DM_POLICY_OPEN",
message="私聊策略设置为 'open',所有用户都可以向机器人发送私信",
suggestion="如需限制,可设置为 'pairing''allowlist'",
)
)
accounts = config.get("accounts", {})
if default_account and isinstance(accounts, dict) and default_account not in accounts:
warnings.append(
SecurityWarning(
level="warning",
code="FEISHU_DEFAULT_ACCOUNT_NOT_FOUND",
message=f"defaultAccount '{default_account}' 在 accounts 中未找到",
suggestion="请检查 defaultAccount 配置是否与 accounts 中的账户名称一致",
)
)
doc_owner_open_id = config.get("docOwnerOpenId", "")
if doc_owner_open_id:
warnings.append(
SecurityWarning(
level="info",
code="FEISHU_DOC_OWNER_SET",
message=f"文档所有者 open_id 已配置: {doc_owner_open_id[:8]}...",
suggestion="确认该 open_id 对应的用户拥有文档管理权限",
)
)
if not allowlist and group_policy == "allowlist":
warnings.append(
SecurityWarning(
level="warning",
code="FEISHU_ALLOWLIST_MAY_DEADLOCK",
message="allowlist 为空 + groupPolicy=allowlist所有群聊将被拒绝访问",
suggestion="请添加至少一个群聊到 allowlist 或修改 groupPolicy",
)
)
verify_token = config.get("verify_token", config.get("verifyToken", ""))
encrypt_key = config.get("encrypt_key", config.get("encryptKey", ""))
webhook_path = config.get("webhookPath", "")
if webhook_path and not verify_token and not encrypt_key:
warnings.append(
SecurityWarning(
level="critical",
code="FEISHU_WEBHOOK_NO_VERIFICATION",
message="Webhook 模式已启用但未配置 verifyToken 或 encryptKey存在安全隐患",
suggestion="请配置 verifyToken 或 encryptKey 以验证 Webhook 请求来源",
)
)
if app_secret := (config.get("app_secret") or config.get("appSecret") or ""):
if len(str(app_secret)) < 16:
warnings.append(
SecurityWarning(
level="warning",
code="FEISHU_APP_SECRET_TOO_SHORT",
message="appSecret 长度过短,可能存在安全风险",
suggestion="确认使用的 appSecret 是从飞书开发者后台直接获取的完整密钥",
)
)
if isinstance(accounts, dict) and len(accounts) > 5:
warnings.append(
SecurityWarning(
level="info",
code="FEISHU_MANY_ACCOUNTS",
message=f"配置了 {len(accounts)} 个账户,请确认所有账户都在使用中",
suggestion="建议定期清理未使用的账户配置",
)
)
reactions_enabled = config.get("reactionsEnabled", True)
if not reactions_enabled:
warnings.append(
SecurityWarning(
level="info",
code="FEISHU_REACTIONS_DISABLED",
message="消息反应功能已禁用",
suggestion="启用反应功能可以改善用户体验,如需启用请设置 reactionsEnabled=True",
)
)
tools_enabled_count = sum(
1 for t in ("doc", "chat", "wiki", "drive", "perm") if config.get("tools", {}).get(t, True)
)
if tools_enabled_count >= 5:
warnings.append(
SecurityWarning(
level="info",
code="FEISHU_ALL_TOOLS_ENABLED",
message=f"已启用 {tools_enabled_count} 个工具集成,请确保权限控制得当",
suggestion="建议检查工具集成权限映射,避免给用户过高的文档/通讯录操作权限",
)
)
return SecurityAuditResult(warnings=warnings)
def collect_feishu_security_warnings(config: dict[str, Any]) -> list[SecurityWarning]:
result = audit_feishu_security(config)
return result.warnings
def collect_feishu_security_audit_findings(config: dict[str, Any]) -> list[dict]:
result = audit_feishu_security(config)
findings: list[dict] = []
for w in result.warnings:
findings.append(
{
"level": w.level,
"code": w.code,
"message": w.message,
"suggestion": w.suggestion,
}
)
return findings