ForcePilot/backend/package/yuxi/channels/adapters/feishu/security_audit.py
Kris 1f78c44b03 refactor: 整理并清理项目中的冗余代码与格式问题
这是一个批量整理提交,包含以下主要改动:
1.  删除多处冗余的空行和未使用的导入
2.  修复文件末尾缺少换行符的问题
3.  调整部分模块的导入顺序与代码排版
4.  修复部分配置默认值与策略逻辑
5.  新增多个功能模块与辅助工具
6.  完善异常处理与日志记录
7.  修复速率限制、消息缓存、权限校验等逻辑bug
8.  废弃部分旧有API与配置项并添加警告提示
2026-05-12 14:51:53 +08:00

231 lines
8.4 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="warning",
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="critical",
code="FEISHU_ALLOWLIST_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 not verify_token and not encrypt_key:
warnings.append(
SecurityWarning(
level="warning",
code="FEISHU_NO_VERIFICATION_CONFIG",
message="未配置 verifyToken 和 encryptKey事件回调请求将无法验证来源",
suggestion="在飞书开放平台获取并配置 verifyToken 或 encryptKey",
)
)
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="warning",
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="warning",
code="FEISHU_ALL_TOOLS_ENABLED",
message=f"已启用 {tools_enabled_count} 个工具集成,请确保权限控制得当",
suggestion="建议检查工具集成权限映射,避免给用户过高的文档/通讯录操作权限",
)
)
encrypt_not_both = (verify_token and not encrypt_key) or (encrypt_key and not verify_token)
if encrypt_not_both:
warnings.append(
SecurityWarning(
level="info",
code="FEISHU_VERIFICATION_PARTIAL",
message="verifyToken 和 encryptKey 仅配置了其中一项",
suggestion="建议同时配置两项以获得完整的安全验证",
)
)
http_proxy = config.get("httpProxy", "")
if http_proxy:
warnings.append(
SecurityWarning(
level="info",
code="FEISHU_HTTP_PROXY_CONFIGURED",
message=f"已配置 HTTP 代理: {http_proxy[:50]}...",
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