{"record":{"id":"239209f074ab0179","repo":"gchq/CyberChef","slug":"err-tostring-239209","errorCode":null,"errorMessage":"${err.toString()}","messagePattern":"\\$\\{err\\.toString\\(\\)\\}","errorType":"exception","errorClass":"OperationError","httpStatus":null,"severity":"error","filePath":"src/core/operations/BcryptCompare.mjs","lineNumber":55,"sourceCode":"    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    async run(input, args) {\n        const hash = args[0];\n\n        let match;\n        try {\n            match = await bcrypt.compare(input, hash, undefined, p => {\n                // Progress callback\n                if (isWorkerEnvironment())\n                    self.sendStatusMessage(`Progress: ${(p * 100).toFixed(0)}%`);\n            });\n        } catch (err) {\n            throw new OperationError(err.toString());\n        }\n\n        return match ? \"Match: \" + input : \"No match\";\n\n    }\n\n}\n\nexport default BcryptCompare;\n","sourceCodeStart":37,"sourceCodeEnd":65,"githubUrl":"https://github.com/gchq/CyberChef/blob/4290ea753912378913b1f3f54e0fc5720afeda5d/src/core/operations/BcryptCompare.mjs#L37-L65","documentation":"Generic catch-all thrown by BcryptCompare.run when bcrypt.compare rejects its arguments rather than resolving to false. bcrypt.compare expects a valid bcrypt hash string as its second argument (the hash from args[0]); if that hash is malformed (wrong version prefix, bad cost, invalid base64 salt, truncated), bcrypt throws and the original Error.toString() is forwarded. A genuine non-match resolves to false and yields 'No match', not this error.","triggerScenarios":"Passing a hash that is not a $2a$/$2b$/$2y$ bcrypt string, a hash with a missing or invalid cost factor, a corrupted base64 salt section, or a hash truncated below the 60-character minimum.","commonSituations":"Comparing against an MD5/SHA hash by mistake; hash copied incompletely (bcrypt hashes are 60 chars); version prefix stripped during copy; whitespace/newline accidentally included in the stored hash.","solutions":["Ensure the hash argument is a full, valid bcrypt hash (e.g. $2b$12$... 60 characters).","Trim whitespace/newlines from the stored hash before comparing.","If you have a non-bcrypt hash, use the matching verify operation instead.","Distinguish this error (malformed hash) from a normal 'No match' result (valid hash, wrong password)."],"exampleFix":"// before - truncated/malformed hash\nchef.bcryptCompare(\"pw\", \"$2b$12$abc\");\n\n// after - full 60-char bcrypt hash\nchef.bcryptCompare(\"pw\", \"$2b$12$abcdefghijklmnopqrstuuVXQ1Nh2yTLB07h2yTLB07h2yTLB07h\");","handlingStrategy":"validation","validationCode":"const BCRYPT_HASH_RE = /^\\$2[aby]\\$\\d{2}\\$[./A-Za-z0-9]{53}$/;\nfunction assertBcryptHash(hash) {\n  const h = String(hash).trim();\n  if (!BCRYPT_HASH_RE.test(h)) {\n    throw new Error(\"Not a valid bcrypt hash (expected $2a/$2b/$2y$<cost>$<53 base64 chars>)\");\n  }\n  return h;\n}\nassertBcryptHash(hash);","typeGuard":"function isBcryptHash(s) {\n  return /^\\$2[aby]\\$\\d{2}\\$[./A-Za-z0-9]{53}$/.test(String(s).trim());\n}","tryCatchPattern":"try {\n  await chef.bcryptCompare(input, hash);\n} catch (e) {\n  if (/illegal|invalid|hash|salt/i.test(e.message)) {\n    throw new Error(`Stored hash is not a valid bcrypt hash: ${e.message}`);\n  }\n  throw e;\n}","preventionTips":["Store full 60-character bcrypt hashes only.","Trim whitespace/newlines from stored hashes before comparing.","Distinguish a thrown error (malformed hash) from a 'No match' result (valid hash, wrong input)."],"tags":["crypto","bcrypt","password","input-validation"],"backgroundTag":null,"analyzedSha":"4290ea753912378913b1f3f54e0fc5720afeda5d","analyzedAt":"2026-08-13T06:05:50.210Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}