Api Key Exposure in Hanami with Mysql
Api Key Exposure in Hanami with Mysql — how this specific combination creates or exposes the vulnerability
Hanami is a Ruby web framework that encourages explicit configuration and environment-specific settings. When API keys—such as those for external services or databases—are stored in initializers or environment files and then referenced in Hanami apps that connect to a Mysql instance, exposure can occur through multiple channels. A common pattern is to set ENV["SERVICE_API_KEY"] in a file like config/secrets.rb or via dotenv files, and then use that key in a Mysql connection string or direct query. If these files are committed to version control, the keys become part of the repository history and are accessible to anyone with read access. Additionally, if the Hanami app exposes debug or error pages that display environment variables or configuration snippets, an attacker who triggers an error can observe the API key in context.
When combined with Mysql, the risk becomes more tangible if the API key is used as part of a database connection or as a credential for accessing Mysql-based services. For example, if a Hanami app uses a key to authenticate to a MySQL-replica or to a service that logs sensitive queries, that key might be inadvertently logged in Mysql general logs or error logs. Queries like SHOW PROCESSLIST or logs that capture connection metadata can reveal the key if the application code passes it as a literal string in query construction or in diagnostic output. Furthermore, if the Hanami app dynamically builds SQL using string interpolation with user input, an attacker might leverage injection techniques to extract configuration values that include API keys, especially when error messages expose stack traces containing the key.
Another vector involves the use of Mysql’s native logging features. If the Hanami application connects with a user that has the SUPER privilege or uses general logging, SQL statements—including those containing keys passed as parameters—can be written to the Mysql general log. An attacker who gains access to the file system or to the logging interface can retrieve these keys. Even if the Hanami app uses prepared statements, misconfigured logging at the database level can still expose sensitive strings that were intended to remain internal. Because Mysql does not inherently redact sensitive values from logs, the framework’s handling of credentials must explicitly avoid placing keys in query text or session variables that might be captured.
In secure architectures, API keys should be injected at runtime through secure environment mechanisms and never embedded in SQL strings or configuration files that may be logged. Hanami applications interacting with Mysql must ensure that any key used for service authentication is kept out of logs, error messages, and query text. This requires strict separation between configuration and code, and disciplined use of environment variables that are not exposed through application interfaces or database logs.
Mysql-Specific Remediation in Hanami — concrete code fixes
Remediation focuses on preventing API keys from appearing in SQL queries, logs, or error output when using Mysql with Hanami. The following practices and code examples illustrate secure handling.
- Use environment variables injected at runtime and reference them via
ENVwithout embedding in SQL strings. In Hanami, configure your database connection inconfig/application.rbor an initializer using secure lookup:
# config/initializers/database.rb
require 'mysql2'
client = Mysql2::Client.new(
host: ENV["MYSQL_HOST"],
username: ENV["MYSQL_USER"],
password: ENV["MYSQL_PASSWORD"],
database: ENV["MYSQL_DB"],
sslca: ENV["MYSQL_SSL_CA"]
)
# Do NOT interpolate API keys into queries
Hanami.configure do |config|
config.container.register(:mysql_client, client)
end
- Avoid constructing SQL with string interpolation that might include keys. Instead, use bound parameters for all user-supplied data. For example, if you need to store or query metadata that could be sensitive, keep keys out of SQL entirely and use secure storage references:
# Safe query pattern in a Hanami action
require 'mysql2'
class Reports::Fetch
def initialize(client)
@client = client
end
def call(user_id)
# Use placeholders, never interpolate
sql = "SELECT id, name FROM reports WHERE user_id = ?"
@client.query(sql, user_id)
end
end
# Usage
client = Mysql2::Client.new(
host: ENV["MYSQL_HOST"],
username: ENV["MYSQL_USER"],
password: ENV["MYSQL_PASSWORD"],
database: ENV["MYSQL_DB"]
)
report = Reports::Fetch.new(client).call(params[:id])
- Disable or secure Mysql logging features that could capture sensitive strings. For example, ensure general_log is disabled in production and avoid using
log_output=filewith sensitive connections. If logging is required, filter or rotate logs externally. In SQL, you can verify and disable general logging as follows:
-- Check if general log is enabled
SHOW VARIABLES LIKE 'general_log';
-- Disable general logging for sensitive sessions (requires SUPER privilege)
SET global general_log = 'OFF';
-- Alternatively, set the log output to a non-sensitive table if auditing is needed
-- (ensure appropriate access controls are in place)
SET global log_output = 'TABLE';
- Apply the principle of least privilege to the Mysql user used by Hanami. Do not grant SUPER or FILE privileges unless strictly necessary. Create a dedicated user with only the required permissions:
-- Example MySQL user setup for Hanami (run as admin)
CREATE USER 'hanami_app'@'localhost' IDENTIFIED BY 'StrongPassword123!';
GRANT SELECT, INSERT, UPDATE ON app_db.reports TO 'hanami_app'@'localhost';
FLUSH PRIVILEGES;
- Rotate API keys and credentials regularly, and ensure they are never printed in logs or error traces. In Hanami, avoid rescuing exceptions and printing ENV values. Instead, log only non-sensitive metadata:
# Safe error handling in a Hanami action
rescue Mysql2::Error => e
# Log only safe diagnostic info
Hanami.logger.error("DB error: #{e.message}")
# Do NOT log e.client or any object that may contain connection strings
render 'errors/internal', status: 500