新增飞书机器人适配器全套功能,包括: - 基础适配器入口与工具导出 - 消息格式化、卡片渲染、回复调度逻辑 - 会话ID生成、模型覆盖策略 - 消息发送缓存、顺序队列管理 - 飞书签名验证、加解密webhook请求 - 审批权限校验、机器人菜单事件处理 - 文档评论、钉消息、语音转码处理 - 静态/动态目录管理、子代理生命周期管理 - 各类工具集:聊天、云盘、文档、知识库API封装
200 lines
7.2 KiB
Python
200 lines
7.2 KiB
Python
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
|