Fix Insecure Webhooks in Sails
Webhooks are essentially unauthenticated POST endpoints exposed to the public internet. If you aren't verifying the payload signature, you're inviting attackers to spoof events, manipulate data, or escalate privileges. In Sails, the default body parser makes it easy to blindly trust incoming JSON. Real security requires a cryptographic handshake using HMAC and a shared secret.
The Vulnerable Pattern
// api/controllers/WebhookController.js module.exports = async function (req, res) { // VULNERABILITY: Blindly trusting the request body const { userId, eventType } = req.body;if (eventType === ‘payment_success’) { await User.updateOne({ id: userId }).set({ plan: ‘premium’ }); }
return res.ok(); };
The Secure Implementation
The fix involves three layers of defense: 1. A shared secret known only to the provider and your Sails app. 2. HMAC-SHA256 signature verification to ensure the payload hasn't been tampered with or spoofed. 3. Using `crypto.timingSafeEqual` to compare signatures, which prevents attackers from guessing the secret byte-by-byte via execution time analysis. For production environments, ensure you use the raw request body if your middleware modifies `req.body` during parsing.
// api/controllers/WebhookController.js const crypto = require('crypto');module.exports = async function (req, res) { const signature = req.headers[‘x-hub-signature-256’]; const secret = sails.config.custom.webhookSecret;
if (!signature) { return res.forbidden(‘Missing signature’); }
// Calculate HMAC SHA256 const hmac = crypto.createHmac(‘sha256’, secret); const digest = ‘sha256=’ + hmac.update(JSON.stringify(req.body)).digest(‘hex’);
// Use timingSafeEqual to prevent side-channel timing attacks const trusted = Buffer.from(digest, ‘ascii’); const untrusted = Buffer.from(signature, ‘ascii’);
if (trusted.length !== untrusted.length || !crypto.timingSafeEqual(trusted, untrusted)) { return res.forbidden(‘Invalid signature’); }
// Payload is verified; proceed with logic const { userId, eventType } = req.body; if (eventType === ‘payment_success’) { await User.updateOne({ id: userId }).set({ plan: ‘premium’ }); }
return res.ok(); };
Your Sails API
might be exposed to Insecure Webhooks
74% of Sails apps fail this check. Hackers use automated scanners to find this specific flaw. Check your codebase before they do.
Free Tier • No Credit Card • Instant Report
Verified by Ghost Labs Security Team
This content is continuously validated by our automated security engine and reviewed by our research team. Ghost Labs analyzes over 500+ vulnerability patterns across 40+ frameworks to provide up-to-date remediation strategies.