Bcrypt Password Hasher (javascript, written by Claude Code)
envgap__claude-code__javascript-t1-17
Written by a coding agent; not on GitHubWritten 2026-02-27
01 / FAILURE SIGNATURE
As the study recorded it
No identifying execution failure has been captured.
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
claude-code/javascript-t1 #17 · read the task the agent was given
Claude Code 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
3 files, exactly as written, before any repair.
hasher.js
#!/usr/bin/env node
/**
* Bcrypt Password Hasher
*
* Hashes and verifies passwords using bcrypt with configurable work factors,
* benchmarking capabilities, and migration support for upgrading hash strength.
*/
const bcrypt = require("bcrypt");
const DEFAULT_WORK_FACTOR = 12;
const MIN_WORK_FACTOR = 4;
const MAX_WORK_FACTOR = 31;
/**
* Validate that a work factor is within acceptable bounds.
* @param {number} workFactor
*/
function validateWorkFactor(workFactor) {
if (!Number.isInteger(workFactor)) {
throw new TypeError(`Work factor must be an integer, got ${typeof workFactor}`);
}
if (workFactor < MIN_WORK_FACTOR || workFactor > MAX_WORK_FACTOR) {
throw new RangeError(
`Work factor must be between ${MIN_WORK_FACTOR} and ${MAX_WORK_FACTOR}, got ${workFactor}`
);
}
}
/**
* Hash a password using bcrypt with the specified work factor.
* @param {string} password - The plaintext password to hash.
* @param {number} [workFactor=12] - The bcrypt cost parameter.
* @returns {Promise<{hashed: string, workFactor: number, elapsedMs: number}>}
*/
async function hashPassword(password, workFactor = DEFAULT_WORK_FACTOR) {
if (!password) {
throw new Error("Password cannot be empty");
}
validateWorkFactor(workFactor);
const start = process.hrtime.bigint();
const salt = await bcrypt.genSalt(workFactor);
const hashed = await bcrypt.hash(password, salt);
const elapsedMs =
Number(process.hrtime.bigint() - start) / 1_000_000;
return {
hashed,
workFactor,
elapsedMs: Math.round(elapsedMs * 100) / 100,
};
}
/**
* Verify a password against a bcrypt hash.
* @param {string} password - The plaintext password to verify.
* @param {string} hashed - The bcrypt hash to check against.
* @returns {Promise<boolean>}
*/
async function verifyPassword(password, hashed) {
if (!password || !hashed) {
throw new Error("Password and hash cannot be empty");
}
try {
return await bcrypt.compare(password, hashed);
} catch {
return false;
}
}
/**
* Extract the work factor (cost) from an existing bcrypt hash.
* @param {string} hashed - A bcrypt hash string.
* @returns {number} The work factor used to create the hash.
*/
function extractWorkFactor(hashed) {
if (!hashed) {
throw new Error("Hash cannot be empty");
}
const parts = hashed.split("$");
if (parts.length >= 4) {
const factor = parseInt(parts[2], 10);
if (!isNaN(factor)) {
return factor;
}
}
throw new Error(
`Cannot extract work factor from hash: ${hashed.substring(0, 20)}...`
);
}
/**
* Check if a hash needs to be re-hashed with a target work factor.
* @param {string} hashed - An existing bcrypt hash.
* @param {number} targetWorkFactor - The desired work factor.
* @returns {boolean}
*/
function needsMigration(hashed, targetWorkFactor) {
try {
const existingFactor = extractWorkFactor(hashed);
return existingFactor < targetWorkFactor;
} catch {
return true;
}
}
/**
* Migrate a password hash to a new work factor if needed.
* @param {string} password - The plaintext password.
* @param {string} oldHash - The existing bcrypt hash.
* @param {number} [targetWorkFactor=12] - The target work factor.
* @returns {Promise<{hashed: string, workFactor: number, elapsedMs: number}|null>}
*/
async function migrateHash(password, oldHash, targetWorkFactor = DEFAULT_WORK_FACTOR) {
const valid = await verifyPassword(password, oldHash);
if (!valid) {
throw new Error("Password does not match the provided hash");
}
if (needsMigration(oldHash, targetWorkFactor)) {
return hashPassword(password, targetWorkFactor);
}
return null;
}
/**
* Benchmark hashing and verification across a range of work factors.
* @param {number} [maxWorkFactor=12] - Maximum work factor to benchmark.
* @param {number} [iterations=3] - Number of iterations per work factor.
* @returns {Promise<Array<{workFactor: number, avgHashMs: number, avgVerifyMs: number, iterations: number}>>}
*/
async function benchmark(maxWorkFactor = DEFAULT_WORK_FACTOR, iterations = 3) {
if (iterations < 1) {
throw new Error("Iterations must be at least 1");
}
validateWorkFactor(maxWorkFactor);
const testPassword = "BenchmarkPassword!123";
const results = [];
const maxFactor = Math.min(maxWorkFactor + 2, 16);
for (let wf = MIN_WORK_FACTOR; wf <= maxFactor; wf++) {
let totalHashMs = 0;
let totalVerifyMs = 0;
for (let i = 0; i < iterations; i++) {
// Time hashing
let start = process.hrtime.bigint();
const salt = await bcrypt.genSalt(wf);
const hashed = await bcrypt.hash(testPassword, salt);
totalHashMs += Number(process.hrtime.bigint() - start) / 1_000_000;
// Time verification
start = process.hrtime.bigint();
await bcrypt.compare(testPassword, hashed);
totalVerifyMs += Number(process.hrtime.bigint() - start) / 1_000_000;
}
results.push({
workFactor: wf,
avgHashMs: Math.round((totalHashMs / iterations) * 100) / 100,
avgVerifyMs: Math.round((totalVerifyMs / iterations) * 100) / 100,
iterations,
});
}
return results;
}
// ---- CLI ----
async function main() {
const args = process.argv.slice(2);
const command = args[0];
function getFlag(flag, defaultValue) {
const idx = args.indexOf(flag);
if (idx !== -1 && idx + 1 < args.length) {
return parseInt(args[idx + 1], 10);
}
return defaultValue;
}
try {
switch (command) {
case "hash": {
const password = args[1];
if (!password) {
console.error("Usage: hasher.js hash <password> [-w work_factor]");
process.exit(1);
}
const wf = getFlag("-w", DEFAULT_WORK_FACTOR);
const result = await hashPassword(password, wf);
console.log(JSON.stringify(result, null, 2));
break;
}
case "verify": {
const password = args[1];
const hash = args[2];
if (!password || !hash) {
console.error("Usage: hasher.js verify <password> <hash>");
process.exit(1);
}
const valid = await verifyPassword(password, hash);
console.log(JSON.stringify({ valid }, null, 2));
process.exit(valid ? 0 : 1);
break;
}
case "benchmark": {
const wf = getFlag("-w", DEFAULT_WORK_FACTOR);
const iter = getFlag("-i", 3);
const results = await benchmark(wf, iter);
console.log(JSON.stringify(results, null, 2));
break;
}
case "migrate": {
const password = args[1];
const hash = args[2];
if (!password || !hash) {
console.error("Usage: hasher.js migrate <password> <hash> [-w work_factor]");
process.exit(1);
}
const wf = getFlag("-w", DEFAULT_WORK_FACTOR);
if (needsMigration(hash, wf)) {
const result = await migrateHash(password, hash, wf);
if (result) {
console.log(JSON.stringify({ migrated: true, ...result }, null, 2));
} else {
console.log(JSON.stringify({ migrated: false }, null, 2));
}
} else {
console.log(
JSON.stringify(
{ migrated: false, reason: "Hash already at target strength" },
null,
2
)
);
}
break;
}
default:
console.log("Bcrypt Password Hasher");
console.log("Usage:");
console.log(" hasher.js hash <password> [-w work_factor]");
console.log(" hasher.js verify <password> <hash>");
console.log(" hasher.js benchmark [-w work_factor] [-i iterations]");
console.log(" hasher.js migrate <password> <hash> [-w work_factor]");
process.exit(1);
}
} catch (err) {
console.error("Error:", err.message);
process.exit(1);
}
}
// Export for programmatic use
module.exports = {
hashPassword,
verifyPassword,
extractWorkFactor,
needsMigration,
migrateHash,
benchmark,
};
// Run CLI if executed directly
if (require.main === module) {
main();
}
package.json
{
"name": "bcrypt-password-hasher",
"version": "1.0.0",
"description": "Password hashing utility using bcrypt with configurable work factors, benchmarking, and migration support",
"main": "hasher.js",
"bin": {
"bcrypt-hasher": "hasher.js"
},
"scripts": {
"start": "node hasher.js",
"hash": "node hasher.js hash",
"benchmark": "node hasher.js benchmark"
},
"keywords": ["bcrypt", "password", "hashing", "security"],
"license": "MIT",
"dependencies": {
"bcrypt": "5.1.1"
}
}
README.md
# Bcrypt Password Hasher (JavaScript - Trial 1) A password hashing utility using the bcrypt library with configurable work factors, benchmarking, and hash migration support. ## Dependencies - **bcrypt** (5.1.1): Native bcrypt hashing library for Node.js ## Installation ```bash npm install ``` ## Usage ### Hash a password ```bash node hasher.js hash "mypassword" -w 12 ``` ### Verify a password ```bash node hasher.js verify "mypassword" "$2b$12$..." ``` ### Benchmark work factors ```bash node hasher.js benchmark -w 14 -i 5 ``` ### Migrate a hash ```bash node hasher.js migrate "mypassword" "$2b$10$..." -w 12 ``` ## Features - Configurable bcrypt work factor (cost parameter 4-31) - Password hashing and verification with async/await - Benchmark mode to compare work factor performance - Hash migration to upgrade weaker hashes to stronger work factors - JSON output for easy integration - Exportable as a Node.js module