Api Key Exposure in Restify with Mysql
Api Key Exposure in Restify with Mysql — how this specific combination creates or exposes the vulnerability
When a Restify API service handles authentication via API keys and interacts with a Mysql database, several specific conditions can unintentionally expose those keys. This typically occurs when application code logs or returns full database rows that contain columns used to store credentials or secrets. For example, a developer might store an api_key in a Mysql table and later serialize an entire row to a JSON response or write it to logs for debugging.
In Restify, routes that query Mysql without strict field selection can return sensitive columns to clients or to logging middleware. Consider a route that fetches a user profile by ID and returns the result directly:
server.get('/profile/:id', (req, res, next) => {
const query = 'SELECT * FROM users WHERE id = ?';
conn.query(query, [req.params.id], (err, rows) => {
if (err) return next(err);
res.send(rows[0]);
});
});
If the users Mysql table includes a column named api_key, this endpoint will expose it to any authenticated or unauthenticated caller depending on route configuration. This becomes a critical exposure when combined with insufficient access controls, since the API key is effectively returned in clear text within the HTTP response.
Another vector involves logging. Many Restify applications use request or response logging plugins that capture payloads. If a Mysql query result containing an api_key is logged at any level (application, database driver, or infrastructure), the key can be persisted in log stores or exposed through log aggregation interfaces. For instance:
server.on('after', (req, res, route, err) => {
server.log.info(`${req.method} ${req.url} -> ${JSON.stringify(res._data)}`);
});
Here, res._data might include a Mysql row with an api_key. Even if the response is correct for the client, the log now holds a credential that could be accessed by unauthorized parties with log read permissions.
Additionally, error handling paths can inadvertently disclose keys. If a Mysql error leaks internal query details or row contents, and those details are returned in an error response, the api_key may appear in stack traces or error messages returned to the caller. Proper separation of internal database structures from external responses is essential to prevent this combination from creating an exposure path.
Mysql-Specific Remediation in Restify — concrete code fixes
Remediation focuses on ensuring that sensitive columns from Mysql are never included in responses or logs, and that queries follow the principle of least privilege both in SQL and in application code.
- Explicitly select only required fields in Mysql queries, omitting sensitive columns:
server.get('/profile/:id', (req, res, next) => {
const query = 'SELECT id, name, email FROM users WHERE id = ?';
conn.query(query, [req.params.id], (err, rows) => {
if (err) return next(err);
res.send(rows[0]);
});
});
- Use parameterized queries to avoid injection and ensure precise control over returned data:
server.post('/search', (req, res, next) => {
const { q } = req.body;
const sql = 'SELECT id, display_name FROM items WHERE category = ? AND active = 1';
conn.query(sql, [q], (err, rows) => {
if (err) return next(err);
res.send(rows);
});
});
- Sanitize logs by excluding sensitive fields before serialization:
server.on('after', (req, res, route, err) => {
const safeData = { method: req.method, url: req.url, statusCode: res.statusCode };
server.log.info(JSON.stringify(safeData));
});
- Isolate credentials using environment variables and configuration that never reaches Mysql responses:
// server setup
const apiKey = process.env.API_KEY;
// Use apiKey for outbound services, never select it from Mysql
server.get('/external', (req, res, next) => {
// call external service with apiKey, but do not involve Mysql in this flow
res.send({ status: 'ok' });
});
- Apply the principle of least privilege to the Mysql user used by Restify:
-- Create a user with only necessary permissions
CREATE USER 'restify_app'@'%' IDENTIFIED BY 'strong_password';
GRANT SELECT (id, name, email) ON app_db.users TO 'restify_app'@'%';
REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'restify_app'@'%';
These measures ensure that even if an endpoint queries a Mysql table that contains sensitive columns, those columns are never surfaced in API responses or logs, reducing the risk of accidental key exposure through the Restify application and its database interactions.