Cors Wildcard in Flask with Api Keys
Cors Wildcard in Flask with Api Keys — how this specific combination creates or exposes the vulnerability
A CORS wildcard (Access-Control-Allow-Origin: *) combined with API key authentication in a Flask application can unintentionally expose protected endpoints to unauthorized origins. When Flask is configured to respond with a wildcard CORS header while also validating API keys, the security boundary between public and privileged access can blur in practice.
Consider a Flask route that requires an API key passed via a custom header, such as X-API-Key. If the CORS configuration sets Access-Control-Allow-Origin: *, browsers allow any web page to make cross-origin requests to that route and include the API key header. The server-side validation of the API key still occurs, but the endpoint is now reachable from any site, widening the potential attack surface.
This configuration can lead to unintended access in scenarios where the API key is embedded in client-side code or leaked to browsers. A malicious site can initiate authenticated requests on behalf of a user or service, especially if the API key has broad permissions. Even though the API key itself is not sent automatically by browsers in a same-origin request without explicit scripting, a CORS wildcard enables external pages to include the key programmatically via JavaScript.
Furthermore, if preflight requests are handled permissively, OPTIONS requests with the wildcard can succeed for any origin, allowing attackers to probe endpoints and enumerate supported methods. This behavior can expose route structures and authentication mechanisms without requiring direct access to the API documentation. In combination, these factors create a scenario where an otherwise protected resource becomes reachable from untrusted origins, violating the principle of least privilege.
Real-world parallels can be found in API abuse cases involving misconfigured CORS and exposed credentials, such as incidents referenced in common vulnerability databases where overly permissive CORS rules led to unauthorized data access. While the API key mechanism remains intact, the permissive CORS policy undermines its isolation.
Api Keys-Specific Remediation in Flask — concrete code fixes
Remediation centers on restricting CORS origins and ensuring API keys are validated in a way that does not rely on browser-enforced isolation alone. Below are concrete, secure configurations for Flask using the flask-cors package and manual header checks.
1. Restrict CORS origins explicitly
Instead of using a wildcard, specify trusted origins that are allowed to access the API. This prevents arbitrary websites from making authenticated requests.
from flask import Flask, request, jsonify
from flask_cors import CORS
app = Flask(__name__)
# Allow only specific trusted origins
CORS(app, resources={r"/api/*": {"origins": ["https://trusted.example.com", "https://app.example.com"]}})
API_KEYS = {"example-key-123": "service-a", "another-key-456": "service-b"}
@app.route('/api/data')
def get_data():
api_key = request.headers.get('X-API-Key')
if not api_key or api_key not in API_KEYS:
return jsonify({"error": "Unauthorized"}), 401
return jsonify({"data": "secure information"})
if __name__ == '__main__':
app.run()
2. Validate API keys with a middleware layer
Implement a before-request handler to enforce authentication before routing, ensuring all endpoints are protected consistently.
from flask import Flask, request, jsonify
app = Flask(__name__)
# Allowed API keys stored securely (e.g., hashed in production)
API_KEYS = {"example-key-123": "service-a"}
@app.before_request
def authenticate():
if request.endpoint and not request.endpoint.startswith('static'):
api_key = request.headers.get('X-API-Key')
if api_key not in API_KEYS:
return jsonify({"error": "Invalid or missing API key"}), 401
@app.route('/api/endpoint')
def protected_route():
return jsonify({"status": "access granted"})
if __name__ == '__main__':
app.run()
3. Use environment-based CORS configuration
Ensure CORS rules differ between development and production. Avoid enabling wildcards in production environments.
import os
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
if os.getenv('FLASK_ENV') == 'production':
CORS(app, resources={r"/api/*": {"origins": ["https://api.example.com"]}})
else:
CORS(app) # More permissive in development, but not wildcard in production
4. Combine with rate limiting and monitoring
Even with correct CORS and API key usage, adding rate limiting helps mitigate abuse. Complement this with logging for suspicious patterns.
from flask import Flask, request, jsonify
from flask_limiter import Limiter
app = Flask(__name__)
limiter = Limiter(app, key_func=lambda: request.headers.get('X-API-Key', 'anonymous'))
@app.route('/api/action', methods=['POST'])
@limiter.limit("10/minute")
def perform_action():
api_key = request.headers.get('X-API-Key')
if not api_key or api_key not in ["valid-key"]:
return jsonify({"error": "Unauthorized"}), 401
return jsonify({"result": "ok"})
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 |