Fix 7 critical security bugs and 1 high-severity data loss bug
- CSRF: HMAC-signed double-submit cookie (server-bound, not raw compare)
- Keychain: execFileSync with arg arrays to prevent command injection
- Caddy config: always use structured generation, never accept raw config
- Templates: replace {{GENERATED_SECRET}} with crypto.randomBytes
- Caddyfile removal: move regex inside ctx.caddy.modify() to fix TOCTOU race
- Credentials: proper-lockfile for all file operations, fix key rotation
to decrypt with old key before generating new key
- Service removal: filter by ID only, not AND with appTemplate
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -267,6 +267,51 @@ function writeEncryptedFile(filePath, credentials, sensitiveFields = ['password'
|
||||
console.log(`[Crypto] Saved encrypted credentials to ${filePath}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotate the encryption key — generates a new key and returns both old and new
|
||||
* @returns {{ oldKey: Buffer, newKey: Buffer }} Old and new key pair
|
||||
* @throws {Error} If new key cannot be saved to disk
|
||||
*/
|
||||
function rotateKey() {
|
||||
const oldKey = loadOrCreateKey(); // Ensure we have the current key loaded
|
||||
const newKey = generateKey();
|
||||
|
||||
try {
|
||||
fs.writeFileSync(KEY_FILE, newKey.toString('hex'), { mode: 0o600 });
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to save new encryption key: ${error.message}`);
|
||||
}
|
||||
|
||||
// Only update the cached key after file write succeeds
|
||||
encryptionKey = newKey;
|
||||
return { oldKey, newKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt data using a specific key (for key rotation)
|
||||
* @param {string} encryptedData - Encrypted string in format iv:authTag:ciphertext
|
||||
* @param {Buffer} key - The key to decrypt with
|
||||
* @returns {string} Decrypted plaintext
|
||||
*/
|
||||
function decryptWithKey(encryptedData, key) {
|
||||
const parts = encryptedData.split(':');
|
||||
if (parts.length !== 3) {
|
||||
throw new Error('Invalid encrypted data format');
|
||||
}
|
||||
|
||||
const iv = Buffer.from(parts[0], 'base64');
|
||||
const authTag = Buffer.from(parts[1], 'base64');
|
||||
const ciphertext = parts[2];
|
||||
|
||||
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
||||
decipher.setAuthTag(authTag);
|
||||
|
||||
let decrypted = decipher.update(ciphertext, 'base64', 'utf8');
|
||||
decrypted += decipher.final('utf8');
|
||||
|
||||
return decrypted;
|
||||
}
|
||||
|
||||
// Initialize key on module load
|
||||
loadOrCreateKey();
|
||||
|
||||
@@ -280,5 +325,7 @@ module.exports = {
|
||||
readEncryptedFile,
|
||||
writeEncryptedFile,
|
||||
loadOrCreateKey,
|
||||
deriveKey
|
||||
deriveKey,
|
||||
rotateKey,
|
||||
decryptWithKey
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user