Pii Leakage in Flask with Api Keys
Pii Leakage in Flask with Api Keys — how this specific combination creates or exposes the vulnerability
In Flask applications, API keys are commonly passed via HTTP headers, query parameters, or cookies to authorize requests to downstream services or to protect internal endpoints. When API keys are handled without care, they can become an unintentional pathway for PII leakage. For example, a developer might log incoming requests including the API key header to aid debugging, inadvertently capturing user identities or session tokens alongside the key. If the log storage or error reporting pipeline is not properly restricted, these logs can expose PII and become a target for unauthorized access.
Another common pattern is reflecting the API key or user-supplied identifiers derived from it in HTTP responses or error messages. If an endpoint echoes back a user identifier or role to help with debugging and does not properly sanitize the output, an attacker could use a compromised API key to harvest PII through crafted requests. Additionally, Flask routes that accept an API key as a route variable or query parameter might include that value in URLs that are stored in server-side referrers, browser history, or third-party monitoring tools, expanding the surface for PII exposure.
The risk is compounded when API keys are used to gate sensitive operations that return personal data, such as user profiles or transaction histories. Without strict input validation and tight scoping, an attacker who obtains or guesses a valid API key might leverage it to traverse associations and access PII belonging to other users. Because API keys often act like long-lived credentials, their exposure can enable persistent unauthorized access to endpoints that disclose PII, making the combination a significant concern in API security assessments.
Api Keys-Specific Remediation in Flask — concrete code fixes
Remediation focuses on preventing API keys from appearing in logs, error messages, and URLs, and on ensuring they are treated as sensitive data that must never be reflected to the client. Below are concrete Flask patterns that reduce the risk of PII leakage when using API keys.
Secure header-based API key handling
Instead of passing API keys as query parameters, use HTTP headers and ensure they are not logged inadvertently. Configure Flask to filter sensitive headers from logs and avoid echoing them in responses.
from flask import Flask, request, jsonify
import logging
app = Flask(__name__)
# Configure logging to exclude sensitive headers
class RedactingFilter(logging.Filter):
def filter(self, record):
if hasattr(record, 'msg'):
record.msg = str(record.msg).replace(request.headers.get('X-API-Key', ''), '[REDACTED]')
return True
app.logger.addFilter(RedactingFilter())
@app.route("/data")
def get_data():
api_key = request.headers.get("X-API-Key")
if not api_key or api_key != "VALID_KEY":
app.logger.warning("Unauthorized access attempt")
return jsonify({"error": "Unauthorized"}), 401
# Process request without exposing the key
return jsonify({"data": "sensitive information"})
if __name__ == "__main__":
app.run()
Avoid query parameters and URL leakage
Using query parameters for API keys increases the risk that keys are stored in browser history, server logs, or referrer headers. Use POST or authenticated headers and ensure no PII is embedded in URLs.
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/profile", methods=["POST"])
def get_profile():
api_key = request.headers.get("X-API-Key")
if api_key != "SECRET":
return jsonify({"error": "Forbidden"}), 403
user_id = request.json.get("user_id")
# Ensure user_id is validated and never reflected as raw input
if not isinstance(user_id, int) or user_id <= 0:
return jsonify({"error": "Invalid user_id"}), 400
# Return only necessary data, avoiding any raw key usage
return jsonify({"profile": {"user_id": user_id, "name": "Jane Doe"}})
if __name__ == "__main__":
app.run()
Do not reflect API keys in responses or errors
Never include the API key or any derived identifiers in JSON responses or error payloads. Sanitize exceptions and avoid verbose debugging output in production.
from flask import Flask, request, jsonify
import traceback
app = Flask(__name__)
@app.errorhandler(Exception)
def handle_exception(e):
# Generic error response, no key or stack trace
return jsonify({"error": "Internal server error"}), 500
@app.route("/secure")
def secure():
api_key = request.headers.get("X-API-Key")
if api_key != "TOPSECRET":
app.logger.error("Invalid key used")
return jsonify({"error": "Unauthorized"}), 401
try:
# Business logic here
pass
except Exception:
app.logger.error(traceback.format_exc())
return jsonify({"error": "Internal server error"}), 500
return jsonify({"status": "ok"})
if __name__ == "__main__":
app.run()Related CWEs: dataExposure
| CWE ID | Name | Severity |
|---|---|---|
| CWE-200 | Exposure of Sensitive Information | HIGH |
| CWE-209 | Error Information Disclosure | MEDIUM |
| CWE-213 | Exposure of Sensitive Information Due to Incompatible Policies | HIGH |
| CWE-215 | Insertion of Sensitive Information Into Debugging Code | MEDIUM |
| CWE-312 | Cleartext Storage of Sensitive Information | HIGH |
| CWE-359 | Exposure of Private Personal Information (PII) | HIGH |
| CWE-522 | Insufficiently Protected Credentials | CRITICAL |
| CWE-532 | Insertion of Sensitive Information into Log File | MEDIUM |
| CWE-538 | Insertion of Sensitive Information into Externally-Accessible File | HIGH |
| CWE-540 | Inclusion of Sensitive Information in Source Code | HIGH |