Api Key Exposure in Adonisjs with Mssql
Api Key Exposure in Adonisjs with Mssql — how this specific combination creates or exposes the vulnerability
When AdonisJS applications connect to Microsoft SQL Server via mssql, improper handling of connection credentials and query construction can lead to API key exposure. The mssql package commonly reads configuration from environment variables or config files. If an attacker can read environment variables (for example, via a log injection or server-side template rendering bug), raw connection strings containing embedded API keys or secrets may be exposed.
Additionally, AdonisJS route handlers that build dynamic queries by concatenating user input into SQL strings can inadvertently expose API keys when error messages or debug output reveal query text or schema details. For instance, constructing a WHERE clause by directly interpolating request parameters into a T-SQL string can lead to SQL injection, which often results in verbose errors that expose internal connection details, including API keys used for external service authentication stored in columns or configuration.
Another vector specific to the mssql pool in AdonisJS is misconfigured connection pooling or shared pool references across requests. If API keys are embedded in the connection config object and that object is reused or logged improperly, keys can leak through application logs or error traces. Since AdonisJS typically loads config once at startup, any exposure of that config at runtime can expose the API keys to anyone who can trigger or view logs, debug endpoints, or server-side error pages.
Consider an AdonisJS route that builds a T-SQL query by concatenating a user-supplied identifier to select a row containing an API key column:
const { request } = use('request')
const userId = request.param('id')
const query = "SELECT api_key FROM services WHERE user_id = " + userId
const result = await MsSql.query(query)
If an attacker passes id=1; SELECT TOP 1 api_key FROM services--, the resulting query returns an API key, and if error handling exposes the full query or result set, the key is effectively leaked. The mssql driver will execute the injected statement, and any returned sensitive column values become exposed.
Environment-based exposure is also common. An AdonisJS config file might define:
// config/database.js
module.exports = {
connection: {
server: Env.get('DB_SERVER'),
user: Env.get('DB_USER'),
password: Env.get('DB_PASSWORD'),
options: {
encrypt: true,
trustServerCertificate: false
},
// API key for a third-party service stored in connection metadata
apiKey: Env.get('EXTERNAL_API_KEY')
}
}
module.exports = { ...connection, user: connection.user + ':' + connection.apiKey }
If the application logs the connection string or a developer accidentally exposes the config via a debug route, the API key is exposed. The mssql library does not inherently treat apiKey as sensitive, so developers must ensure such values are never serialized into logs, responses, or client-side code.
Mssql-Specific Remediation in Adonisjs — concrete code fixes
To prevent API key exposure when using mssql in AdonisJS, adopt parameterized queries, strict config management, and output sanitization. Never build T-SQL by concatenating strings, and avoid exposing configuration objects that contain sensitive values.
Use parameterized queries to prevent SQL injection and avoid exposing API keys through error messages. Instead of embedding values directly, use bound parameters:
const userId = request.param('id')
const result = await MsSql.query('SELECT api_key FROM services WHERE user_id = @userId', {
userId: { value: userId, type: 'Int' }
})
Isolate API keys from database configuration. Do not store service API keys in the same object used for database authentication. Use environment variables and a dedicated configuration module that does not concatenate keys into connection strings:
// config/services.js
module.exports = {
externalApi: {
key: Env.get('EXTERNAL_API_KEY'),
endpoint: Env.get('EXTERNAL_API_ENDPOINT')
}
}
// Use directly without merging into database config
const apiKey = services.externalApi.key
Sanitize outputs and handle errors safely. Ensure error handlers do not return raw query text or configuration details. Log only non-sensitive metadata, and scrub any response that might include API key values:
try {
const result = await MsSql.query('SELECT id, name FROM services WHERE user_id = @userId', {
userId: { value: request.param('id'), type: 'Int' }
})
return result.recordset
} catch (error) {
// Log error code only, never the query or bindings in plain text
Logger.error('mssql_query_failed', { code: error.code })
throw ApplicationException.invoke('unable to load services')
Use connection pooling securely. Ensure the mssql pool is configured with minimal privileges and that any external API keys are not embedded in pool-level metadata. Rotate keys regularly and avoid sharing the same key across multiple services or environments.
| Risk | Bad Practice Example | Secure Alternative |
|---|---|---|
| SQL Injection | Query built via string concatenation | Parameterized queries with bound values |
| Config Exposure | API key concatenated into connection user string | Separate service config, no key merging |
| Log Leakage | Logging full query with keys | Log error codes only, scrub outputs |