fix: remediate audited defects (resolves #29, #30, #31, #32)
Build and Push Docker Image / build (push) Successful in 51s
Licence Check / Licence compatibility and copyright header verification (push) Failing after 1m48s
CI / Security audit (push) Successful in 2m27s
CI / Swagger Validation & Coverage (push) Successful in 2m27s
CI / Tests & coverage (push) Successful in 2m39s

- Fix permanent qBittorrent fallback degradation by resetting fallback flags during group-by polling (resolves #29)
- Fix Ombi webhook key mismatch in status endpoint and test mocks (resolves #30)
- Prevent timingSafeEqual TypeErrors on multi-byte CSRF token length mismatches (resolves #31)
- Eliminate duplicate write stream on server.log by delegating to index.js console override (resolves #32)
This commit is contained in:
2026-05-22 16:01:20 +01:00
parent 4aa3590017
commit 548aca6bee
7 changed files with 26 additions and 18 deletions
+5 -4
View File
@@ -26,13 +26,14 @@ function verifyCsrf(req, res, next) {
return res.status(403).json({ error: 'CSRF token missing' });
}
// Constant-time comparison to prevent timing attacks
if (cookieToken.length !== headerToken.length) {
const a = Buffer.from(cookieToken);
const b = Buffer.from(headerToken);
// Constant-time comparison of underlying buffer lengths to prevent timing attacks
if (a.length !== b.length) {
return res.status(403).json({ error: 'CSRF token invalid' });
}
const a = Buffer.from(cookieToken);
const b = Buffer.from(headerToken);
if (!require('crypto').timingSafeEqual(a, b)) {
return res.status(403).json({ error: 'CSRF token invalid' });
}
+2 -2
View File
@@ -392,9 +392,9 @@ router.get('/webhook/status', requireAuth, async (req, res) => {
requestProcessing: webhookConfig.enabled || false
},
stats: metrics ? {
eventsReceived: metrics.eventCount || 0,
eventsReceived: metrics.eventsReceived || 0,
pollsSkipped: metrics.pollsSkipped || 0,
lastWebhookTimestamp: metrics.lastEventTimestamp || null
lastWebhookTimestamp: metrics.lastWebhookTimestamp || null
} : null
});
} catch (error) {
+5
View File
@@ -156,6 +156,11 @@ class DownloadClientRegistry {
result[type] = [];
}
// Reset fallback flags for qBittorrent clients
if (client.resetFallbackFlag) {
client.resetFallbackFlag();
}
try {
const downloads = await client.getActiveDownloads();
result[type].push(...downloads);
+1 -10
View File
@@ -1,16 +1,7 @@
// Copyright (c) 2026 Gordon Bolton. MIT License.
const fs = require('fs');
const path = require('path');
// Use DATA_DIR so the non-root container user (UID 1000) can write logs.
// Falls back to ../../data/server.log (same directory index.js uses).
const DATA_DIR = process.env.DATA_DIR || path.join(__dirname, '../../data');
if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR, { recursive: true });
const logFile = fs.createWriteStream(path.join(DATA_DIR, 'server.log'), { flags: 'a' });
function logToFile(message) {
logFile.write(`[${new Date().toISOString()}] ${message}\n`);
console.log(message);
}
module.exports = {