Fix 16 HIGH/MEDIUM security bugs across API

HIGH fixes:
- TOTP disable now requires valid code verification
- TOTP secret removed from plaintext file storage
- Container ID validated before update/check-update/logs operations
- DNS server parameter restricted to configured servers (SSRF prevention)
- Backup export no longer includes encryption key
- Backup restore of sensitive files requires TOTP re-authentication

MEDIUM fixes:
- Session cookie Secure flag added
- Caddy reload errors no longer leaked to client
- saveConfig uses atomic locked updates via configStateManager
- Log file path traversal prevented via symlink resolution
- Credential cache entries now expire after 5 minutes
- _httpFetch enforces 10MB response size limit
- External URL path injection into Caddyfile blocked
- Custom volume host paths validated against allowed roots
- Error logs endpoint no longer returns stack traces
- Logo delete path traversal prevented via path.basename()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-07 00:15:28 -08:00
parent 6979302fb7
commit 59b6d7d360
12 changed files with 172 additions and 69 deletions

View File

@@ -129,7 +129,22 @@ module.exports = function(ctx) {
const parts = vol.split(':');
const containerPath = parts.slice(1).join(':');
const override = config.customVolumes.find(cv => cv.containerPath === containerPath);
if (override && override.hostPath) return `${toDockerDesktopPath(override.hostPath)}:${containerPath}`;
if (override && override.hostPath) {
// Validate host path is under allowed roots (docker data dir or media paths)
const normalizedHost = path.resolve(override.hostPath);
const allowedRoots = [path.resolve(platformPaths.dockerData)];
if (config.mediaPath) {
config.mediaPath.split(',').map(p => p.trim()).filter(Boolean).forEach(p => allowedRoots.push(path.resolve(p)));
}
const isAllowed = allowedRoots.some(root =>
normalizedHost === root || normalizedHost.startsWith(root + path.sep)
);
if (!isAllowed) {
ctx.log.warn('deploy', 'Custom volume host path rejected', { hostPath: override.hostPath, allowed: allowedRoots });
return vol; // Keep original volume, don't apply unsafe override
}
return `${toDockerDesktopPath(override.hostPath)}:${containerPath}`;
}
return vol;
});
}