← All tasks
javascriptcodex/javascript-t1 #17Not a task: already works

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);
}