Format String in Flask with Api Keys
Format String in Flask with Api Keys — how this specific combination creates or exposes the vulnerability
A format string vulnerability in Flask can become especially risky when API keys are handled through user-influenced strings. In Python, using the % operator or str.format with external input can cause unintended memory reads or writes. When an API key is included in a log message, debug endpoint, or error response that is built using these formatting techniques, an attacker can supply format specifiers such as %s, %x, or %n to manipulate the output.
For example, consider a Flask route that logs an API key for debugging:
from flask import Flask, request
app = Flask(__name__)
@app.route("/call")
def call_api():
api_key = request.args.get("api_key", "")
# Vulnerable: user input directly used in a format string
log_message = "Calling external service with api_key=%s" % api_key
app.logger.info(log_message)
return {"status": "ok"}
If an attacker sends ?api_key=%s %s %s, the log line may read arbitrary memory contents. If the same input is used with %n, it can write to memory, which in some contexts may lead to more severe outcomes. The key issue is that the API key becomes part of a format string controlled by the attacker, turning what should be an opaque credential into a potential data leak or manipulation vector.
Another scenario involves building error messages that include the API key for traceability:
@app.route("/data")
def get_data():
key = request.args.get("key", "")
try:
# Some operation that may fail
result = process(key)
except Exception as e:
# Vulnerable: including key in a user-controlled format string
error_msg = "Error processing key %s: %s" % (key, e)
return {"error": error_msg}, 500
return {"data": result}
Here, if key contains format specifiers, the exception handler can produce unexpected output, potentially revealing sensitive parts of the application state or environment. In an LLM/AI security context, such logs might be captured by downstream systems; an attacker could attempt prompt injection or output scanning if logs are ever reflected back to users. Although format string issues are not specific to LLMs, exposing API keys in logs increases the risk of credential leakage through unintended channels.
middleBrick scans for input validation and data exposure findings that can highlight patterns where secrets appear in logs or error messages. It also checks for unsafe consumption practices, such as directly embedding user input into formatted strings, which can amplify the impact of format string bugs.
Api Keys-Specific Remediation in Flask — concrete code fixes
To remediate format string risks when handling API keys in Flask, avoid using user input as a format string. Instead, use explicit concatenation, structured logging, or safe string formatting that does not interpret format specifiers from external data.
1. Use f-strings or str.format with fixed field names, and pass user data as arguments rather than as the format template itself:
from flask import Flask, request
app = Flask(__name__)
@app.route("/call")
def call_api():
api_key = request.args.get("api_key", "")
# Safe: api_key is data, not part of the format string
app.logger.info("Calling external service with api_key=%s", api_key)
return {"status": "ok"}
2. When constructing error messages, keep the API key as a separate argument and avoid composing it into a user-controlled format string:
@app.route("/data")
def get_data():
key = request.args.get("key", "")
try:
result = process(key)
except Exception as e:
# Safe: key is passed as data, not used as a format template
app.logger.error("Error processing key %s: %s", key, e)
return {"error": "processing failed"}, 500
return {"data": result}
3. For structured logging or JSON output, use a dictionary and serialize it explicitly to prevent accidental interpolation:
import json
from flask import Flask, request
app = Flask(__name__)
@app.route("/call")
def call_api():
api_key = request.args.get("api_key", "")
entry = {"event": "call_api", "api_key": api_key}
app.logger.info(json.dumps(entry))
return {"status": "ok"}
4. Enforce strict input validation by rejecting keys that contain suspicious patterns such as % in contexts where the key is later used in logging or error reporting. While this does not replace safe formatting, it reduces the likelihood of an attacker injecting format specifiers:
import re
from flask import Flask, request, abort
app = Flask(__name__)
@app.route("/call")
def call_api():
api_key = request.args.get("api_key", "")
if re.search(r"[%#]", api_key):
abort(400, "Invalid api_key")
app.logger.info("Calling external service with api_key=%s", api_key)
return {"status": "ok"}
These changes ensure that API keys are treated strictly as data. Tools like middleBrick can identify remaining risks through its input validation and data exposure checks, helping you verify that secrets are not inadvertently exposed through logs or error responses.