"""Security audit module for Synology Chat channel. Performs security configuration checks and identifies potential risks in the Synology Chat integration. """ from __future__ import annotations from typing import Any def audit_config(config: dict[str, Any]) -> list[dict[str, Any]]: findings: list[dict[str, Any]] = [] if not config.get("verify_ssl", True): findings.append( { "id": "ssl_disabled", "severity": "high", "message": "SSL verification is disabled, connections may be vulnerable to MITM attacks", "remediation": "Set verify_ssl to true or ensure the NAS certificate is trusted", } ) if not config.get("password") and not config.get("password_file"): findings.append( { "id": "no_credential", "severity": "critical", "message": "No DSM password or password_file configured", "remediation": "Set password or password_file in config or DSM_PASSWORD env var", } ) sec = config.get("security", {}) if not isinstance(sec, dict): sec = {} dm_policy = sec.get("dm_policy", "open") if dm_policy == "open": findings.append( { "id": "open_dm_policy", "severity": "medium", "message": "DM policy is 'open', any user can interact with the bot", "remediation": "Set dm_policy to 'allowlist' or 'pairing' and configure allow_from", } ) if dm_policy == "pairing" and not sec.get("allow_from"): findings.append( { "id": "pairing_no_initial_allowlist", "severity": "low", "message": "Pairing mode enabled but initial allow_from is empty", "remediation": "Add at least one trusted user ID to allow_from for operational access", } ) rate_limit = config.get("rate_limit", {}).get("max_per_minute", 30) if isinstance(rate_limit, (int, float)) and rate_limit > 60: findings.append( { "id": "high_rate_limit", "severity": "low", "message": f"Rate limit is set to {rate_limit}/min, which may be high for a NAS device", "remediation": "Consider lowering to 30-60 requests per minute", } ) return findings def audit_connection(dsm_url: str, verify_ssl: bool) -> list[dict[str, Any]]: findings: list[dict[str, Any]] = [] if dsm_url.startswith("http://"): findings.append( { "id": "cleartext_dsm_url", "severity": "high", "message": "DSM URL uses HTTP (cleartext), credentials may be transmitted in plaintext", "remediation": "Use HTTPS for the DSM URL", } ) if not dsm_url: findings.append( { "id": "empty_dsm_url", "severity": "critical", "message": "DSM URL is empty", "remediation": "Configure a valid DSM URL", } ) return findings