ForcePilot/backend/package/yuxi/channels/adapters/feishu/security_audit.py
Kris a6fa7245e5 feat(feishu): 完整实现飞书适配器核心模块
新增飞书机器人适配器全套功能,包括:
- 基础适配器入口与工具导出
- 消息格式化、卡片渲染、回复调度逻辑
- 会话ID生成、模型覆盖策略
- 消息发送缓存、顺序队列管理
- 飞书签名验证、加解密webhook请求
- 审批权限校验、机器人菜单事件处理
- 文档评论、钉消息、语音转码处理
- 静态/动态目录管理、子代理生命周期管理
- 各类工具集:聊天、云盘、文档、知识库API封装
2026-05-12 00:43:59 +08:00

200 lines
7.2 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.

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