WhatsApp-Ausgangs-Sendefehler mit Allowlist-Richtlinie - WhatsApp Outbound Send Failure with Allowlist Policy
Wenn die WhatsApp dmPolicy 'allowlist' verwendet wird, schlagen ausgehende Nachrichten mit 'Delivering to WhatsApp requires target' fehl, weil sendTo-Ziele in derselben allowFrom-Liste sein müssen, die den eingehenden Zugriff steuert.
🔍 Symptome
Primäre Fehlererscheinung
Beim Versuch, eine WhatsApp-Nachricht mit dem message-Tool an einen Kontakt zu senden, der nicht in der allowFrom-Liste enthalten ist:
Error: Delivering to WhatsApp requires target <E.164|group JID>
at resolveOutboundTarget (src/whatsapp/resolve-outbound-target.ts:XX)
at sendWhatsAppMessage (src/whatsapp/sender.ts:XX)
Konfigurationskontext
Das Problem tritt auf, wenn die folgende Konfiguration vorhanden ist:
json { “channels”: { “whatsapp”: { “dmPolicy”: “allowlist”, “allowFrom”: ["+1234567890"] } } }
CLI-Diagnosebefehle
bash
Versuchen, eine Nachricht an einen nicht aufgeführten Kontakt zu senden
$ openclaw tools call message ‘{“to”: “+0987654321”, “body”: “Hello”}’
Erwartet: Nachricht erfolgreich gesendet
Tatsächlich: Fehler - Delivering to WhatsApp requires target <E.164|group JID>
Sekundäres Symptom: Verwirrendes Sicherheitsmodell
Benutzer beobachten, dass das Hinzufügen eines Kontakts zu allowFrom zwei Auswirkungen hat:
- Der Kontakt kann nun ausgehende Nachrichten vom Bot empfangen
- Der Kontakt kann auch eingehende Nachrichten senden, die den Bot auslösen
Dies verstößt gegen das Prinzip der geringsten Privilegien und erzeugt Sicherheitsverwirrung.
🧠 Ursache
Architekturanalyse
Die Grundursache liegt in der gemeinsamen Datenabhängigkeit zwischen eingehender und ausgehender Zugriffskontrolllogik.
Datei: src/whatsapp/resolve-outbound-target.ts
typescript
export async function resolveOutboundTarget(
normalizedTo: string,
allowList: string[]
): Promise
const hasWildcard = allowList.includes("*");
if (hasWildcard || allowList.length === 0) { return { ok: true, to: normalizedTo }; }
if (allowList.includes(normalizedTo)) { return { ok: true, to: normalizedTo }; }
return {
ok: false,
error: Delivering to WhatsApp requires target <E.164|group JID>,
};
}
Das Problem: Diese Funktion empfängt das allowFrom-Array als allowList-Parameter, was bedeutet, dass die ausgehende Berechtigung durch die eingehende Konfiguration kontrolliert wird.
Datei: src/web/inbound/access-control.ts
typescript export function checkInboundAccess( from: string, allowFrom: string[] ): InboundAccessResult { const hasWildcard = allowFrom.includes("*"); const isAllowed = hasWildcard || allowFrom.includes(from);
return { allowed: isAllowed, reason: isAllowed ? “allowed” : “inbound_not_authorized” }; }
Das Problem des gemeinsamen Kontrollpunkts
| Konfiguration | Eingehende Wirkung | Ausgehende Wirkung |
|---|---|---|
"allowFrom": ["+1234567890"] | Nur +1234567890 kann den Bot auslösen | Bot kann nur an +1234567890 senden |
"allowFrom": ["*"] | Jeder kann den Bot auslösen | Bot kann an jeden senden |
Designverstoß
Die aktuelle Implementierung verstößt gegen das Prinzip der Trennung von Anliegen. Das allowFrom-Feld wurde für die eingehende Zugriffskontrolle entwickelt, wird aber für die ausgehende Autorisierung wiederverwendet, was eine unbeabsichtigte Kopplung schafft.
🛠️ Schritt-für-Schritt-Lösung
Phase 1: Konfigurationstyp hinzufügen
Datei: src/config/types.whatsapp.ts
Vorher: typescript export interface WhatsAppConfig { dmPolicy: “allowlist” | “open”; allowFrom: string[]; // … other fields }
Nachher: typescript export interface WhatsAppConfig { dmPolicy: “allowlist” | “open”; allowFrom: string[]; allowSendTo?: string[]; // NEU: Separate ausgehende Allowlist // … other fields }
Phase 2: Ausgehende Auflösungslogik aktualisieren
Datei: src/whatsapp/resolve-outbound-target.ts
Vorher:
typescript
export async function resolveOutboundTarget(
normalizedTo: string,
allowList: string[]
): Promise
const hasWildcard = allowList.includes("*");
if (hasWildcard || allowList.length === 0) { return { ok: true, to: normalizedTo }; }
if (allowList.includes(normalizedTo)) { return { ok: true, to: normalizedTo }; }
return {
ok: false,
error: Delivering to WhatsApp requires target <E.164|group JID>,
};
}
Nachher:
typescript
export async function resolveOutboundTarget(
normalizedTo: string,
sendToList: string[] | undefined,
inboundAllowFrom: string[]
): Promise
// Wenn sendTo explizit konfiguriert ist, verwende es if (sendToList !== undefined) { const hasWildcard = sendToList.includes("*");
if (hasWildcard || sendToList.length === 0) {
return { ok: true, to: normalizedTo };
}
if (sendToList.includes(normalizedTo)) {
return { ok: true, to: normalizedTo };
}
return {
ok: false,
error: `Target ${normalizedTo} is not in allowSendTo list`,
};
}
// Fallback auf Legacy-Verhalten (verwende eingehendes allowFrom für ausgehend) const hasWildcard = inboundAllowFrom.includes("*");
if (hasWildcard || inboundAllowFrom.length === 0) { return { ok: true, to: normalizedTo }; }
if (inboundAllowFrom.includes(normalizedTo)) { return { ok: true, to: normalizedTo }; }
return {
ok: false,
error: Delivering to WhatsApp requires target <E.164|group JID>,
};
}
Phase 3: Aufruferstellen aktualisieren
Datei: src/whatsapp/sender.ts (oder wo immer resolveOutboundTarget aufgerufen wird)
Vorher: typescript const target = await resolveOutboundTarget( normalizedTo, config.allowFrom // Übergabe der eingehenden Liste für ausgehende Prüfung );
Nachher: typescript const target = await resolveOutboundTarget( normalizedTo, config.allowSendTo, // Verwende dedizierte ausgehende Liste config.allowFrom // Übergabe für Legacy-Fallback );
Phase 4: Konfigurationsbeispiel
Empfohlene Produktionskonfiguration:
json { “channels”: { “whatsapp”: { “dmPolicy”: “allowlist”, “allowFrom”: ["+1234567890", “+1111111111”], “allowSendTo”: ["*"] } } }
Strenge ausgehende Konfiguration:
json { “channels”: { “whatsapp”: { “dmPolicy”: “allowlist”, “allowFrom”: ["+1234567890"], “allowSendTo”: [ “+0987654321”, “+1122334455”, “[email protected]” ] } } }
🧪 Verifizierung
Testfall 1: Ausgehend an erlaubten SendTo
bash
Konfiguration
“allowSendTo”: ["+0987654321"]
$ openclaw tools call message ‘{“to”: “+0987654321”, “body”: “Test”}’
Erwartete Ausgabe: json { “ok”: true, “messageId”: “wamid.xxx…”, “timestamp”: “2024-01-15T10:30:00Z” }
Testfall 2: Ausgehend an nicht aufgeführten SendTo
bash
Konfiguration
“allowSendTo”: ["+0987654321"]
$ openclaw tools call message ‘{“to”: “+5555555555”, “body”: “Test”}’
Erwartete Ausgabe: json { “ok”: false, “error”: “Target +5555555555 is not in allowSendTo list” }
Testfall 3: Eingehend von erlaubtem Absender
bash
Konfiguration
“allowFrom”: ["+0987654321"]
“allowSendTo”: ["*"]
Nachricht VON +0987654321 AN den Bot senden
Erwartetes Verhalten: Nachricht wird verarbeitet und löst Bot-Antwort aus.
Testfall 4: Eingehend von nicht aufgeführtem Absender
bash
Konfiguration
“allowFrom”: ["+0987654321"]
Nachricht VON +5555555555 AN den Bot senden
Erwartetes Verhalten: Nachricht wird mit Zugriffskontrollfehler abgelehnt.
Testfall 5: Wildcard SendTo
bash
Konfiguration
“allowSendTo”: ["*"]
$ openclaw tools call message ‘{“to”: “+anyvalidnumber”, “body”: “Test”}’
Erwartete Ausgabe: Nachricht wird erfolgreich gesendet.
Verifizierungsskript
typescript // test/whatsapp-outbound-permissions.test.ts
import { resolveOutboundTarget } from “../src/whatsapp/resolve-outbound-target”;
describe(“resolveOutboundTarget”, () => { test(“allows when target is in sendTo list”, async () => { const result = await resolveOutboundTarget( “+0987654321”, ["+0987654321", “+1122334455”], ["+1234567890"] ); expect(result.ok).toBe(true); });
test(“blocks when target is not in sendTo list”, async () => { const result = await resolveOutboundTarget( “+5555555555”, ["+0987654321"], ["+1234567890"] ); expect(result.ok).toBe(false); expect(result.error).toContain(“not in allowSendTo list”); });
test(“allows wildcard sendTo”, async () => { const result = await resolveOutboundTarget( “+5555555555”, ["*"], ["+1234567890"] ); expect(result.ok).toBe(true); });
test(“falls back to allowFrom when sendTo is undefined”, async () => { const result = await resolveOutboundTarget( “+1234567890”, undefined, // sendTo nicht konfiguriert ["+1234567890"] ); expect(result.ok).toBe(true); }); });
⚠️ Häufige Fehler
Fehler 1: Falsches E.164-Format
WhatsApp erfordert Nummern im E.164-Format (z.B. +1234567890). Die Verwendung von Formaten ohne führendes + führt zu stillen Fehlern.
bash
FALSCH
$ openclaw tools call message ‘{“to”: “1234567890”, “body”: “Test”}’
RICHTIG
$ openclaw tools call message ‘{“to”: “+1234567890”, “body”: “Test”}’
Fehler 2: Gruppen-JID vs. Telefonnummer
Gruppen-IDs verwenden ein anderes Format als Telefonnummern. Stellen Sie die korrekte JID-Syntax sicher:
json { “allowSendTo”: [ “+1234567890”, // Telefonnummer “[email protected]” // Gruppen-JID ] }
Fehler 3: Leeres Array vs. Undefined
Ein leeres allowSendTo: []-Array wird anders behandelt als allowSendTo, das undefined ist:
"allowSendTo": []— Blockiert alle ausgehenden Nachrichten"allowSendTo": undefined— Fällt zurück auf Legacy-allowFrom-Verhalten
Fehler 4: Docker-Umgebungsvariablen-Zuordnung
Bei Verwendung von Umgebungsvariablen für die Konfiguration:
bash
FALSCH - Dies erstellt einen String, kein Array
WHATSAPP_ALLOW_SEND_TO=+1234567890,+0987654321
RICHTIG - JSON-String für Arrays verwenden
WHATSAPP_ALLOW_SEND_TO=["+1234567890","+0987654321"]
Fehler 5: Cache-Probleme
Nach dem Aktualisieren der Konfiguration muss der laufende Prozess neu laden:
bash
OpenClaw-Dienst neu starten
$ systemctl restart openclaw
Oder für Docker
$ docker-compose down && docker-compose up -d
Fehler 6: Migration von Legacy-Konfiguration
Existierende Konfigurationen ohne allowSendTo sollten über den Fallback-Mechanismus weiterhin funktionieren. Testen Sie jedoch gründlich:
typescript // Fallback-Verhalten verifizieren const sendTo = config.allowSendTo ?? config.allowFrom;
Fehler 7: Sicherheitsimplikationen von Wildcards
Das Setzen von "allowSendTo": ["*"] erlaubt das Senden an jede gültige WhatsApp-Nummer. Berücksichtigen Sie:
- Ratenbegrenzung für das Message-Tool
- Zusätzliche autorisierungsbasierte Anwendungsebene
- Protokollierung aller ausgehenden Nachrichtenversuche
🔗 Zugehörige Fehler
E_DELIVERY_FAILED— Generischer Übermittlungsfehler, wenn die WhatsApp-API die Nachricht ablehntE_INVALID_TARGET— Zielnummerformat ist ungültig (nicht E.164-konform)E_INBOUND_NOT_AUTHORIZED— Eingehende Nachricht aufgrund von Allowlist-Richtlinie abgelehntE_SESSION_NOT_READY— WhatsApp-Sitzung vor ausgehendem Versuch nicht hergestelltE_ALLOWLIST_BLOCKED— Ausgehendes Ziel nicht in konfigurierter Allowlist
Zugehörige GitHub-Issues
- Issue #XXX — WhatsApp dmPolicy Allowlist blockiert legitime ausgehende Nachrichten
- Issue #YYY — Anfrage: Separate eingehende/ausgehende Zugriffskontrolle für WhatsApp
- Issue #ZZZ — Dokumentation: WhatsApp-Kanal-Sicherheitsmodell unklar
Konfigurationsreferenz
channels.whatsapp.dmPolicy— Steuert den eingehenden Zugriffsmodus (`"allowlist"` | `"open"`)channels.whatsapp.allowFrom— Eingehende Allowlist (Telefonnummern und Gruppen-JIDs)channels.whatsapp.allowSendTo— Ausgehende Allowlist (Telefonnummern und Gruppen-JIDs) [NEU]