HIGH api key exposureflaskmongodb

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.

Frequently Asked Questions

Can middleware or logging in Flask accidentally expose API keys stored in MongoDB?
Yes. If Flask logs incoming requests or MongoDB driver logs contain the full query documents, API keys inserted into documents can appear in logs. Avoid logging raw key values and filter sensitive fields before serialization.
Is it safe to return MongoDB document IDs in error messages when working with Flask?
Document IDs themselves are often not secret, but returning full documents or IDs in detailed error messages can aid reconnaissance. Use generic error messages and avoid echoing database IDs or keys in responses.