SCRAPE-23: Sanitize error messages in scraper_runs (#27)

Co-authored-by: Stephen Minakian <stephenminakian@gmail.com>
Co-committed-by: Stephen Minakian <stephenminakian@gmail.com>
This commit is contained in:
2026-02-06 23:44:02 -07:00
committed by stephen
parent e654d59fea
commit e70f7429ea
2 changed files with 273 additions and 4 deletions

View File

@ -651,6 +651,54 @@ async function getYesterdayUnitCodes(db, today) {
return new Set(yesterdayRecords.map(r => r.unit_code));
}
// ============================================================
// Error Sanitization
// ============================================================
/**
* Sanitize an error object to remove sensitive information before storage.
* Removes file paths, connection strings, and credential patterns while
* preserving the error type and a useful general description for debugging.
*
* @param {Error} error - Error object to sanitize
* @returns {Object} Sanitized error with name, message, and optionally stack
*/
function sanitizeError(error) {
const sanitized = {
name: error.name || 'Error',
message: sanitizeMessage(error.message || ''),
};
if (error.stack) {
sanitized.stack = sanitizeMessage(error.stack);
}
return sanitized;
}
/**
* Sanitize a string message by removing sensitive patterns.
* @param {string} message - Raw error message
* @returns {string} Sanitized message
*/
function sanitizeMessage(message) {
let result = message;
// Redact MongoDB connection strings (mongodb:// and mongodb+srv://)
result = result.replace(/mongodb(\+srv)?:\/\/[^\s,;)}\]'"]+/gi, '[REDACTED_CONNECTION_STRING]');
// Redact credential/secret patterns: KEY=value, password=value, token=value, etc.
result = result.replace(/\b(api[_-]?key|secret[_-]?key|secret[_-]?token|token|password|passwd|authorization|credential)\s*=\s*\S+/gi, '$1=[REDACTED]');
// Remove Unix absolute paths (/home/..., /var/..., /tmp/..., /usr/..., /etc/..., /opt/...)
result = result.replace(/\/(?:home|var|tmp|usr|etc|opt)\/[^\s:,;)}\]'"]+/g, '[PATH]');
// Remove Windows-style absolute paths (C:\..., D:\...)
result = result.replace(/[A-Z]:\\[^\s:,;)}\]'"]+/gi, '[PATH]');
return result;
}
// ============================================================
// Scraper Run History
// ============================================================
@ -787,12 +835,13 @@ async function runScrape(db, options = {}) {
result.status = 'success';
} catch (error) {
const cleanError = sanitizeError(error);
logger.error('Scrape failed', {
errorType: error.name,
errorMessage: error.message
errorType: cleanError.name,
errorMessage: cleanError.message
});
result.status = 'failed';
result.errors.push(error.message);
result.errors.push(cleanError.message);
} finally {
result.completedAt = new Date().toISOString();
@ -834,5 +883,6 @@ module.exports = {
parseFloatValue,
trimString,
isRetryableError,
sleep
sleep,
sanitizeError
};