from __future__ import annotations from dataclasses import dataclass, field from typing import Any from yuxi.channels.adapters.slack.security import ( DmPolicy, GroupPolicy, SecurityConfig, ) @dataclass class AuditFinding: severity: str category: str message: str recommendation: str = "" fixable: bool = False @dataclass class AuditResult: findings: list[AuditFinding] = field(default_factory=list) overall: str = "pass" @property def has_critical(self) -> bool: return any(f.severity == "critical" for f in self.findings) @property def has_warnings(self) -> bool: return any(f.severity in ("warning", "critical") for f in self.findings) def to_dict(self) -> dict[str, Any]: return { "overall": self.overall, "findings": [ { "severity": f.severity, "category": f.category, "message": f.message, "recommendation": f.recommendation, "fixable": f.fixable, } for f in self.findings ], } def audit_security_config(security: SecurityConfig) -> AuditResult: findings: list[AuditFinding] = [] if security.dm_policy == DmPolicy.OPEN: findings.append( AuditFinding( severity="warning", category="dm_policy", message="DM policy is set to 'open' — anyone can DM the bot", recommendation="Consider changing to 'allowlist' or 'pairing' for production", fixable=True, ) ) if security.dm_policy == DmPolicy.DISABLED: findings.append( AuditFinding( severity="warning", category="dm_policy", message="DM policy is set to 'disabled' — bot ignores all direct messages", recommendation="Set to 'allowlist' or 'open' to allow DM access", fixable=True, ) ) if security.group_policy == GroupPolicy.OPEN: findings.append( AuditFinding( severity="info", category="group_policy", message="Group policy is 'open' — bot responds in all channels", recommendation="Consider 'allowlist' for production environments", fixable=True, ) ) if security.group_policy == GroupPolicy.DISABLED: findings.append( AuditFinding( severity="warning", category="group_policy", message="Group policy is 'disabled' — bot ignores all channel messages", recommendation="Set to 'allowlist' or 'open' to allow channel access", fixable=True, ) ) if not security.allow_from and not security.allow_from_wildcard: if security.dm_policy == DmPolicy.ALLOWLIST or security.group_policy == GroupPolicy.ALLOWLIST: findings.append( AuditFinding( severity="critical", category="allowlist", message="Allowlist is empty but DM/Group policy requires allowlist", recommendation="Add user/channel IDs to allow_from or switch policy to 'open'", fixable=True, ) ) overall = "pass" if findings: severities = {f.severity for f in findings} if "critical" in severities: overall = "fail" elif "warning" in severities: overall = "warn" return AuditResult(findings=findings, overall=overall) def audit_connection_status( connected: bool, bot_user_id: str, team: str, ) -> AuditResult: findings: list[AuditFinding] = [] if not connected: findings.append( AuditFinding( severity="critical", category="connection", message="Slack adapter is not connected", recommendation="Check bot_token and app_token configuration", fixable=True, ) ) if not bot_user_id: findings.append( AuditFinding( severity="warning", category="connection", message="Bot user ID not resolved — bot identity unknown", recommendation="Verify that auth.test succeeds during connection", fixable=True, ) ) overall = "fail" if findings else "pass" return AuditResult(findings=findings, overall=overall) def auto_fix_security(security: SecurityConfig) -> SecurityConfig: import copy fixed = copy.deepcopy(security) if not fixed.allow_from and not fixed.allow_from_wildcard: if fixed.dm_policy == DmPolicy.ALLOWLIST: fixed.dm_policy = DmPolicy.PAIRING if not fixed.allow_from and not fixed.allow_from_wildcard: if fixed.group_policy == GroupPolicy.ALLOWLIST: fixed.group_policy = GroupPolicy.OPEN return fixed