A few months ago, I started offering consulting services as a Django developer. One of my first clients had an urgent issue:
Our OpenAI credits are draining at a crazy rate, and we don’t know why.
They suspected misuse. I dug into their codebase and deployment setup — and one of the first red flags was this:
# settings.py
DEBUG = True
Their production server was running with debug mode enabled.
It didn't take long to find something more worrying...
An Endpoint Was Leaking Their OpenAI API Key
Django's debug mode is helpful during development. But in production, it exposes detailed stack traces and local variable values in the browser when an exception occurs.
In their case, one endpoint was trying to parse JSON from a request body...but when the request was malformed(for example, accessing the endpoint via GET instead of POST, a malformed JSON body etc), it raised a JSONDecodeError
. Here is a simplified version of the code:
from django.http import HttpRequest, JsonResponse
from dotenv import load_dotenv
import os
import json
load_dotenv() # loads env variables from a .env file
def index(request: HttpRequest) -> JsonResponse:
open_ai_key = os.getenv("OPENAI_KEY")
data = json.loads(request.body.decode("utf-8")) # Boom on bad requests
return JsonResponse(data)
With DEBUG=True, that simple mistake triggered Django’s detailed error page, including:
- The full stack trace
- Request info
- Local variables — including open_ai_key 😱
Here’s a screenshot from a proof of concept (POC) I recreated to demonstrate what was happening:
Fix
I immediately advised two things:
- Rotate the OpenAI key — assume it’s compromised.
- Re-deploy the app with
DEBUG = False
insettings.py
.
DEBUG = False
With DEBUG set to false, Django shows a standard error 500 page without leaking any sensitive data.
I also recommended wrapping JSON parsing in a try/except block to avoid unhandled exceptions:
try:
data = json.loads(request.body.decode("utf-8"))
except json.JSONDecodeError:
return JsonResponse({"error": "Invalid JSON"}, status=400)
Key takeaways
- Never deploy Django with
Django=True
- Wrap dangerous logic (like JSON parsing) in proper error handling.
What looked like a billing issue turned out to be a security hole. If you’re building with Django (or any framework), treat debug tools like loaded guns: safe in dev, dangerous in prod.
Happy Django-ing 👋
Top comments (0)