Harden audit durability and diagnostic redaction

Persist fallback audit targets before use and fail upload starts or active-batch additions closed when the plan cannot be recorded. Keep lifecycle audits out of session and debug logs, expose diagnostics through opaque metadata, localize audit failures, and redact paths plus complete credential values without corrupting benign text.
This commit is contained in:
Sucukdeluxe
2026-08-13 20:34:26 +02:00
parent a98b63618d
commit e63214cae8
7 changed files with 372 additions and 77 deletions
+41 -9
View File
@@ -1,4 +1,5 @@
const nodePath = require('path');
const { formatUploadPlanLogLine } = require('./upload-log');
function getUploadAuditLogPath(uploadLogPath, pathApi = nodePath) {
if (typeof uploadLogPath !== 'string' || !uploadLogPath.trim()) return null;
@@ -12,7 +13,7 @@ function createUploadAuditWriter(options) {
const resolveUploadLogTarget = source.resolveUploadLogTarget;
const rotateLogFile = typeof source.rotateLogFile === 'function' ? source.rotateLogFile : () => {};
const invalidateUploadLogTarget = typeof source.invalidateUploadLogTarget === 'function' ? source.invalidateUploadLogTarget : () => {};
const persistFallbackLogPath = typeof source.persistFallbackLogPath === 'function' ? source.persistFallbackLogPath : async () => {};
const persistFallbackLogPath = typeof source.persistFallbackLogPath === 'function' ? source.persistFallbackLogPath : async () => false;
const reportError = typeof source.reportError === 'function' ? source.reportError : () => {};
const retryDelays = Array.isArray(source.retryDelays) && source.retryDelays.length > 0 ? source.retryDelays : [0, 100, 250];
const maxBytes = Number.isFinite(source.maxBytes) ? source.maxBytes : 10 * 1024 * 1024;
@@ -24,27 +25,35 @@ function createUploadAuditWriter(options) {
}
async function append(line, label) {
let excludedPath = null;
const excludedPaths = new Set();
for (const delay of retryDelays) {
if (delay) await new Promise(resolve => setTimeout(resolve, delay));
const uploadTarget = resolveUploadLogTarget(excludedPath);
const uploadTarget = resolveUploadLogTarget(excludedPaths);
if (!uploadTarget || excludedPaths.has(uploadTarget.path)) continue;
const targetPath = uploadTarget && getUploadAuditLogPath(uploadTarget.path, path);
if (!targetPath) continue;
try {
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
rotateLogFile(targetPath, maxBytes, maxBackups);
await fs.promises.appendFile(targetPath, line, 'utf-8');
activePath = targetPath;
if (uploadTarget.isFallback) {
let persisted = false;
try {
await persistFallbackLogPath(uploadTarget.path);
persisted = await persistFallbackLogPath(uploadTarget.path);
} catch (error) {
reportError('audit-fallback-persist', error);
}
if (persisted !== true) {
excludedPaths.add(uploadTarget.path);
invalidateUploadLogTarget();
reportError('audit-fallback-persist', new Error('Fallback log path could not be persisted'));
continue;
}
}
rotateLogFile(targetPath, maxBytes, maxBackups);
await fs.promises.appendFile(targetPath, line, 'utf-8');
activePath = targetPath;
return true;
} catch (error) {
excludedPath = uploadTarget.path;
excludedPaths.add(uploadTarget.path);
invalidateUploadLogTarget();
reportError(label, error);
}
@@ -55,4 +64,27 @@ function createUploadAuditWriter(options) {
return { append, getActivePath: () => activePath };
}
module.exports = { getUploadAuditLogPath, createUploadAuditWriter };
function createUploadAuditEvents(writer, now = () => new Date()) {
if (!writer || typeof writer.append !== 'function') throw new TypeError('createUploadAuditEvents requires an audit writer');
return {
appendSourceCleanup: event => writer.append(`# SOURCE-CLEANUP ${JSON.stringify(event)}\r\n`, 'source-cleanup'),
appendUploadPlan: (plan, mode) => writer.append(formatUploadPlanLogLine(now(), plan, mode), 'upload-plan')
};
}
async function runAfterDurableAudit(audit, action) {
let persisted = false;
try {
persisted = await audit();
} catch {}
if (persisted !== true) return { ok: false };
return { ok: true, value: await action() };
}
function getUploadAuditFailureMessage(language) {
return language === 'de'
? 'Der Uploadplan konnte nicht dauerhaft protokolliert werden. Bitte prüfe den Log-Pfad und versuche es erneut.'
: 'The upload plan could not be recorded durably. Check the log path and try again.';
}
module.exports = { getUploadAuditLogPath, createUploadAuditWriter, createUploadAuditEvents, runAfterDurableAudit, getUploadAuditFailureMessage };