TOTP Generator (javascript, written by Codex)
envgap__codex__javascript-t1-14
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 #14 · 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: TOTP Generator Write a program that generates and validates Time-based One-Time Passwords (TOTP) compatible with RFC 6238, supporting secret key management, QR code URI generation, and multi-account storage. FUNCTIONAL REQUIREMENTS: - Support subcommands: generate (create a new TOTP secret), code (show current OTP code), verify (check if a code is valid), and list (show all stored accounts) - generate: Create a cryptographically random base32-encoded secret key of configurable length (--length flag, default 20 bytes) - code: Display the current 6-digit TOTP code for a given account, with a countdown timer showing seconds until the code expires (30-second default period) - verify: Accept a 6-digit code and check if it matches the current or adjacent time windows (configurable drift tolerance via --drift flag, default 1 window) - Support configurable TOTP parameters: digit count (6 or 8), time period (30 or 60 seconds), and hash algorithm (SHA-1, SHA-256, SHA-512) - Generate otpauth:// URIs compatible with authenticator apps (Google Authenticator, Authy) - Store account secrets in an encrypted local JSON file using a master password - Support multiple accounts with labels (--account flag with issuer:username format) - Print the current code, remaining seconds, and next code to console - If no arguments are given, generate a demo account with a random secret, display the current code and the otpauth:// URI, verify the current code, then show what happens with an incorrect code - Handle errors: invalid base32 secrets, expired codes, duplicate account names, wrong master password 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": "totp-generator",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "totp-generator",
"version": "1.0.0",
"dependencies": {},
"engines": {
"node": ">=20.0.0"
}
}
}
}
package.json
{
"name": "totp-generator",
"version": "1.0.0",
"private": true,
"type": "module",
"main": "src/index.js",
"description": "RFC 6238 TOTP Generator with encrypted local account storage",
"scripts": {
"start": "node src/index.js"
},
"engines": {
"node": ">=20.0.0"
},
"dependencies": {}
}
README.md
# TOTP Generator (JavaScript) RFC 6238-compatible TOTP generator/verifier with encrypted multi-account storage. ## Requirements - Ubuntu 22.04 - Node.js 20+ (LTS) ## Dependencies - Direct: none - Transitive: none Pinned in `package.json` and `package-lock.json`. ## Setup ```bash npm install ``` ## Run ```bash node src/index.js generate --master "<password>" --account "Issuer:user@example.com" [--length 20] [--digits 6|8] [--period 30|60] [--algorithm SHA-1|SHA-256|SHA-512] [--store ./totp_accounts.enc.json] node src/index.js code --master "<password>" --account "Issuer:user@example.com" [--store ./totp_accounts.enc.json] node src/index.js verify --master "<password>" --account "Issuer:user@example.com" --code 123456 [--drift 1] [--store ./totp_accounts.enc.json] node src/index.js list --master "<password>" [--store ./totp_accounts.enc.json] ``` ## Features - Subcommands: `generate`, `code`, `verify`, `list` - Cryptographically random base32 secrets - Configurable digits/period/hash algorithm - RFC 6238 code generation and drift-tolerant verification - `otpauth://` URI generation for authenticator apps - Encrypted JSON account storage using PBKDF2 + AES-256-GCM - Duplicate account detection and wrong password handling - No-args demo mode
src/index.js
import crypto from "crypto";
import fs from "fs";
import path from "path";
const DEFAULT_STORE = path.resolve("totp_accounts.enc.json");
const PBKDF2_ITERS = 150000;
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 normalizeAlgorithm(input) {
const raw = String(input || "SHA-1").toUpperCase();
const map = { "SHA-1": "sha1", "SHA-256": "sha256", "SHA-512": "sha512" };
if (!map[raw]) throw new Error("Unsupported algorithm. Use SHA-1, SHA-256, or SHA-512.");
return { label: raw, node: map[raw] };
}
function parseDigits(input) {
const v = Number.parseInt(String(input || "6"), 10);
if (![6, 8].includes(v)) throw new Error("Digits must be 6 or 8.");
return v;
}
function parsePeriod(input) {
const v = Number.parseInt(String(input || "30"), 10);
if (![30, 60].includes(v)) throw new Error("Period must be 30 or 60.");
return v;
}
function parseDrift(input) {
const v = Number.parseInt(String(input || "1"), 10);
if (Number.isNaN(v) || v < 0 || v > 10) throw new Error("Drift must be between 0 and 10.");
return v;
}
function validateLabel(label) {
if (!label || !label.includes(":")) throw new Error("Account label must be in issuer:username format.");
return label;
}
function base32Encode(buffer) {
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
let bits = 0;
let value = 0;
let out = "";
for (const byte of buffer) {
value = (value << 8) | byte;
bits += 8;
while (bits >= 5) {
out += alphabet[(value >>> (bits - 5)) & 31];
bits -= 5;
}
}
if (bits > 0) out += alphabet[(value << (5 - bits)) & 31];
return out;
}
function base32Decode(text) {
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
const clean = text.toUpperCase().replace(/=+$/g, "").replace(/\s+/g, "");
if (!clean) throw new Error("Invalid base32 secret: empty.");
let bits = 0;
let value = 0;
const out = [];
for (const ch of clean) {
const idx = alphabet.indexOf(ch);
if (idx < 0) throw new Error("Invalid base32 secret.");
value = (value << 5) | idx;
bits += 5;
if (bits >= 8) {
out.push((value >>> (bits - 8)) & 0xff);
bits -= 8;
}
}
return Buffer.from(out);
}
function leftPadCode(code, digits) {
return String(code).padStart(digits, "0");
}
function totpAtCounter(secretBase32, digits, algorithmNode, counter) {
const secret = base32Decode(secretBase32);
const counterBuf = Buffer.alloc(8);
counterBuf.writeBigUInt64BE(BigInt(counter));
const hmac = crypto.createHmac(algorithmNode, secret).update(counterBuf).digest();
const offset = hmac[hmac.length - 1] & 0x0f;
const binCode = ((hmac[offset] & 0x7f) << 24)
| ((hmac[offset + 1] & 0xff) << 16)
| ((hmac[offset + 2] & 0xff) << 8)
| (hmac[offset + 3] & 0xff);
const mod = 10 ** digits;
return leftPadCode(binCode % mod, digits);
}
function totpNow(secretBase32, digits, period, algorithmNode, epochSeconds = Math.floor(Date.now() / 1000)) {
const counter = Math.floor(epochSeconds / period);
const remaining = period - (epochSeconds % period);
return {
current: totpAtCounter(secretBase32, digits, algorithmNode, counter),
next: totpAtCounter(secretBase32, digits, algorithmNode, counter + 1),
remaining,
counter,
};
}
function verifyTotp(secretBase32, code, digits, period, algorithmNode, drift, epochSeconds = Math.floor(Date.now() / 1000)) {
if (!/^\d{6,8}$/.test(code)) throw new Error("Code must be 6 or 8 digits.");
const counter = Math.floor(epochSeconds / period);
for (let w = -drift; w <= drift; w += 1) {
const expected = totpAtCounter(secretBase32, digits, algorithmNode, counter + w);
if (expected === code) return { valid: true, windowOffset: w };
}
return { valid: false, windowOffset: null };
}
function makeOtpAuthUri(accountLabel, secretBase32, digits, period, algorithmLabel) {
const [issuerRaw, usernameRaw] = accountLabel.split(":", 2);
const issuer = issuerRaw.trim();
const username = usernameRaw.trim();
const label = encodeURIComponent(`${issuer}:${username}`);
const query = new URLSearchParams({
secret: secretBase32,
issuer,
algorithm: algorithmLabel.replace("-", ""),
digits: String(digits),
period: String(period),
});
return `otpauth://totp/${label}?${query.toString()}`;
}
function deriveKey(masterPassword, salt) {
return crypto.pbkdf2Sync(masterPassword, salt, PBKDF2_ITERS, 32, "sha256");
}
function encryptJson(jsonText, masterPassword) {
const salt = crypto.randomBytes(16);
const iv = crypto.randomBytes(12);
const key = deriveKey(masterPassword, salt);
const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
const ciphertext = Buffer.concat([cipher.update(jsonText, "utf8"), cipher.final()]);
const tag = cipher.getAuthTag();
return {
version: 1,
kdf: "PBKDF2-SHA256",
iterations: PBKDF2_ITERS,
salt: salt.toString("base64"),
iv: iv.toString("base64"),
tag: tag.toString("base64"),
data: ciphertext.toString("base64"),
};
}
function decryptJson(blob, masterPassword) {
try {
const salt = Buffer.from(blob.salt, "base64");
const iv = Buffer.from(blob.iv, "base64");
const tag = Buffer.from(blob.tag, "base64");
const data = Buffer.from(blob.data, "base64");
const key = crypto.pbkdf2Sync(masterPassword, salt, blob.iterations, 32, "sha256");
const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
decipher.setAuthTag(tag);
const plaintext = Buffer.concat([decipher.update(data), decipher.final()]);
return plaintext.toString("utf8");
} catch {
throw new Error("Wrong master password or corrupted encrypted store.");
}
}
function loadStore(storePath, masterPassword) {
if (!fs.existsSync(storePath)) return { version: 1, accounts: [] };
const raw = JSON.parse(fs.readFileSync(storePath, "utf8"));
const plain = decryptJson(raw, masterPassword);
const parsed = JSON.parse(plain);
if (!Array.isArray(parsed.accounts)) throw new Error("Encrypted store is malformed.");
return parsed;
}
function saveStore(storePath, masterPassword, data) {
const encrypted = encryptJson(JSON.stringify(data, null, 2), masterPassword);
fs.mkdirSync(path.dirname(storePath), { recursive: true });
fs.writeFileSync(storePath, `${JSON.stringify(encrypted, null, 2)}\n`, "utf8");
}
function requireMaster(options) {
if (!options.master) throw new Error("Missing --master <password> for encrypted storage.");
return String(options.master);
}
function getAccount(store, label) {
const account = store.accounts.find((a) => a.label === label);
if (!account) throw new Error(`Account not found: ${label}`);
return account;
}
function cmdGenerate(options) {
const master = requireMaster(options);
const storePath = path.resolve(options.store || DEFAULT_STORE);
const accountLabel = validateLabel(options.account || "");
const length = Number.parseInt(String(options.length || "20"), 10);
if (Number.isNaN(length) || length < 10 || length > 128) throw new Error("Length must be between 10 and 128 bytes.");
const digits = parseDigits(options.digits || "6");
const period = parsePeriod(options.period || "30");
const algo = normalizeAlgorithm(options.algorithm || "SHA-1");
const store = loadStore(storePath, master);
if (store.accounts.some((a) => a.label === accountLabel)) throw new Error(`Duplicate account: ${accountLabel}`);
const secret = base32Encode(crypto.randomBytes(length));
const account = {
label: accountLabel,
secret,
digits,
period,
algorithm: algo.label,
createdAt: new Date().toISOString(),
};
store.accounts.push(account);
saveStore(storePath, master, store);
const uri = makeOtpAuthUri(accountLabel, secret, digits, period, algo.label);
const now = totpNow(secret, digits, period, algo.node);
console.log(`Account added: ${accountLabel}`);
console.log(`Store file : ${storePath}`);
console.log(`Secret (base32): ${secret}`);
console.log(`otpauth URI : ${uri}`);
console.log(`Current code : ${now.current}`);
console.log(`Next code : ${now.next}`);
console.log(`Expires in : ${now.remaining}s`);
}
function cmdCode(options) {
const master = requireMaster(options);
const storePath = path.resolve(options.store || DEFAULT_STORE);
const accountLabel = validateLabel(options.account || "");
const store = loadStore(storePath, master);
const account = getAccount(store, accountLabel);
const algo = normalizeAlgorithm(account.algorithm);
const now = totpNow(account.secret, account.digits, account.period, algo.node);
console.log(`Account : ${account.label}`);
console.log(`Current code : ${now.current}`);
console.log(`Next code : ${now.next}`);
console.log(`Expires in : ${now.remaining}s`);
}
function cmdVerify(options) {
const master = requireMaster(options);
const storePath = path.resolve(options.store || DEFAULT_STORE);
const accountLabel = validateLabel(options.account || "");
const code = String(options.code || "");
const drift = parseDrift(options.drift || "1");
const store = loadStore(storePath, master);
const account = getAccount(store, accountLabel);
const algo = normalizeAlgorithm(account.algorithm);
const result = verifyTotp(account.secret, code, account.digits, account.period, algo.node, drift);
if (!result.valid) {
console.log("Verification: INVALID (expired or incorrect code)");
process.exitCode = 2;
return;
}
const position = result.windowOffset === 0 ? "current window" : `window offset ${result.windowOffset}`;
console.log(`Verification: VALID (${position})`);
}
function cmdList(options) {
const master = requireMaster(options);
const storePath = path.resolve(options.store || DEFAULT_STORE);
const store = loadStore(storePath, master);
if (store.accounts.length === 0) {
console.log("No accounts stored.");
return;
}
console.log(`Accounts in ${storePath}:`);
for (const account of store.accounts) {
console.log(`- ${account.label} | ${account.algorithm} | digits=${account.digits} | period=${account.period}s`);
}
}
function demo() {
const demoStore = path.resolve("totp_demo.enc.json");
const master = "demo-master-password";
const accountLabel = "DemoIssuer:demo.user@example.com";
if (fs.existsSync(demoStore)) fs.unlinkSync(demoStore);
cmdGenerate({
master,
store: demoStore,
account: accountLabel,
length: "20",
digits: "6",
period: "30",
algorithm: "SHA-1",
});
const store = loadStore(demoStore, master);
const account = getAccount(store, accountLabel);
const algo = normalizeAlgorithm(account.algorithm);
const now = totpNow(account.secret, account.digits, account.period, algo.node);
console.log(`\nDemo verify with correct code (${now.current})`);
cmdVerify({ master, store: demoStore, account: accountLabel, code: now.current, drift: "1" });
console.log("\nDemo verify with incorrect code (000000)");
cmdVerify({ master, store: demoStore, account: accountLabel, code: "000000", drift: "1" });
}
function usage() {
return [
"Usage:",
" node src/index.js generate --master <password> --account issuer:username [--length 20] [--digits 6|8] [--period 30|60] [--algorithm SHA-1|SHA-256|SHA-512] [--store path]",
" node src/index.js code --master <password> --account issuer:username [--store path]",
" node src/index.js verify --master <password> --account issuer:username --code <123456> [--drift 1] [--store path]",
" node src/index.js list --master <password> [--store path]",
"",
"No arguments runs a demo workflow.",
].join("\n");
}
function main() {
const { options, positional } = parseArgs(process.argv.slice(2));
if (positional.length === 0) {
demo();
return;
}
const cmd = positional[0].toLowerCase();
if (cmd === "generate") cmdGenerate(options);
else if (cmd === "code") cmdCode(options);
else if (cmd === "verify") cmdVerify(options);
else if (cmd === "list") cmdList(options);
else throw new Error(`Unknown subcommand: ${cmd}\n${usage()}`);
}
try {
main();
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.error(`Error: ${message}`);
process.exit(1);
}