DEV Community

SAINT
SAINT

Posted on

How I Caught an MPESA API Leak Hiding in Plain Sight

_
So here's the tea_ 🍵.

A while back, I picked up a Django consulting gig — nothing major, just helping out a startup that was panicking over something real bad:

“Bro, someone’s draining our MPESA till via the Daraja API.”

They kept rotating their consumer key and secret, but the money kept disappearing. It was like patching a leaking pipe without finding the hole. So they called me in.

🔍 The Setup
I asked for full access to the codebase. They were using Django (bless them — I love Django too), and they had a clean enough setup. But something instantly made me flinch:

python

# settings.py
DEBUG = True
🚨 Red flag. Big one.
Enter fullscreen mode Exit fullscreen mode

They were running production with Django’s debug mode on — which basically hands over your server's internals on a silver platter whenever something crashes.

I started poking around and spotted something worse...

đź’€ The Leak Was Right There...
They had this endpoint meant to parse JSON from requests:

python

def index(request: HttpRequest) -> JsonResponse:
    consumer_secret = os.getenv("CONSUMER_SECRET")
    consumer_key = os.getenv("CONSUMER_KEY")
    data = json.loads(request.body.decode("utf-8"))  # đź’Ą crashes on bad JSON
    return JsonResponse(data)
Enter fullscreen mode Exit fullscreen mode

Looks innocent enough, right?

Well... anytime that JSON payload was invalid — like if someone sent a GET instead of a POST or posted broken JSON — Django threw an unhandled exception.

But with

DEBUG=True
Enter fullscreen mode Exit fullscreen mode

, it responded with the entire stack trace...

And guess what was sitting right there in the local variables?

Yup. The consumer key and secret. đź« 

So if a curious hacker sent a malformed request to that endpoint… they’d get a nice, juicy traceback with the keys to the kingdom.

đź”§** My Fixes**
I immediately told them:

Rotate the consumer credentials — assume they’re already out there.

Set

 DEBUG = False
Enter fullscreen mode Exit fullscreen mode

— like yesterday.

Wrap the JSON parsing in a try-except block to catch garbage input:

python

try:
    data = json.loads(request.body.decode("utf-8"))
except json.JSONDecodeError:
    return JsonResponse({"error": "Invalid JSON"}, status=400)
After patching it, the bleeding stopped
Enter fullscreen mode Exit fullscreen mode

.

🔍** Bonus: I Got Curious…**
Out of curiosity, I ran a few dorks on Shodan and Censys to see how many live Django apps in Kenya still had DEBUG=True.

Let’s just say... we’re sitting on a ticking time bomb.

đź§  Lessons Learned
Never — ever — deploy Django with DEBUG=True. It’s basically broadcasting your secrets to the world.

Wrap any fragile logic (especially input parsing) in try/except blocks.

Treat dev tools like weapons — fine in the lab, deadly in prod.

Assume your environment variables are vulnerable when stack traces are exposed.

That’s it. One line of careless code almost cost them a business.

And the scary part? Most of this could've been caught with a simple code review.

If you're building anything with sensitive APIs — especially involving money — take time to audit your error handling and config. It could save you millions... or your reputation.

Happy coding — and stay paranoid. 🧠💻

Top comments (0)