{"record":{"id":"a2f86a02451eb989","repo":"gchq/CyberChef","slug":"byte-length-must-be-a-positive-integer-a2f86a","errorCode":null,"errorMessage":"Byte length must be a positive integer","messagePattern":"Byte length must be a positive integer","errorType":"validation","errorClass":"OperationError","httpStatus":null,"severity":"error","filePath":"src/core/lib/Modhex.mjs","lineNumber":133,"sourceCode":"\n/**\n * Convert a modhex string into a byte array.\n *\n * @param {string} data\n * @param {string} [delim]\n * @param {number} [byteLen=2]\n * @returns {byteArray}\n *\n * @example\n * // returns [10,20,30]\n * fromModhex(\"cl bf bu\");\n *\n * // returns [10,20,30]\n * fromModhex(\"cl:bf:bu\", \"Colon\");\n */\nexport function fromModhex(data, delim=\"Auto\", byteLen=2) {\n    if (byteLen < 1 || Math.round(byteLen) !== byteLen)\n        throw new OperationError(\"Byte length must be a positive integer\");\n\n    // The `.replace(/\\s/g, \"\")` an interesting workaround: Hex \"multiline\" tests aren't actually\n    // multiline. Tests for Modhex fixes that, thus exposing the issue.\n    data = data.toLowerCase().replace(/\\s/g, \"\");\n\n    if (delim !== \"None\") {\n        const delimRegex = delim === \"Auto\" ? /[^cbdefghijklnrtuv]/gi : Utils.regexRep(delim);\n        data = data.split(delimRegex);\n    } else {\n        data = [data];\n    }\n\n    let regularHexString = \"\";\n    for (let i = 0; i < data.length; i++) {\n        for (const letter of data[i].split(\"\")) {\n            regularHexString += HEX_ALPHABET_MAP[MODHEX_ALPHABET_MAP.indexOf(letter)];\n        }\n    }","sourceCodeStart":115,"sourceCodeEnd":151,"githubUrl":"https://github.com/gchq/CyberChef/blob/4290ea753912378913b1f3f54e0fc5720afeda5d/src/core/lib/Modhex.mjs#L115-L151","documentation":"fromModhex(data, delim, byteLen) converts a Modhex string (Yubico's cbdefghijklnrtuv alphabet) back to a byte array. byteLen controls the per-byte hex grouping handed to fromHex. The guard rejects byteLen values that are < 1 or non-integral before any parsing happens.","triggerScenarios":"Calling fromModhex(data, delim, 0), fromModhex(data, delim, -1), or fromModhex(data, delim, 1.5). Also triggered by accidentally passing a string or other non-integer that satisfies the math check incorrectly, or by leaving the second argument off and having delim shifted into byteLen.","commonSituations":"Wrong argument order (passing delim where byteLen goes); UI dropdown returning 0 when 'None' is selected; calculation that produces a fractional byteLen; default value misconfigured in a recipe import.","solutions":["Pass a positive integer for byteLen (typically 2): fromModhex(data, 'Auto', 2).","Coerce and clamp the value: Math.max(1, Math.floor(byteLen)) before calling.","Re-check argument order: signature is (data, delim, byteLen), not (data, byteLen, delim).","Validate in your UI layer that the byte-length selector cannot return 0 or non-integers."],"exampleFix":"// before\nfromModhex('cl bf bu', 2);          // 2 is treated as delim, byteLen defaults but...\nfromModhex('clbf', 'Auto', 0);       // byteLen=0 -> error\n\n// after\nfromModhex('cl bf bu', 'Auto', 2);   // explicit positive integer\nconst safeByteLen = Math.max(1, Math.floor(Number(byteLen) || 2));\nfromModhex(data, 'Auto', safeByteLen);","handlingStrategy":"validation","validationCode":"function safeByteLen(n) {\n  const i = Math.floor(Number(n));\n  return Number.isInteger(i) && i >= 1 ? i : 2;\n}\n\nfromModhex(data, 'Auto', safeByteLen(maybeByteLen));","typeGuard":"function isPositiveIntegerByteLen(x): x is number {\n  return typeof x === 'number' && Number.isInteger(x) && x >= 1;\n}","tryCatchPattern":"try {\n  return fromModhex(data, delim, byteLen);\n} catch (e) {\n  if (e instanceof OperationError && /positive integer/.test(e.message)) {\n    return fromModhex(data, delim, 2); // fall back to default\n  }\n  throw e;\n}","preventionTips":["Always pass an explicit positive integer for byteLen (typically 2).","Mind the argument order: (data, delim, byteLen).","Constrain UI inputs so the byte-length selector cannot return 0.","Coerce with Math.max(1, Math.floor(...)) before calling."],"tags":["encoding","modhex","yubico","validation","user-input"],"backgroundTag":null,"analyzedSha":"4290ea753912378913b1f3f54e0fc5720afeda5d","analyzedAt":"2026-08-13T06:05:50.210Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}