Feb 18, 2026·5 min read·12 visits
OpenClaw introduced a configuration flag to help developers test Twilio webhooks over ngrok. Instead of fixing header validation, the flag implemented a hardcoded `return true` bypass for signature checks. This allows attackers to forge webhooks and trigger unauthorized voice events on affected instances.
In the world of programmable voice, verifying webhooks is critical—unless you're using OpenClaw's 'ngrok compatibility' mode. A vulnerability in the `openclaw` npm package allowed attackers to completely bypass Twilio signature verification by exploiting a developer convenience flag designed for local testing. By effectively telling the server 'it's okay, I'm using ngrok,' the application would skip cryptographic checks entirely, allowing unauthenticated actors to forge incoming voice calls and manipulate call flows.
Webhook verification is a pain. If you've ever developed a Twilio integration locally, you know the struggle: Twilio signs requests based on the absolute URL they think they are hitting. But when you are sitting behind an ngrok tunnel on your laptop, the Host header, X-Forwarded-Proto, and the actual URL often get mangled or mismatched. The signature validation fails, not because you are being hacked, but because the internet is messy.
OpenClaw, an open-source programmable voice platform, tried to solve this 'developer experience' friction. The goal was noble: allow developers using the free tier of ngrok (which inserts its own interstitial warning pages and messes with headers) to test their voice flows without tearing their hair out over HMAC failures.
However, in security, the road to hell is paved with 'temporary workarounds'. Instead of correctly reconstructing the complex header logic required to validate a signature through a proxy, the developers opted for a much simpler solution: a literal 'Get Out of Jail Free' card.
The vulnerability lies in extensions/voice-call/src/webhook-security.ts. The logic was intended to handle the disparity between the public ngrok URL and the local express server's perception of the request.
The implementation of tunnel.allowNgrokFreeTierLoopbackBypass was catastrophically simple. It didn't relax the rules; it removed the game board entirely. The code checked for three things:
.ngrok-free.app or .ngrok.io domain?If these three stars aligned, the application short-circuited the entire verification process. It didn't try to validate the signature with a different strategy. It just returned ok: true. This is the cryptographic equivalent of a bouncer checking if you're wearing sneakers, and if so, letting you into the club without checking ID.
Let's look at the smoking gun. This isn't a complex buffer overflow or a race condition. It is a logic error that is painfully easy to read.
The Vulnerable Code:
// extensions/voice-call/src/webhook-security.ts
const isNgrokFreeTier = verificationUrl.includes(".ngrok-free.app") || verificationUrl.includes(".ngrok.io");
// The Fatal Flaw
if (isNgrokFreeTier && options?.allowNgrokFreeTierLoopbackBypass && isLoopback) {
console.warn("[voice-call] Twilio signature validation failed (ngrok free tier compatibility, loopback only)");
// BYPASS!
return {
ok: true,
reason: "ngrok free tier compatibility mode (loopback only)",
verificationUrl,
isNgrokFreeTier: true,
};
}The Fix (Commit ff11d87):
The patch removes this short-circuit entirely. Instead of skipping verification, the flag now instructs the verification logic to trust the X-Forwarded headers only when on loopback, allowing the server to reconstruct the correct URL that Twilio signed, and then actually verify the HMAC.
// The fix logic: Don't skip, just adjust the URL construction
if (isLoopback && options?.allowNgrokFreeTierLoopbackBypass) {
// Use headers to reconstruct the public URL correctly
// Then proceed to STANDARD cryptographic verification
}Exploiting this is trivial if the target environment allows it. The attack relies on the fact that when ngrok (the software) forwards a request to your local machine, the connection to your app comes from 127.0.0.1 (loopback). The code trusts the source IP because it assumes loopback is safe.
The Attack Chain:
allowNgrokFreeTierLoopbackBypass: true enabled (common in staging environments)./webhooks/voice. They include any X-Twilio-Signature header (it can be garbage, e.g., X-Twilio-Signature: aaaaa).Why it works:
OpenClaw sees the request coming from the ngrok agent (localhost). It sees the domain is ngrok.io. It sees the flag is on. It ignores the garbage signature and processes the request.
The attacker can now simulate an incoming call, injecting malicious TwiML (Twilio Markup Language) to record audio, redirect calls, or harass users, all while billing the victim's Twilio account.
The remediation logic in version 2026.2.14 is a lesson in how to handle proxies correctly. You never bypass authentication because the network topology is hard; you adapt the authentication to the topology.
The developers removed the return { ok: true } block. They replaced it with logic that says: "If we are on loopback and in compatibility mode, we will trust the X-Forwarded-Proto and X-Forwarded-Host headers to tell us what the real URL is."
This allows the application to reconstruct the exact string that Twilio signed (https://my-tunnel.ngrok.io/webhook) rather than the internal string (http://localhost:3000/webhook), causing the HMAC calculation to match and the signature to pass naturally. Security is restored, and convenience is maintained—without leaving the door wide open.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
openclaw openclaw | <= 2026.2.13 | 2026.2.14 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-347 |
| Attack Vector | Network |
| CVSS Score | 5.8 (Medium) |
| Impact | Integrity Loss (Auth Bypass) |
| Exploit Status | PoC Available |
| Affected Component | webhook-security.ts |
Improper Verification of Cryptographic Signature