[WhatsApp許可リストポリシーでのアウトバウンド送信失敗] - WhatsApp Outbound Send Failure with Allowlist Policy
WhatsAppのdmPolicy 'allowlist'を使用している場合、'Delivering to WhatsApp requires target'というエラーでアウトバウンドメッセージの送信に失敗します,这是因为sendToターゲットが、インバウンドアクセスを制御する同じallowFromリストに含まれる必要があるためです。
🔍 症状
主なエラーの発生状況
messageツールを使用して、allowFromリストに含まれていない連絡先に対してWhatsAppメッセージを送信しようとすると:
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)
設定のコンテキスト
以下の設定が存在する場合に問題が発生します:
json { “channels”: { “whatsapp”: { “dmPolicy”: “allowlist”, “allowFrom”: ["+1234567890"] } } }
CLI診断コマンド
bash
リストに含まれていない連絡先へのメッセージ送信を試行
$ openclaw tools call message ‘{“to”: “+0987654321”, “body”: “Hello”}’
期待結果: メッセージが正常に送信される
実際の結果: エラー - Delivering to WhatsApp requires target <E.164|group JID>
二次的症状: 混乱を招くセキュリティモデル
ユーザーは、allowFromに連絡先を追加すると2つの効果が生じることを観測します:
- 連絡先はボットからアウトバウンドメッセージを受信できる
- 連絡先はボットをトリガーするインバウンドメッセージも送信できる
これは最小権限の原則に反し、セキュリティ上の混乱を生み出します。
🧠 原因
アーキテクチャ分析
根本原因は、インバウンドとアウトバウンドのアクセス制御ロジック間の共有データ依存にあります。
ファイル: 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>,
};
}
問題点: この関数はallowListパラメータとしてallowFrom配列を受け取るため、アウトバウンドの許可がインバウンド設定によって制御されています。
ファイル: 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” }; }
共有制御点の問題
| 設定 | インバウンドへの影響 | アウトバウンドへの影響 |
|---|---|---|
"allowFrom": ["+1234567890"] | +1234567890のみがボットをトリガーできる | ボットは+1234567890にのみ送信できる |
"allowFrom": ["*"] | 誰でもボットをトリガーできる | ボットは誰にでも送信できる |
設計違反
現在の実装は関心の分離の原則に違反しています。allowFromフィールドはインバウンドアクセス制御用に設計されていましたが、アウトバウンド認証に転用されており、予期せぬ結合を生み出しています。
🛠️ 解決手順
フェーズ1: 設定タイプの追加
ファイル: src/config/types.whatsapp.ts
変更前: typescript export interface WhatsAppConfig { dmPolicy: “allowlist” | “open”; allowFrom: string[]; // … other fields }
変更後: typescript export interface WhatsAppConfig { dmPolicy: “allowlist” | “open”; allowFrom: string[]; allowSendTo?: string[]; // NEW: Separate outbound allowlist // … other fields }
フェーズ2: アウトバウンド解決ロジックの更新
ファイル: 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>,
};
}
変更後:
typescript
export async function resolveOutboundTarget(
normalizedTo: string,
sendToList: string[] | undefined,
inboundAllowFrom: string[]
): Promise
// If sendTo is explicitly configured, use it 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 to legacy behavior (use inbound allowFrom for outbound) 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>,
};
}
フェーズ3: 呼び出し元の更新
ファイル: src/whatsapp/sender.ts(resolveOutboundTargetが呼び出される場所)
変更前: typescript const target = await resolveOutboundTarget( normalizedTo, config.allowFrom // Passing inbound list for outbound check );
変更後: typescript const target = await resolveOutboundTarget( normalizedTo, config.allowSendTo, // Use dedicated outbound list config.allowFrom // Pass for legacy fallback );
フェーズ4: 設定例
推奨される本番環境設定:
json { “channels”: { “whatsapp”: { “dmPolicy”: “allowlist”, “allowFrom”: ["+1234567890", “+1111111111”], “allowSendTo”: ["*"] } } }
厳格なアウトバウンド設定:
json { “channels”: { “whatsapp”: { “dmPolicy”: “allowlist”, “allowFrom”: ["+1234567890"], “allowSendTo”: [ “+0987654321”, “+1122334455”, “[email protected]” ] } } }
🧪 検証
テストケース1: 許可されたSendToへのアウトバウンド
bash
Configuration
“allowSendTo”: ["+0987654321"]
$ openclaw tools call message ‘{“to”: “+0987654321”, “body”: “Test”}’
期待される出力: json { “ok”: true, “messageId”: “wamid.xxx…”, “timestamp”: “2024-01-15T10:30:00Z” }
テストケース2: 許可されていないSendToへのアウトバウンド
bash
Configuration
“allowSendTo”: ["+0987654321"]
$ openclaw tools call message ‘{“to”: “+5555555555”, “body”: “Test”}’
期待される出力: json { “ok”: false, “error”: “Target +5555555555 is not in allowSendTo list” }
テストケース3: 許可された送信者からのインバウンド
bash
Configuration
“allowFrom”: ["+0987654321"]
“allowSendTo”: ["*"]
+0987654321からボットへメッセージを送信
期待される動作: メッセージが処理され、ボットのレスポンスがトリガーされます。
テストケース4: 許可されていない送信者からのインバウンド
bash
Configuration
“allowFrom”: ["+0987654321"]
+5555555555からボットへメッセージを送信
期待される動作: インバウンドアクセス制御エラーでメッセージが拒否されます。
テストケース5: ワイルドカードSendTo
bash
Configuration
“allowSendTo”: ["*"]
$ openclaw tools call message ‘{“to”: “+anyvalidnumber”, “body”: “Test”}’
期待される出力: メッセージが正常に送信されます。
検証スクリプト
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 not configured ["+1234567890"] ); expect(result.ok).toBe(true); }); });
⚠️ よくある落とし穴
落とし穴1: 正しくないE.164フォーマット
WhatsAppはE.164形式(例:+1234567890)の番号を必要とします。先頭の+なしで使用すると、サイレント失敗が発生します。
bash
誤り
$ openclaw tools call message ‘{“to”: “1234567890”, “body”: “Test”}’
正しい
$ openclaw tools call message ‘{“to”: “+1234567890”, “body”: “Test”}’
落とし穴2: グループJIDと電話番号の混同
グループIDは電話番号とは異なる形式を使用します。正しいJID構文ことを確認してください:
json { “allowSendTo”: [ “+1234567890”, // 電話番号 “[email protected]” // グループJID ] }
落とし穴3: 空配列とundefinedの違い
空のallowSendTo: []配列は、allowSendToがundefinedの場合と異なった扱いを受けます:
"allowSendTo": []— すべてのアウトバウンドメッセージをブロック"allowSendTo": undefined— レガシーなallowFrom動作にフォールバック
落とし穴4: Docker環境変数のマッピング
環境変数で設定を使用する場合:
bash
誤り - これは配列ではなく文字列になります
WHATSAPP_ALLOW_SEND_TO=+1234567890,+0987654321
正しい - 配列にはJSON文字列を使用
WHATSAPP_ALLOW_SEND_TO=["+1234567890","+0987654321"]
落とし穴5: キャッシュの問題
設定を更新した後、実行中のプロセスがリロードすることを確認してください:
bash
OpenClawサービスを再起動
$ systemctl restart openclaw
Dockerの場合
$ docker-compose down && docker-compose up -d
落とし穴6: レガシー設定からの移行
allowSendToなしの設定は、フォールバックメカニズム経由で引き続き動作するはずです。ただし、十分にテストしてください:
typescript // フォールバックが動作することを確認 const sendTo = config.allowSendTo ?? config.allowFrom;
落とし穴7: ワイルドカードのセキュリティ上の影響
"allowSendTo": ["*"]を設定すると、有効なWhatsApp番号への送信が許可されます。以下の点を検討してください:
- messageツールへのレート制限
- アプリケーションレベルの追加認証
- すべてのアウトバウンドメッセージ試行のログ記録
🔗 関連するエラー
E_DELIVERY_FAILED— WhatsApp APIがメッセージを拒否した場合の汎用配信失敗E_INVALID_TARGET— ターゲット番号の形式が無効(E.164に準拠していない)E_INBOUND_NOT_AUTHORIZED— 許可リストポリシーによりインバウンドメッセージが拒否されたE_SESSION_NOT_READY— アウトバウンド試行前にWhatsAppセッションが確立されていないE_ALLOWLIST_BLOCKED— アウトバウンドターゲットが設定された許可リストに含まれていない
関連するGitHubイシュー
- Issue #XXX — WhatsApp dmPolicy許可リストが正当なアウトバウンドメッセージをブロックする
- Issue #YYY — 要望: WhatsAppのインバウンド/アウトバウンドアクセス制御の分離
- Issue #ZZZ — ドキュメント: WhatsAppチャンネルのセキュリティモデルが不明確
設定リファレンス
channels.whatsapp.dmPolicy— インバウンドアクセスモードを制御(`"allowlist"` | `"open"`)channels.whatsapp.allowFrom— インバウンド許可リスト(電話番号とグループJID)channels.whatsapp.allowSendTo— アウトバウンド許可リスト(電話番号とグループJID)[新規]