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:
@@ -15,7 +15,8 @@ const CREDENTIALS_FILE = process.env.CREDENTIALS_FILE || path.join(__dirname, 'c
|
||||
class CredentialManager {
|
||||
constructor() {
|
||||
this.useKeychain = keychainManager.available;
|
||||
this.cache = new Map(); // In-memory cache for performance
|
||||
this.cache = new Map(); // In-memory cache with TTL
|
||||
this.CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
||||
this.lockOptions = {
|
||||
retries: { retries: 10, minTimeout: 100, maxTimeout: 300 },
|
||||
stale: 30000
|
||||
@@ -47,7 +48,7 @@ class CredentialManager {
|
||||
if (success) {
|
||||
// Store metadata separately in file
|
||||
await this.storeMetadata(key, metadata);
|
||||
this.cache.set(key, value);
|
||||
this.cache.set(key, { value, exp: Date.now() + this.CACHE_TTL_MS });
|
||||
console.log(`[CredentialManager] Stored '${key}' in OS keychain`);
|
||||
return true;
|
||||
}
|
||||
@@ -56,7 +57,7 @@ class CredentialManager {
|
||||
|
||||
// Fallback to encrypted file storage
|
||||
await this.storeInFile(key, value, metadata);
|
||||
this.cache.set(key, value);
|
||||
this.cache.set(key, { value, exp: Date.now() + this.CACHE_TTL_MS });
|
||||
console.log(`[CredentialManager] Stored '${key}' in encrypted file`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
@@ -72,16 +73,20 @@ class CredentialManager {
|
||||
*/
|
||||
async retrieve(key) {
|
||||
try {
|
||||
// Check cache first
|
||||
// Check cache first (with TTL expiration)
|
||||
if (this.cache.has(key)) {
|
||||
return this.cache.get(key);
|
||||
const cached = this.cache.get(key);
|
||||
if (Date.now() < cached.exp) {
|
||||
return cached.value;
|
||||
}
|
||||
this.cache.delete(key);
|
||||
}
|
||||
|
||||
// Try OS keychain first
|
||||
if (this.useKeychain) {
|
||||
const value = await keychainManager.retrieve(key);
|
||||
if (value) {
|
||||
this.cache.set(key, value);
|
||||
this.cache.set(key, { value, exp: Date.now() + this.CACHE_TTL_MS });
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -89,7 +94,7 @@ class CredentialManager {
|
||||
// Fallback to encrypted file storage
|
||||
const value = await this.retrieveFromFile(key);
|
||||
if (value) {
|
||||
this.cache.set(key, value);
|
||||
this.cache.set(key, { value, exp: Date.now() + this.CACHE_TTL_MS });
|
||||
}
|
||||
return value;
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user