tops-bmad
Version:
CLI tool to install BMAD workflow files into any project with integrated Shai-Hulud 2.0 security scanning
84 lines (72 loc) • 2.9 kB
JavaScript
import crypto from "crypto";
import fs from "fs-extra";
const ALGORITHM = "aes-256-cbc";
const IV_LENGTH = 16; // For AES, this is always 16
/**
* Derives a key from a password using PBKDF2
* @param {string} password - The plain text password
* @returns {Buffer} - The derived key
*/
function deriveKey(password) {
// Use a fixed salt for consistency (in production, you might want to store salt with encrypted data)
const salt = crypto.createHash("sha256").update("bmad-cli-salt").digest();
return crypto.pbkdf2Sync(password, salt, 100000, 32, "sha256");
}
/**
* Encrypts a file using AES-256-CBC
* @param {string} inputPath - Path to the file to encrypt
* @param {string} outputPath - Path where encrypted file will be saved
* @param {string} password - Plain text password
* @returns {Promise<void>}
*/
export async function encryptFile(inputPath, outputPath, password) {
try {
const key = deriveKey(password);
const iv = crypto.randomBytes(IV_LENGTH);
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
const input = await fs.readFile(inputPath);
const encrypted = Buffer.concat([
iv, // Prepend IV to the encrypted data
cipher.update(input),
cipher.final()
]);
await fs.writeFile(outputPath, encrypted);
console.log(`✅ File encrypted successfully: ${outputPath}`);
} catch (error) {
console.error("❌ Error encrypting file:", error.message);
throw error;
}
}
/**
* Decrypts a file using AES-256-CBC
* @param {string} inputPath - Path to the encrypted file
* @param {string} outputPath - Path where decrypted file will be saved
* @param {string} password - Plain text password
* @returns {Promise<void>}
*/
export async function decryptFile(inputPath, outputPath, password) {
try {
const key = deriveKey(password);
const encrypted = await fs.readFile(inputPath);
// Extract IV from the beginning of the encrypted data
const iv = encrypted.slice(0, IV_LENGTH);
const encryptedData = encrypted.slice(IV_LENGTH);
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
const decrypted = Buffer.concat([
decipher.update(encryptedData),
decipher.final()
]);
await fs.writeFile(outputPath, decrypted);
} catch (error) {
// If decryption fails, it's likely due to wrong password
if (error.message.includes("bad decrypt") || error.message.includes("wrong final block length")) {
throw new Error("Invalid secret key. Please check your password and try again.");
}
// If the file is not encrypted or corrupted
if (error.message.includes("Invalid") || error.message.includes("unsupported")) {
throw new Error("Failed to decrypt file. The file may be corrupted or encrypted with a different key.");
}
console.error("❌ Error decrypting file:", error.message);
throw error;
}
}