Api Key Exposure in Flask with Mongodb
Api Key Exposure in Flask with Mongodb — how this specific combination creates or exposes the vulnerability
When an API built with Flask interacts with a MongoDB backend, improper handling of sensitive values such as API keys can lead to exposure through multiple vectors. A common pattern is storing configuration values, including API keys, in environment variables and reading them in Flask via os.environ. If these values are accidentally included in logs, error messages, or responses, or if they are embedded in MongoDB documents without appropriate protection, they can be exposed to unauthorized parties.
In a Flask application using PyMongo, developers sometimes pass API keys through query parameters or request bodies for convenience. For example, an endpoint that accepts a key to delegate access might look like this:
from flask import Flask, request, jsonify
from pymongo import MongoClient
import os
app = Flask(__name__)
client = MongoClient(os.getenv('MONGO_URI'))
db = client.get_database('appdb')
@app.route('/lookup')
def lookup():
api_key = request.args.get('key')
# Intentionally risky: storing or logging the key without care
db.keys.insert_one({'name': 'external', 'key': api_key})
return jsonify({'status': 'ok'})
In this pattern, the API key is taken directly from the query string and written into a MongoDB collection. This creates several issues: the key is reflected in the database in clear text, it may be included in application logs if the insert operation is logged, and it can be inadvertently returned in error traces or debug output. An attacker who gains read access to the database, or who can influence logging and error reporting, can recover the key.
Additionally, if the Flask app serializes MongoDB documents into JSON responses or error payloads, the key can leak through those channels. For instance, returning a document retrieved from MongoDB without filtering sensitive fields can expose the key to any client that can trigger the endpoint or view logs:
from flask import abort
def get_key_record(key_id):
doc = db.keys.find_one({'_id': key_id})
if doc is None:
abort(404)
# Risk: returning the full document may include the key field
return jsonify(doc)
Moreover, if the MongoDB connection string (MONGO_URI) itself contains credentials and is constructed incorrectly, or if the Flask app exposes internal configuration endpoints, the combination of Flask routes and MongoDB data stores can amplify the impact of a single exposed API key. Because the scan category Data Exposure checks for sensitive values reflected in responses and stored insecurely, this specific combination is likely to surface findings related to improper storage and transmission of secrets.
Mongodb-Specific Remediation in Flask — concrete code fixes
To reduce the risk of API key exposure when using Flask with MongoDB, apply targeted controls around storage, logging, and data handling. The goal is to avoid persisting raw keys in the database, to prevent keys from appearing in logs or responses, and to enforce strict validation on incoming values.
1) Do not store raw API keys in MongoDB. If you must store references, store only hashes or opaque tokens. For example, if you need to associate a user with a key, store a hashed version or a generated token:
import hashlib
import uuid
from flask import jsonify
def store_key_reference(owner_id, raw_key):
# Store a hash or a token instead of the raw key
token = str(uuid.uuid4())
key_hash = hashlib.sha256(raw_key.encode()).hexdigest()
db.keys.insert_one({
'owner': owner_id,
'token': token,
'key_hash': key_hash
})
return token
2) Filter sensitive fields before serialization. When returning documents from MongoDB, explicitly select only the fields you need and exclude fields like 'key' or 'secret':
def get_public_key_info(key_id):
doc = db.keys.find_one({'_id': key_id}, {'token': 1, 'created_at': 1, '_id': 1})
if doc is None:
abort(404)
# Exclude 'key_hash' or any sensitive field from the response
safe_doc = {k: doc[k] for k in doc if k in ('token', 'created_at')}
return jsonify(safe_doc)
3) Validate and restrict the source of keys. Instead of accepting keys directly from query parameters, use authenticated routes or secure headers, and validate format to avoid injection or logging issues:
from flask import request, g
def require_api_key():
auth = request.headers.get('X-API-Key')
if not auth or not validate_key_format(auth):
abort(401)
g.api_key = auth
def validate_key_format(key):
# Example format check
return isinstance(key, str) and 32 <= len(key) <= 64
4) Harden your MongoDB connection and configuration. Use environment variables for connection strings and avoid logging the full URI or credentials. Configure PyMongo with appropriate options to minimize verbose logging:
import pymongo
from pymongo.monitoring import CommandListener
class SafeCommandListener(CommandListener):
def started(self, event):
# Avoid logging full command payloads that may contain keys
pass
def succeeded(self, event):
pass
client = pymongo.MongoClient(
os.getenv('MONGO_URI'),
monitor_commands=False
)
These changes align with the scan categories Authentication, Data Exposure, and Input Validation, helping ensure that API keys are handled securely end to end when Flask and MongoDB are used together.