diff --git a/contributors/emails/fangliquan@qq.com b/contributors/emails/fangliquan@qq.com index 58b0678f6bd7a..8909594dec660 100644 --- a/contributors/emails/fangliquan@qq.com +++ b/contributors/emails/fangliquan@qq.com @@ -1 +1 @@ -fangliquanflq +fangliquanflq diff --git a/gateway/platforms/whatsapp_cloud.py b/gateway/platforms/whatsapp_cloud.py index 41b815088dabe..729ea12fdf1ef 100644 --- a/gateway/platforms/whatsapp_cloud.py +++ b/gateway/platforms/whatsapp_cloud.py @@ -255,13 +255,16 @@ class WhatsAppCloudAdapter(WhatsAppBehaviorMixin, BasePlatformAdapter): self._reply_prefix: Optional[str] = extra.get("reply_prefix") # Allowlist: honor the *documented* WHATSAPP_CLOUD_ALLOWED_USERS (the # var the setup wizard writes) in addition to WHATSAPP_CLOUD_ALLOW_FROM. - # Precedence matches construction forever: explicit config, then legacy - # ALLOW_FROM, then ALLOWED_USERS. Track the winning source so live DM - # checks do not let a lower-precedence env broaden access. - explicit_allow = extra.get("allow_from") or extra.get("allowFrom") - if explicit_allow: + # Precedence matches construction forever: explicit config (by key + # presence, including empty []), then legacy ALLOW_FROM, then + # ALLOWED_USERS. Track the winning source so live DM checks do not let + # a lower-precedence env broaden access. + if "allow_from" in extra: self._dm_allowlist_source = "config" - allow_raw = explicit_allow + allow_raw = extra.get("allow_from") + elif "allowFrom" in extra: + self._dm_allowlist_source = "config" + allow_raw = extra.get("allowFrom") elif os.getenv("WHATSAPP_CLOUD_ALLOW_FROM"): self._dm_allowlist_source = "WHATSAPP_CLOUD_ALLOW_FROM" allow_raw = os.getenv("WHATSAPP_CLOUD_ALLOW_FROM") diff --git a/plugins/platforms/whatsapp/adapter.py b/plugins/platforms/whatsapp/adapter.py index 0254d5f4d073c..ca92e5f69fb2d 100644 --- a/plugins/platforms/whatsapp/adapter.py +++ b/plugins/platforms/whatsapp/adapter.py @@ -409,12 +409,16 @@ class WhatsAppAdapter(WhatsAppBehaviorMixin, BasePlatformAdapter): self._reply_prefix: Optional[str] = config.extra.get("reply_prefix") self._dm_policy = str(config.extra.get("dm_policy") or os.getenv("WHATSAPP_DM_POLICY", "pairing")).strip().lower() # Prefer config.extra, then the documented WHATSAPP_ALLOWED_USERS env - # (setup wizard / pairing mirror). Track which source won so live DM - # checks preserve that precedence (env must not override explicit config). - explicit_allow = config.extra.get("allow_from") or config.extra.get("allowFrom") - if explicit_allow: + # (setup wizard / pairing mirror). Select by key *presence* so an + # explicit empty allow_from: [] stays authoritative and does not fall + # through to a lower-precedence env grant. Track which source won so + # live DM checks preserve that precedence. + if "allow_from" in config.extra: self._dm_allowlist_source = "config" - allow_raw = explicit_allow + allow_raw = config.extra.get("allow_from") + elif "allowFrom" in config.extra: + self._dm_allowlist_source = "config" + allow_raw = config.extra.get("allowFrom") elif os.getenv("WHATSAPP_ALLOWED_USERS"): self._dm_allowlist_source = "WHATSAPP_ALLOWED_USERS" allow_raw = os.getenv("WHATSAPP_ALLOWED_USERS") diff --git a/tests/gateway/test_pairing_allowlist_bypass.py b/tests/gateway/test_pairing_allowlist_bypass.py index 4f49f20be26f7..e0ac09257d5e3 100644 --- a/tests/gateway/test_pairing_allowlist_bypass.py +++ b/tests/gateway/test_pairing_allowlist_bypass.py @@ -337,6 +337,28 @@ def test_whatsapp_live_allowlist_keeps_explicit_config_over_env(monkeypatch): assert adapter._is_dm_intake_allowed("15550000003") is False +def test_whatsapp_explicit_empty_allow_from_blocks_env_grant(monkeypatch): + """allow_from: [] is present config — must not fall through to env grants.""" + from gateway.config import PlatformConfig + from plugins.platforms.whatsapp.adapter import WhatsAppAdapter + + monkeypatch.setenv("WHATSAPP_ALLOWED_USERS", "15550000002") + adapter = WhatsAppAdapter( + PlatformConfig( + enabled=True, + extra={ + "dm_policy": "allowlist", + "allow_from": [], + }, + ) + ) + + assert adapter._dm_allowlist_source == "config" + assert adapter._allow_from == set() + assert adapter._is_dm_intake_allowed("15550000002") is False + assert adapter._is_dm_allowed("15550000002") is False + + def test_whatsapp_live_allowlist_rereads_env_when_env_seeded(monkeypatch): """Env-seeded adapters still pick up pairing allowlist mutations live.""" from gateway.config import PlatformConfig diff --git a/tests/gateway/test_whatsapp_cloud_allowed_users.py b/tests/gateway/test_whatsapp_cloud_allowed_users.py index 3b2b52437b2ee..955e67cd4a809 100644 --- a/tests/gateway/test_whatsapp_cloud_allowed_users.py +++ b/tests/gateway/test_whatsapp_cloud_allowed_users.py @@ -103,6 +103,25 @@ def test_explicit_config_beats_cloud_env_on_live_checks(monkeypatch): assert adapter._is_dm_allowed("15550000004") is False +def test_explicit_empty_allow_from_blocks_cloud_env_grants(monkeypatch): + """allow_from: [] is present config — conflicting cloud env must not authorize.""" + adapter = _build_adapter( + monkeypatch, + { + "WHATSAPP_CLOUD_ALLOW_FROM": "15550000002", + "WHATSAPP_CLOUD_ALLOWED_USERS": "15550000003", + }, + extra={"dm_policy": "allowlist", "allow_from": []}, + ) + + assert adapter._dm_allowlist_source == "config" + assert adapter._allow_from == set() + assert adapter._is_dm_allowed("15550000002") is False + assert adapter._is_dm_allowed("15550000003") is False + assert adapter._is_dm_intake_allowed("15550000002") is False + assert adapter._is_dm_intake_allowed("15550000003") is False + + def test_cloud_allowed_users_live_reread_when_env_seeded(monkeypatch): adapter = _build_adapter( monkeypatch,