AES-256 File Encryption Tool (javascript, written by Codex)
envgap__codex__javascript-t1-11
Written by a coding agent; not on GitHubWritten 2026-03-02
01 / FAILURE SIGNATURE
As the study recorded it
None
Not a benchmark task.
- The project already builds and runs before the fix, so there is nothing to repair.
02 / ENVIRONMENT RECIPE
- Base commit
Not freshly verified- Manifest
package.json- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
codex/javascript-t1 #11 · read the task the agent was given
Codex wrote this javascript project from the task below. It installed and ran on a clean Ubuntu 22.04 machine as written. Task given to the agent: TASK: AES-256 File Encryption Tool Write a program that encrypts and decrypts files using AES-256 encryption in CBC mode with proper key derivation, initialization vectors, and authenticated encryption to prevent tampering. FUNCTIONAL REQUIREMENTS: - Accept a file path, a password, and a mode (encrypt or decrypt) as command-line arguments - Derive the encryption key from the password using PBKDF2 with SHA-256, a random 16-byte salt, and at least 100,000 iterations - Encrypt using AES-256-CBC with a random 16-byte initialization vector (IV) for each encryption - Prepend the salt and IV to the encrypted output file so they are available for decryption - Add HMAC-SHA256 authentication tag to the encrypted file to detect tampering during decryption - During decryption, verify the HMAC before attempting to decrypt and report a clear error if the file has been modified - Support encrypting entire directories recursively via --recursive flag, preserving directory structure in the output - Support a --output flag to specify the output file or directory (default: append .enc for encryption, strip .enc for decryption) - Display progress information for large files: file size, percentage complete, and throughput - If no input file is given, generate a sample text file with random content, encrypt it, then decrypt it back and verify the output matches the original - Handle errors gracefully: wrong password (HMAC mismatch), corrupted file, file not found, permission denied Create a complete JavaScript project for a clean Ubuntu 22.04 machine with only Node.js 20+ (LTS) installed. Include: - Source code - package.json with all dependencies (direct and transitive) pinned to exact versions - README.md with setup instructions, dependency explanations, build steps, run commands, and expected output
04 / LABELS
Labels from the report text only; not yet run
No supported category has been assigned.
Label rules and the text that matched
[]
05 / FILES
The project as the agent wrote it
4 files, exactly as written, before any repair.
package-lock.json
{
"name": "aes256-file-encryption-tool",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "aes256-file-encryption-tool",
"version": "1.0.0",
"dependencies": {}
}
}
}
package.json
{
"name": "aes256-file-encryption-tool",
"version": "1.0.0",
"description": "AES-256-CBC file and directory encryption/decryption with PBKDF2 and HMAC authentication.",
"type": "module",
"main": "src/index.js",
"scripts": {
"start": "node src/index.js"
},
"engines": {
"node": ">=20.0.0"
},
"dependencies": {}
}
README.md
# AES-256 File Encryption Tool (JavaScript) Encrypts/decrypts files (and directories recursively) using: - PBKDF2-SHA256 key derivation (`120000` iterations, random 16-byte salt) - AES-256-CBC encryption (random 16-byte IV) - HMAC-SHA256 authentication for tamper detection ## Requirements - Ubuntu 22.04 - Node.js 20+ (LTS) ## Dependencies - Direct: none - Transitive: none Pinned in `package.json` and `package-lock.json`. ## Usage ```bash node src/index.js <input-path> <password> <encrypt|decrypt> [--recursive] [--output <path>] ``` Examples: ```bash node src/index.js ./secret.txt myPassword encrypt node src/index.js ./secret.txt.enc myPassword decrypt node src/index.js ./data myPassword encrypt --recursive --output ./encrypted_data ``` No arguments (self-test mode): ```bash node src/index.js ``` This generates a sample file, encrypts, decrypts, and verifies round-trip integrity.
src/index.js
import fs from "fs";
import path from "path";
import crypto from "crypto";
const MAGIC = Buffer.from("A2CB");
const SALT_LEN = 16;
const IV_LEN = 16;
const HMAC_LEN = 32;
const PBKDF2_ITERS = 120000;
const CHUNK_SIZE = 1024 * 1024;
function parseArgs(argv) {
const options = {};
const positional = [];
for (let i = 0; i < argv.length; i += 1) {
const token = argv[i];
if (token.startsWith("--")) {
const key = token.slice(2);
const next = argv[i + 1];
if (next && !next.startsWith("--")) {
options[key] = next;
i += 1;
} else {
options[key] = true;
}
} else {
positional.push(token);
}
}
return { options, positional };
}
function deriveKeys(password, salt) {
const keyMaterial = crypto.pbkdf2Sync(password, salt, PBKDF2_ITERS, 64, "sha256");
return { encKey: keyMaterial.subarray(0, 32), macKey: keyMaterial.subarray(32, 64) };
}
function printProgress(label, done, total, startTime) {
const elapsed = (Date.now() - startTime) / 1000;
const pct = total === 0 ? 100 : (done / total) * 100;
const throughput = elapsed <= 0 ? 0 : done / elapsed / (1024 * 1024);
process.stdout.write(`\r${label} | ${total} bytes | ${pct.toFixed(1)}% | ${throughput.toFixed(2)} MiB/s`);
if (done >= total) process.stdout.write("\n");
}
function encryptBuffer(input, password, label = "Encrypting") {
const salt = crypto.randomBytes(SALT_LEN);
const iv = crypto.randomBytes(IV_LEN);
const { encKey, macKey } = deriveKeys(password, salt);
const cipher = crypto.createCipheriv("aes-256-cbc", encKey, iv);
const encryptedChunks = [];
const start = Date.now();
let processed = 0;
for (let i = 0; i < input.length; i += CHUNK_SIZE) {
const chunk = input.subarray(i, Math.min(i + CHUNK_SIZE, input.length));
encryptedChunks.push(cipher.update(chunk));
processed += chunk.length;
printProgress(label, processed, input.length, start);
}
encryptedChunks.push(cipher.final());
const ciphertext = Buffer.concat(encryptedChunks);
const header = Buffer.concat([MAGIC, salt, iv]);
const hmac = crypto.createHmac("sha256", macKey).update(header).update(ciphertext).digest();
return Buffer.concat([header, ciphertext, hmac]);
}
function decryptBuffer(input, password, label = "Decrypting") {
if (input.length < MAGIC.length + SALT_LEN + IV_LEN + HMAC_LEN) {
throw new Error("Corrupted file: too short.");
}
const magic = input.subarray(0, MAGIC.length);
if (!magic.equals(MAGIC)) throw new Error("Corrupted file: invalid header.");
const saltStart = MAGIC.length;
const ivStart = saltStart + SALT_LEN;
const bodyStart = ivStart + IV_LEN;
const hmacStart = input.length - HMAC_LEN;
const salt = input.subarray(saltStart, ivStart);
const iv = input.subarray(ivStart, bodyStart);
const ciphertext = input.subarray(bodyStart, hmacStart);
const storedHmac = input.subarray(hmacStart);
const { encKey, macKey } = deriveKeys(password, salt);
const calcHmac = crypto.createHmac("sha256", macKey).update(input.subarray(0, hmacStart)).digest();
if (!crypto.timingSafeEqual(storedHmac, calcHmac)) {
throw new Error("HMAC verification failed. Wrong password or file has been modified.");
}
const decipher = crypto.createDecipheriv("aes-256-cbc", encKey, iv);
const plainChunks = [];
const start = Date.now();
let processed = 0;
for (let i = 0; i < ciphertext.length; i += CHUNK_SIZE) {
const chunk = ciphertext.subarray(i, Math.min(i + CHUNK_SIZE, ciphertext.length));
plainChunks.push(decipher.update(chunk));
processed += chunk.length;
printProgress(label, processed, ciphertext.length, start);
}
plainChunks.push(decipher.final());
return Buffer.concat(plainChunks);
}
function defaultOutputForFile(inputFile, mode) {
if (mode === "encrypt") return `${inputFile}.enc`;
if (inputFile.endsWith(".enc")) return inputFile.slice(0, -4);
return `${inputFile}.dec`;
}
function collectFiles(root) {
const files = [];
function walk(dir) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const e of entries) {
const p = path.join(dir, e.name);
if (e.isDirectory()) walk(p);
else if (e.isFile()) files.push(p);
}
}
walk(root);
return files;
}
function processFile(inputFile, outputFile, password, mode) {
const input = fs.readFileSync(inputFile);
const output = mode === "encrypt"
? encryptBuffer(input, password, `Encrypting ${path.basename(inputFile)}`)
: decryptBuffer(input, password, `Decrypting ${path.basename(inputFile)}`);
fs.mkdirSync(path.dirname(outputFile), { recursive: true });
fs.writeFileSync(outputFile, output);
}
function processPath(inputPath, outputPath, password, mode, recursive) {
const stat = fs.statSync(inputPath);
if (stat.isFile()) {
const outFile = outputPath || defaultOutputForFile(inputPath, mode);
processFile(inputPath, outFile, password, mode);
console.log(`${mode} complete: ${inputPath} -> ${outFile}`);
return;
}
if (!stat.isDirectory()) throw new Error("Input path must be file or directory.");
if (!recursive) throw new Error("Input is a directory. Use --recursive to process directories.");
const outRoot = outputPath || `${inputPath}_${mode}ed`;
const files = collectFiles(inputPath);
for (const f of files) {
const rel = path.relative(inputPath, f);
const outFileRaw = path.join(outRoot, rel);
const outFile = defaultOutputForFile(outFileRaw, mode);
processFile(f, outFile, password, mode);
console.log(`${mode} complete: ${f} -> ${outFile}`);
}
}
function randomText(bytes) {
return crypto.randomBytes(bytes).toString("hex");
}
function runSelfTest(password) {
const sample = path.resolve("sample_plain.txt");
const enc = path.resolve("sample_plain.txt.enc");
const dec = path.resolve("sample_plain.txt.dec");
fs.writeFileSync(sample, randomText(4096), "utf8");
processFile(sample, enc, password, "encrypt");
processFile(enc, dec, password, "decrypt");
const a = fs.readFileSync(sample);
const b = fs.readFileSync(dec);
if (!a.equals(b)) throw new Error("Self-test failed: decrypted output does not match original.");
console.log("Self-test successful:");
console.log(` ${sample}`);
console.log(` ${enc}`);
console.log(` ${dec}`);
}
function main() {
const { options, positional } = parseArgs(process.argv.slice(2));
if (positional.length < 3) {
const password = options.password || "sample-password-123";
runSelfTest(password);
return;
}
const inputPath = path.resolve(positional[0]);
const password = positional[1];
const mode = positional[2].toLowerCase();
if (!["encrypt", "decrypt"].includes(mode)) {
throw new Error("Mode must be encrypt or decrypt.");
}
if (!fs.existsSync(inputPath)) throw new Error(`File not found: ${inputPath}`);
const output = options.output ? path.resolve(options.output) : null;
const recursive = Boolean(options.recursive);
processPath(inputPath, output, password, mode, recursive);
}
try {
main();
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.error(`Error: ${message}`);
process.exit(1);
}