Bcrypt Password Hasher (javascript, written by Codex)
envgap__codex__javascript-t1-17
Written by a coding agent; not on GitHubWritten 2026-03-03
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 #17 · 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: Bcrypt Password Hasher Write a program that hashes and verifies passwords using the bcrypt algorithm with configurable work factors, supporting bulk operations, migration from weaker hashing schemes, and password policy enforcement. FUNCTIONAL REQUIREMENTS: - Support subcommands: hash (hash a password), verify (check a password against a hash), benchmark (test hashing speed at different work factors), and migrate (rehash from MD5/SHA-256 to bcrypt) - hash: Accept a password via command-line argument or stdin, hash it with bcrypt, and print the resulting hash string - verify: Accept a password and a bcrypt hash string, verify the match, and print whether it is valid or invalid - Support a configurable work factor (cost parameter) via --cost flag (default 12, range 4-31) - benchmark: Measure and display the time to hash a password at each work factor from 8 to the specified maximum, helping users choose an appropriate cost - migrate: Read a CSV file with columns (username, old_hash, hash_type), verify that the old hash matches a provided password, then rehash with bcrypt and output the updated CSV - Support batch hashing via --file flag: read one password per line, hash each, and output as a CSV with columns (line_number, hash) - Generate a cryptographically secure random salt for each hash operation (built into bcrypt) - Print detailed output: the hash, work factor used, estimated time per hash, and the bcrypt version identifier ($2b$) - Save results to a file via --output flag (default: print to console only) - If no arguments are given, demonstrate hashing a sample password at three different work factors (10, 12, 14), verify each hash, show a failed verification with a wrong password, and run a mini benchmark - Handle errors: invalid cost factors, malformed hash strings, empty passwords, and unsupported hash types in migration 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": "bcrypt-password-hasher",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "bcrypt-password-hasher",
"version": "1.0.0",
"dependencies": {
"bcryptjs": "2.4.3"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/bcryptjs": {
"version": "2.4.3"
}
}
}
package.json
{
"name": "bcrypt-password-hasher",
"version": "1.0.0",
"private": true,
"type": "module",
"main": "src/index.js",
"scripts": {
"start": "node src/index.js"
},
"engines": {
"node": ">=20.0.0"
},
"dependencies": {
"bcryptjs": "2.4.3"
}
}
README.md
# Bcrypt Password Hasher (JavaScript) Supports hashing, verification, benchmarking, migration from MD5/SHA-256, and batch operations using bcrypt. ## Requirements - Ubuntu 22.04 - Node.js 20+ (LTS) ## Dependencies - Direct: - `bcryptjs@2.4.3` - Transitive: - none ## Setup ```bash npm install ``` ## Run ```bash node src/index.js hash "MyPassword" --cost 12 echo "MyPassword" | node src/index.js hash --cost 12 node src/index.js verify "MyPassword" "$2b$12$..." node src/index.js benchmark --max 14 node src/index.js hash --file ./passwords.txt --cost 12 --output ./hashes.csv node src/index.js migrate --file ./legacy.csv --password "KnownOldPassword" --cost 12 --output ./migrated.csv node src/index.js ``` ## Notes - Cost range: `4..31` - Batch hashing output CSV columns: `line_number,hash` - Migration input CSV columns: `username,old_hash,hash_type` - Migration output CSV columns: `username,bcrypt_hash,status` - No-args mode runs a built-in demo
src/index.js
import crypto from "crypto";
import fs from "fs";
import path from "path";
import bcrypt from "bcryptjs";
function parseArgs(argv) {
const options = {};
const positional = [];
for (let i = 0; i < argv.length; i += 1) {
const t = argv[i];
if (t.startsWith("--")) {
const k = t.slice(2);
const n = argv[i + 1];
if (n && !n.startsWith("--")) {
options[k] = n;
i += 1;
} else options[k] = true;
} else positional.push(t);
}
return { options, positional };
}
function requireCost(v) {
const c = Number.parseInt(String(v ?? "12"), 10);
if (Number.isNaN(c) || c < 4 || c > 31) throw new Error("Cost must be in range 4..31.");
return c;
}
function getPassword(positional, options, index = 1) {
if (positional[index]) return positional[index];
if (options.password) return String(options.password);
if (!process.stdin.isTTY) return fs.readFileSync(0, "utf8").trimEnd();
return "";
}
function ensureNonEmpty(password) {
if (!password) throw new Error("Password is empty.");
}
function hashOne(password, cost) {
const t0 = process.hrtime.bigint();
const hash = bcrypt.hashSync(password, cost);
const ms = Number(process.hrtime.bigint() - t0) / 1e6;
return { hash, ms };
}
function commandHash(positional, options) {
const cost = requireCost(options.cost);
if (options.file) {
const lines = fs.readFileSync(path.resolve(options.file), "utf8").split(/\r?\n/);
const outRows = ["line_number,hash"];
lines.forEach((line, i) => {
if (line.length === 0) return;
const { hash } = hashOne(line, cost);
outRows.push(`${i + 1},${hash}`);
});
const text = `${outRows.join("\n")}\n`;
if (options.output) fs.writeFileSync(path.resolve(options.output), text, "utf8");
process.stdout.write(text);
return;
}
const password = getPassword(positional, options, 1);
ensureNonEmpty(password);
const { hash, ms } = hashOne(password, cost);
const text = [
`hash: ${hash}`,
`cost: ${cost}`,
`bcrypt_version: ${hash.slice(0, 4)}`,
`estimated_time_ms: ${ms.toFixed(2)}`,
"",
].join("\n");
if (options.output) fs.writeFileSync(path.resolve(options.output), text, "utf8");
process.stdout.write(text);
}
function commandVerify(positional, options) {
const password = getPassword(positional, options, 1);
const hash = positional[2] || options.hash || "";
ensureNonEmpty(password);
if (!/^\$2[aby]\$\d{2}\$/.test(hash)) throw new Error("Malformed bcrypt hash.");
const ok = bcrypt.compareSync(password, hash);
const text = `verification: ${ok ? "VALID" : "INVALID"}\n`;
if (options.output) fs.writeFileSync(path.resolve(options.output), text, "utf8");
process.stdout.write(text);
if (!ok) process.exitCode = 2;
}
function commandBenchmark(options) {
const maxCost = requireCost(options.max ?? options.cost ?? "14");
const password = options.password || "BenchmarkSamplePassword!";
const rows = ["cost,time_ms"];
for (let c = 8; c <= maxCost; c += 1) {
const { ms } = hashOne(password, c);
rows.push(`${c},${ms.toFixed(2)}`);
}
const text = `${rows.join("\n")}\n`;
if (options.output) fs.writeFileSync(path.resolve(options.output), text, "utf8");
process.stdout.write(text);
}
function oldHashMatches(password, oldHash, hashType) {
const kind = hashType.toLowerCase();
if (kind === "md5") return crypto.createHash("md5").update(password).digest("hex") === oldHash.toLowerCase();
if (kind === "sha256") return crypto.createHash("sha256").update(password).digest("hex") === oldHash.toLowerCase();
throw new Error(`Unsupported hash type: ${hashType}`);
}
function commandMigrate(options) {
const csvPath = options.file ? path.resolve(options.file) : "";
if (!csvPath) throw new Error("migrate requires --file <csv>.");
const password = options.password || "";
ensureNonEmpty(password);
const cost = requireCost(options.cost);
const lines = fs.readFileSync(csvPath, "utf8").split(/\r?\n/).filter(Boolean);
if (lines.length === 0) throw new Error("CSV file is empty.");
const outputRows = ["username,bcrypt_hash,status"];
for (const line of lines.slice(1)) {
const [username, oldHash, hashType] = line.split(",");
if (!username || !oldHash || !hashType) continue;
try {
const ok = oldHashMatches(password, oldHash.trim(), hashType.trim());
if (!ok) outputRows.push(`${username},,old_hash_mismatch`);
else outputRows.push(`${username},${bcrypt.hashSync(password, cost)},migrated`);
} catch (e) {
outputRows.push(`${username},,${(e instanceof Error ? e.message : String(e)).replaceAll(",", ";")}`);
}
}
const text = `${outputRows.join("\n")}\n`;
if (options.output) fs.writeFileSync(path.resolve(options.output), text, "utf8");
process.stdout.write(text);
}
function demo() {
const password = "S@mpl3P@ssw0rd!";
for (const cost of [10, 12, 14]) {
const { hash, ms } = hashOne(password, cost);
console.log(`cost=${cost} hash=${hash} time_ms=${ms.toFixed(2)} verify=${bcrypt.compareSync(password, hash)}`);
}
const wrongHash = bcrypt.hashSync(password, 10);
console.log(`wrong verification: ${bcrypt.compareSync("wrong-password", wrongHash) ? "VALID" : "INVALID"}`);
commandBenchmark({ max: "12", password });
}
function main() {
const { positional, options } = parseArgs(process.argv.slice(2));
if (positional.length === 0) {
demo();
return;
}
const cmd = positional[0].toLowerCase();
if (cmd === "hash") commandHash(positional, options);
else if (cmd === "verify") commandVerify(positional, options);
else if (cmd === "benchmark") commandBenchmark(options);
else if (cmd === "migrate") commandMigrate(options);
else throw new Error("Unknown subcommand. Use hash|verify|benchmark|migrate.");
}
try {
main();
} catch (e) {
console.error(`Error: ${e instanceof Error ? e.message : String(e)}`);
process.exit(1);
}