Api Key Exposure in Django with Mongodb
Api Key Exposure in Django with Mongodb — how this specific combination creates or exposes the vulnerability
When Django applications use MongoDB as a backend—commonly via libraries like djongo or mongoengine—developers often store database connection strings, third-party service API keys, or internal tokens in settings and models. If these values are serialized into documents without care, or if query responses expose raw fields to unauthenticated endpoints, keys can leak.
MongoDB’s schema-less design can inadvertently promote storing sensitive fields directly in documents. In Django, if a model includes fields such as api_key or secret and those fields are included in to_json() or returned through an unguarded serialize call, an unauthenticated API endpoint can disclose them. For example, an endpoint like /api/config/ that returns app settings as JSON may include the MongoDB URI or a third-party service key if the view does not explicitly strip sensitive attributes.
Additionally, log aggregation or debugging endpoints that echo query parameters or projection fields can reveal key material. Because MongoDB uses BSON, keys stored as plain strings remain readable unless encrypted at rest or masked at the application layer. The risk is compounded when the same Django project exposes both an administrative interface and public APIs; misconfigured permissions or overlooked exclude declarations in forms or serializers can cause sensitive fields to appear in responses.
Real-world patterns observed in the wild include embedding AWS access keys for backend processing or embedding tokens for third-party integrations directly in documents. When combined with improper index exposure or verbose error messages, these practices create a pathway for enumeration and exfiltration by unauthenticated scanners, aligning with findings from security scans such as those provided by middleBrick’s unauthenticated attack surface checks.
Mongodb-Specific Remediation in Django — concrete code fixes
To mitigate exposure, treat MongoDB fields with the same caution as relational database columns. Use model-level controls, serialization discipline, and runtime validation to ensure keys are never unintentionally surfaced.
1. Exclude sensitive fields from serialization
When using Django REST Framework with MongoDB (e.g., via mongoengine), explicitly exclude sensitive fields in serializers:
from rest_framework import serializers
from myapp.models import ServiceConfig
class SafeConfigSerializer(serializers.DocumentSerializer):
class Meta:
model = ServiceConfig
exclude = ('api_key', 'internal_token', 'db_uri')
Ensure that views use this serializer class and never fall back to a generic ModelSerializer that might include all fields.
2. Use property-based access for sensitive values
Store sensitive values outside the document model and retrieve them via environment variables or a secure vault at runtime. Define model properties that do not persist the raw key:
import os
from mongoengine import Document, StringField
class ServiceConfig(Document):
name = StringField(required=True)
# Do not store api_key in the document
@property
def api_key(self):
return os.environ.get('SERVICE_API_KEY')
@property
def db_uri(self):
return os.environ.get('MONGODB_URI')
This keeps secrets out of the database layer while allowing the application to reference them safely.
3. Sanitize query responses and logs
Override to_mongo and to_json behaviors to strip or mask sensitive content before serialization:
from mongoengine import Document, StringField
class SafeDocument(Document):
meta = {'allow_inheritance': True}
def to_json(self, *args, **kwargs):
data = super().to_json(*args, **kwargs)
# Remove or mask sensitive keys before returning
import json
parsed = json.loads(data)
parsed.pop('api_key', None)
parsed.pop('internal_token', None)
return json.dumps(parsed)
Apply this pattern to models that may contain credentials, ensuring that even if a document is serialized, keys are omitted.
4. Secure connection strings and enforce field-level encryption
Store MongoDB URIs and any embedded credentials in environment variables or a secrets manager, and reference them in settings using os.getenv. If field-level encryption is required, encrypt values before persisting and decrypt only in controlled contexts:
from mongoengine import connect
import os
def get_db():
uri = os.getenv('MONGODB_URI')
return connect(host=uri)
Never log query dictionaries that include raw keys; sanitize log output to prevent accidental disclosure through application or infrastructure logs.
5. Audit and restrict endpoint exposure
Review all API endpoints for unnecessary data projection. Use explicit field selection and avoid returning entire documents. For example, instead of:
config = ServiceConfig.objects.get(name='payment')
return JsonResponse(config.to_mongo()) # Potentially exposes keys
Use selective projection:
config = ServiceConfig.objects.only('name', 'status').get(name='payment')
return JsonResponse({'name': config.name, 'status': config.status})
These practices reduce the attack surface and align with secure handling expectations defined in standards such as OWASP API Top 10 and complement continuous monitoring via tools like middleBrick’s scanning and dashboard for tracking risk posture over time.