fix: add WhatsApp webhook signature verification (X-Hub-Signature-256)

Closes #51

- Add HMAC-SHA256 signature verification for WhatsApp webhooks
- Prevents message spoofing attacks (CWE-345)
- Add whatsapp_app_secret config field with ZEROCLAW_WHATSAPP_APP_SECRET env override
- Add 13 comprehensive unit tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Argenis 2026-02-15 06:17:24 -05:00 committed by GitHub
parent 026a917544
commit 5cc02c5813
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 453 additions and 17 deletions

View file

@ -604,6 +604,10 @@ pub struct WhatsAppConfig {
pub phone_number_id: String,
/// Webhook verify token (you define this, Meta sends it back for verification)
pub verify_token: String,
/// App secret from Meta Business Suite (for webhook signature verification)
/// Can also be set via `ZEROCLAW_WHATSAPP_APP_SECRET` environment variable
#[serde(default)]
pub app_secret: Option<String>,
/// Allowed phone numbers (E.164 format: +1234567890) or "*" for all
#[serde(default)]
pub allowed_numbers: Vec<String>,
@ -1172,6 +1176,7 @@ channel_id = "C123"
access_token: "EAABx...".into(),
phone_number_id: "123456789".into(),
verify_token: "my-verify-token".into(),
app_secret: None,
allowed_numbers: vec!["+1234567890".into(), "+9876543210".into()],
};
let json = serde_json::to_string(&wc).unwrap();
@ -1188,6 +1193,7 @@ channel_id = "C123"
access_token: "tok".into(),
phone_number_id: "12345".into(),
verify_token: "verify".into(),
app_secret: Some("secret123".into()),
allowed_numbers: vec!["+1".into()],
};
let toml_str = toml::to_string(&wc).unwrap();
@ -1209,6 +1215,7 @@ channel_id = "C123"
access_token: "tok".into(),
phone_number_id: "123".into(),
verify_token: "ver".into(),
app_secret: None,
allowed_numbers: vec!["*".into()],
};
let toml_str = toml::to_string(&wc).unwrap();
@ -1230,6 +1237,7 @@ channel_id = "C123"
access_token: "tok".into(),
phone_number_id: "123".into(),
verify_token: "ver".into(),
app_secret: None,
allowed_numbers: vec!["+1".into()],
}),
};