Files
BetterDesk/web-nodejs/lib/logger.js
T
UNITRONIX 648a8d4f8a security(3.4): harden console and Go server for pre-release audit
Commit web-nodejs lockfile with tar override; CI uses npm ci and moderate npm audit.
Add LOG_LEVEL filtering and log redaction in Node console and Go server.
Validate WS tokens on bd-signal; require single-use token for remote-agent relay.
Limit active relay sessions per IP and block open enrollment without TLS.
2026-07-12 20:44:46 +02:00

82 lines
2.6 KiB
JavaScript

'use strict';
/**
* BetterDesk Console — structured console logger with level gating and redaction.
*
* LOG_LEVEL: error | warn | info | debug (default: warn in production, info otherwise)
*/
const config = require('../config/config');
const { sanitizeLogValue, redactUsernameForLog } = require('./logRedact');
const LEVEL_RANK = { error: 0, warn: 1, info: 2, debug: 3 };
function resolveLogLevel() {
const raw = (process.env.LOG_LEVEL || '').trim().toLowerCase();
if (raw && Object.prototype.hasOwnProperty.call(LEVEL_RANK, raw)) {
return raw;
}
return config.isProduction ? 'warn' : 'info';
}
const activeLevel = resolveLogLevel();
const activeRank = LEVEL_RANK[activeLevel];
function formatPart(part) {
const sanitized = sanitizeLogValue(part);
if (typeof sanitized !== 'string') return sanitized;
// Mask quoted usernames in common auth log patterns: for 'admin', user 'admin'
return sanitized
.replace(/(?:for|user|account|credentials for|local user|synced)\s+'([^']+)'/gi, (m, user) => {
return m.replace(user, redactUsernameForLog(user));
})
.replace(/'([^']{2,64})'/g, (m, inner) => {
// Heuristic: skip obvious non-usernames (paths, env flags)
if (/[/\\.:]|^https?:|^BETTERDESK_|^LDAP\+|^Go server|^PBKDF2|^bcrypt/i.test(inner)) {
return m;
}
if (/^(admin|operator|viewer|user|role|provider|hash type|empty|invalid)$/i.test(inner)) {
return m;
}
if (/^[a-zA-Z0-9._@-]+$/.test(inner) && inner.length >= 2) {
return `'${redactUsernameForLog(inner)}'`;
}
return m;
});
}
function formatArgs(args) {
return args.map(formatPart);
}
function write(level, prefix, args) {
if (LEVEL_RANK[level] > activeRank) return;
const line = prefix ? [`[${prefix}]`, ...formatArgs(args)] : formatArgs(args);
if (level === 'error') {
console.error(...line);
} else if (level === 'warn') {
console.warn(...line);
} else {
console.log(...line);
}
}
function child(prefix) {
return {
error: (...args) => write('error', prefix, args),
warn: (...args) => write('warn', prefix, args),
info: (...args) => write('info', prefix, args),
debug: (...args) => write('debug', prefix, args),
};
}
module.exports = {
level: activeLevel,
error: (...args) => write('error', null, args),
warn: (...args) => write('warn', null, args),
info: (...args) => write('info', null, args),
debug: (...args) => write('debug', null, args),
child,
};