Update nango crate: add webhook module with verification, expand types per docs#3713
Update nango crate: add webhook module with verification, expand types per docs#3713
Conversation
…s per docs Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
✅ Deploy Preview for hyprnote canceled.
|
✅ Deploy Preview for hyprnote-storybook canceled.
|
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
| let expected = hex::encode(mac.finalize().into_bytes()); | ||
| expected == signature |
There was a problem hiding this comment.
🔴 Timing side-channel in webhook signature verification using non-constant-time ==
The verify_webhook_signature function at crates/nango/src/webhook.rs:16 uses plain == string comparison to compare the computed HMAC against the provided signature. This is vulnerable to timing side-channel attacks, where an attacker can progressively determine the correct signature byte-by-byte by measuring response time differences.
Root Cause and Impact
Rust's == on strings short-circuits on the first differing byte. An attacker sending crafted webhook requests can measure how long the comparison takes and deduce how many leading bytes of their forged signature match the expected one. Over many requests, this allows reconstructing the full valid HMAC without knowing the secret key.
The hmac crate already provides Mac::verify() which performs constant-time comparison internally. The current code computes the HMAC correctly but then discards the constant-time verification by hex-encoding and using ==:
let expected = hex::encode(mac.finalize().into_bytes());
expected == signatureInstead, the signature should be decoded from hex and verified using mac.verify_slice(), or the raw bytes should be compared using a constant-time comparison function.
Impact: An attacker who can send arbitrary webhook payloads to the endpoint and measure response times could forge valid webhook signatures, bypassing authentication and injecting malicious webhook events.
| let expected = hex::encode(mac.finalize().into_bytes()); | |
| expected == signature | |
| let expected = mac.finalize().into_bytes(); | |
| let Ok(signature_bytes) = hex::decode(signature) else { | |
| return false; | |
| }; | |
| expected.as_slice().len() == signature_bytes.len() | |
| && hmac::Mac::verify_slice(HmacSha256::new_from_slice(secret_key.as_bytes()).unwrap(), &signature_bytes).is_ok() | |
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
Updates
crates/nangoto align with the current Nango API docs for API auth and webhooks.Changes:
types.rs: Addedtags: Option<HashMap<String, String>>toNangoConnectSessionRequestUserper the connect session API. Removed the old minimalNangoConnectWebhook/NangoConnectWebhookEndUsertypes (unused outside this crate).webhook.rs(new): Dedicated module containing:verify_webhook_signature()— HMAC-SHA256 verification for theX-Nango-Hmac-Sha256headerNangoAuthWebhook— full auth webhook payload (authMode,providerConfigKey,provider,environment,success,error, etc.)NangoSyncWebhook/NangoSyncWebhookResults— sync webhook typesNangoWebhookEndUser— now includesendUserEmailandtagsNangoWebhookError— error payload for failed auth operationsReview & Testing Checklist for Human
verify_webhook_signatureuses plain==string comparison rather than constant-time comparison (hmac::Mac::verifyor similar). Evaluate whether this matters for your threat model.NangoConnectWebhook→NangoAuthWebhook,NangoConnectWebhookEndUser→NangoWebhookEndUser. Confirm no downstream consumers outside this crate reference the old names (grep showed none, but verify in any out-of-tree code).NangoAuthWebhook: Fields likeauth_mode,provider,environmentare required. Verify against real Nango webhook payloads that these are always present.Notes
hmac,sha2,hexadded as direct crate deps (not workspace-level) since they're only used here.