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