Log Injection in Flask with Api Keys
Log Injection in Flask with Api Keys — how this specific combination creates or exposes the vulnerability
Log injection in Flask applications that rely on API keys occurs when attacker-controlled data is written directly into application logs without proper sanitization. In this context, the API key itself becomes both a trust signal and a potential source of malicious log content. If a Flask endpoint accepts an API key — for example via an HTTP header such as X-API-Key — and includes the key or parts of it in log messages, an attacker can supply a crafted key containing newline characters or other control sequences. Because many logging frameworks treat newline characters as record delimiters, injected log lines can appear as separate, authoritative entries, which may lead to log forging, log poisoning, or log-based security decisions being bypassed.
Consider a typical Flask route that authenticates requests using an API key and logs the key for debugging:
from flask import Flask, request
app = Flask(__name__)
@app.route("/data")
def get_data():
api_key = request.headers.get("X-API-Key", "")
# Risky: directly logging the API key value
app.logger.info(f"API key used: {api_key}")
if api_key != "super-secret-key":
app.logger.warning("Invalid API key")
return {"error": "unauthorized"}, 401
return {"data": "sensitive"}
If an attacker sends a request with X-API-Key: legitimate-key\nAuthentication: Basic dXNlcjpwYXNz, the log entry can split into multiple lines, making it appear as though a separate authentication event occurred. This is a form of log injection that can obscure the true source of the request or trigger incorrect alerting logic. In more complex flows, log injection can combine with insecure handling of API keys — such as storing them in plaintext or exposing them in error messages — to amplify the impact. For example, an API key with embedded JSON or script-like content may be reflected in log aggregation dashboards, increasing the risk of cross-log confusion or misinterpretation by security monitoring tools.
The issue is not unique to API keys; it is a specific manifestation of a broader class of injection problems in structured logs. Because API keys often carry high value, they are attractive inputs for injection attempts, and logs containing them are frequently retained for compliance and forensic analysis. Without canonicalization and strict validation, logs become an untrusted data source, which can undermine incident response and compliance evidence.
Api Keys-Specific Remediation in Flask — concrete code fixes
To remediate log injection risks in Flask when handling API keys, you must sanitize inputs before logging and avoid including raw key material in log entries. Instead of logging the API key itself, log a stable, non-sensitive identifier such as a key ID or a hash. If you must record the key for traceability, ensure it is escaped and treated as a single line. Below are concrete, secure patterns for Flask applications.
1. Use a key ID instead of the raw key
Maintain a server-side mapping from key ID to key material (stored securely, not in logs). Log only the key ID:
import hashlib
from flask import Flask, request
app = Flask(__name__)
# Example lookup (in practice, use a secure store)
VALID_KEYS = {
"key_id_abc": "super-secret-key",
}
def verify_key(key: str) -> str | None:
for key_id, stored in VALID_KEYS.items():
if stored == key:
return key_id
return None
@app.route("/data")
def get_data():
api_key = request.headers.get("X-API-Key", "")
key_id = verify_key(api_key)
if key_id is None:
app.logger.warning("Invalid API key attempt")
return {"error": "unauthorized"}, 401
# Safe: logging only a non-sensitive identifier
app.logger.info(f"Authenticated request with key_id: {key_id}")
return {"data": "sensitive"}
2. Sanitize and escape log input
If you must log the key, replace newlines and control characters with safe placeholders and ensure the message fits a single log line:
import re
from flask import Flask, request
app = Flask(__name__)
SAFE_KEY_RE = re.compile(r"[^\x20-\x7E]") # printable ASCII range
def safe_log_key(key: str) -> str:
# Replace non-printable characters and newlines
return SAFE_KEY_RE.sub("_", key.strip())
@app.route("/data")
def get_data():
api_key = request.headers.get("X-API-Key", "")
safe_key = safe_log_key(api_key)
app.logger.info(f"API key used: {safe_key}")
if api_key != "super-secret-key":
app.logger.warning("Invalid API key")
return {"error": "unauthorized"}, 401
return {"data": "sensitive"}
3. Structured logging with escaping
When using structured logging (e.g., JSON), ensure values are properly serialized and newlines are handled by the library. Do not manually concatenate log messages:
import json
from flask import Flask, request
app = Flask(__name__)
@app.route("/data")
def get_data():
api_key = request.headers.get("X-API-Key", "")
# Use a JSON-serializable dict; proper serializers escape control chars
app.logger.info(json.dumps({
"event": "api_request",
"key_truncated": api_key[:8] + "****" if api_key else "",
"status": "received"
}))
if api_key != "super-secret-key":
app.logger.warning(json.dumps({"event": "invalid_key"}))
return {"error": "unauthorized"}, 401
return {"data": "sensitive"}
These practices reduce the risk that API keys introduce log injection while preserving the ability to audit and investigate requests. They align with secure handling principles for high-value secrets and help ensure logs remain a reliable source of observability rather than an attack surface.