feat: support * wildcard in platform allowlists and improve WhatsApp docs
* docs: clarify WhatsApp allowlist behavior and document WHATSAPP_ALLOW_ALL_USERS - Add WHATSAPP_ALLOW_ALL_USERS and WHATSAPP_DEBUG to env vars reference - Warn that * is not a wildcard and silently blocks all messages - Show WHATSAPP_ALLOWED_USERS as optional, not required - Update troubleshooting with the * trap and debug mode tip - Fix Security section to mention the allow-all alternative Prompted by a user report in Discord where WHATSAPP_ALLOWED_USERS=* caused all incoming messages to be silently dropped at the bridge level. * feat: support * wildcard in platform allowlists Follow the precedent set by SIGNAL_GROUP_ALLOWED_USERS which already supports * as an allow-all wildcard. Bridge (allowlist.js): matchesAllowedUser() now checks for * in the allowedUsers set before iterating sender aliases. Gateway (run.py): _is_authorized() checks for * in allowed_ids after parsing the allowlist. This is generic — works for all platforms, not just WhatsApp. Updated docs to document * as a supported value instead of warning against it. Added WHATSAPP_ALLOW_ALL_USERS and WHATSAPP_DEBUG to the env vars reference. Tests: JS allowlist test + 2 Python gateway tests (WhatsApp + Telegram to verify cross-platform behavior).
This commit is contained in:
@@ -1650,6 +1650,11 @@ class GatewayRunner:
|
||||
if global_allowlist:
|
||||
allowed_ids.update(uid.strip() for uid in global_allowlist.split(",") if uid.strip())
|
||||
|
||||
# "*" in any allowlist means allow everyone (consistent with
|
||||
# SIGNAL_GROUP_ALLOWED_USERS precedent)
|
||||
if "*" in allowed_ids:
|
||||
return True
|
||||
|
||||
check_ids = {user_id}
|
||||
if "@" in user_id:
|
||||
check_ids.add(user_id.split("@")[0])
|
||||
|
||||
@@ -68,6 +68,11 @@ export function matchesAllowedUser(senderId, allowedUsers, sessionDir) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// "*" means allow everyone (consistent with SIGNAL_GROUP_ALLOWED_USERS)
|
||||
if (allowedUsers.has('*')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const aliases = expandWhatsAppIdentifiers(senderId, sessionDir);
|
||||
for (const alias of aliases) {
|
||||
if (allowedUsers.has(alias)) {
|
||||
|
||||
@@ -45,3 +45,15 @@ test('matchesAllowedUser accepts mapped lid sender when allowlist only contains
|
||||
rmSync(sessionDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('matchesAllowedUser treats * as allow-all wildcard', () => {
|
||||
const sessionDir = mkdtempSync(path.join(os.tmpdir(), 'hermes-wa-allowlist-'));
|
||||
|
||||
try {
|
||||
const allowedUsers = parseAllowedUsers('*');
|
||||
assert.equal(matchesAllowedUser('19175395595@s.whatsapp.net', allowedUsers, sessionDir), true);
|
||||
assert.equal(matchesAllowedUser('267383306489914@lid', allowedUsers, sessionDir), true);
|
||||
} finally {
|
||||
rmSync(sessionDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -90,6 +90,46 @@ def test_whatsapp_lid_user_matches_phone_allowlist_via_session_mapping(monkeypat
|
||||
assert runner._is_user_authorized(source) is True
|
||||
|
||||
|
||||
def test_star_wildcard_in_allowlist_authorizes_any_user(monkeypatch):
|
||||
"""WHATSAPP_ALLOWED_USERS=* should act as allow-all wildcard."""
|
||||
_clear_auth_env(monkeypatch)
|
||||
monkeypatch.setenv("WHATSAPP_ALLOWED_USERS", "*")
|
||||
|
||||
runner, _adapter = _make_runner(
|
||||
Platform.WHATSAPP,
|
||||
GatewayConfig(platforms={Platform.WHATSAPP: PlatformConfig(enabled=True)}),
|
||||
)
|
||||
|
||||
source = SessionSource(
|
||||
platform=Platform.WHATSAPP,
|
||||
user_id="99998887776@s.whatsapp.net",
|
||||
chat_id="99998887776@s.whatsapp.net",
|
||||
user_name="stranger",
|
||||
chat_type="dm",
|
||||
)
|
||||
assert runner._is_user_authorized(source) is True
|
||||
|
||||
|
||||
def test_star_wildcard_works_for_any_platform(monkeypatch):
|
||||
"""The * wildcard should work generically, not just for WhatsApp."""
|
||||
_clear_auth_env(monkeypatch)
|
||||
monkeypatch.setenv("TELEGRAM_ALLOWED_USERS", "*")
|
||||
|
||||
runner, _adapter = _make_runner(
|
||||
Platform.TELEGRAM,
|
||||
GatewayConfig(platforms={Platform.TELEGRAM: PlatformConfig(enabled=True, token="t")}),
|
||||
)
|
||||
|
||||
source = SessionSource(
|
||||
platform=Platform.TELEGRAM,
|
||||
user_id="123456789",
|
||||
chat_id="123456789",
|
||||
user_name="stranger",
|
||||
chat_type="dm",
|
||||
)
|
||||
assert runner._is_user_authorized(source) is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unauthorized_dm_pairs_by_default(monkeypatch):
|
||||
_clear_auth_env(monkeypatch)
|
||||
|
||||
@@ -170,7 +170,9 @@ For native Anthropic auth, Hermes prefers Claude Code's own credential files whe
|
||||
| `SLACK_HOME_CHANNEL_NAME` | Display name for the Slack home channel |
|
||||
| `WHATSAPP_ENABLED` | Enable the WhatsApp bridge (`true`/`false`) |
|
||||
| `WHATSAPP_MODE` | `bot` (separate number) or `self-chat` (message yourself) |
|
||||
| `WHATSAPP_ALLOWED_USERS` | Comma-separated phone numbers (with country code, no `+`) |
|
||||
| `WHATSAPP_ALLOWED_USERS` | Comma-separated phone numbers (with country code, no `+`), or `*` to allow all senders |
|
||||
| `WHATSAPP_ALLOW_ALL_USERS` | Allow all WhatsApp senders without an allowlist (`true`/`false`) |
|
||||
| `WHATSAPP_DEBUG` | Log raw message events in the bridge for troubleshooting (`true`/`false`) |
|
||||
| `SIGNAL_HTTP_URL` | signal-cli daemon HTTP endpoint (for example `http://127.0.0.1:8080`) |
|
||||
| `SIGNAL_ACCOUNT` | Bot phone number in E.164 format |
|
||||
| `SIGNAL_ALLOWED_USERS` | Comma-separated E.164 phone numbers or UUIDs |
|
||||
|
||||
@@ -94,9 +94,20 @@ Add the following to your `~/.hermes/.env` file:
|
||||
# Required
|
||||
WHATSAPP_ENABLED=true
|
||||
WHATSAPP_MODE=bot # "bot" or "self-chat"
|
||||
|
||||
# Access control — pick ONE of these options:
|
||||
WHATSAPP_ALLOWED_USERS=15551234567 # Comma-separated phone numbers (with country code, no +)
|
||||
# WHATSAPP_ALLOWED_USERS=* # OR use * to allow everyone
|
||||
# WHATSAPP_ALLOW_ALL_USERS=true # OR set this flag instead (same effect as *)
|
||||
```
|
||||
|
||||
:::tip Allow-all shorthand
|
||||
Setting `WHATSAPP_ALLOWED_USERS=*` allows **all** senders (equivalent to `WHATSAPP_ALLOW_ALL_USERS=true`).
|
||||
This is consistent with [Signal group allowlists](/docs/reference/environment-variables).
|
||||
To use the pairing flow instead, remove both variables and rely on the
|
||||
[DM pairing system](/docs/user-guide/security#dm-pairing-system).
|
||||
:::
|
||||
|
||||
Optional behavior settings in `~/.hermes/config.yaml`:
|
||||
|
||||
```yaml
|
||||
@@ -174,7 +185,7 @@ whatsapp:
|
||||
| **Bridge crashes or reconnect loops** | Restart the gateway, update Hermes, and re-pair if the session was invalidated by a WhatsApp protocol change. |
|
||||
| **Bot stops working after WhatsApp update** | Update Hermes to get the latest bridge version, then re-pair. |
|
||||
| **macOS: "Node.js not installed" but node works in terminal** | launchd services don't inherit your shell PATH. Run `hermes gateway install` to re-snapshot your current PATH into the plist, then `hermes gateway start`. See the [Gateway Service docs](./index.md#macos-launchd) for details. |
|
||||
| **Messages not being received** | Verify `WHATSAPP_ALLOWED_USERS` includes the sender's number (with country code, no `+` or spaces). |
|
||||
| **Messages not being received** | Verify `WHATSAPP_ALLOWED_USERS` includes the sender's number (with country code, no `+` or spaces), or set it to `*` to allow everyone. Set `WHATSAPP_DEBUG=true` in `.env` and restart the gateway to see raw message events in `bridge.log`. |
|
||||
| **Bot replies to strangers with a pairing code** | Set `whatsapp.unauthorized_dm_behavior: ignore` in `~/.hermes/config.yaml` if you want unauthorized DMs to be silently ignored instead. |
|
||||
|
||||
---
|
||||
@@ -182,9 +193,10 @@ whatsapp:
|
||||
## Security
|
||||
|
||||
:::warning
|
||||
**Always set `WHATSAPP_ALLOWED_USERS`** with phone numbers (including country code, without the `+`)
|
||||
of authorized users. Without this setting, the gateway will **deny all incoming messages** as a
|
||||
safety measure.
|
||||
**Configure access control** before going live. Set `WHATSAPP_ALLOWED_USERS` with specific
|
||||
phone numbers (including country code, without the `+`), use `*` to allow everyone, or set
|
||||
`WHATSAPP_ALLOW_ALL_USERS=true`. Without any of these, the gateway **denies all incoming
|
||||
messages** as a safety measure.
|
||||
:::
|
||||
|
||||
By default, unauthorized DMs still receive a pairing code reply. If you want a private WhatsApp number to stay completely silent to strangers, set:
|
||||
|
||||
Reference in New Issue
Block a user