gchq/CyberChef · error · OperationError

Byte length must be a positive integer

Error message

Byte length must be a positive integer

What it means

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.

Source

Thrown at src/core/lib/Modhex.mjs:133

/**
 * Convert a modhex string into a byte array.
 *
 * @param {string} data
 * @param {string} [delim]
 * @param {number} [byteLen=2]
 * @returns {byteArray}
 *
 * @example
 * // returns [10,20,30]
 * fromModhex("cl bf bu");
 *
 * // returns [10,20,30]
 * fromModhex("cl:bf:bu", "Colon");
 */
export function fromModhex(data, delim="Auto", byteLen=2) {
    if (byteLen < 1 || Math.round(byteLen) !== byteLen)
        throw new OperationError("Byte length must be a positive integer");

    // The `.replace(/\s/g, "")` an interesting workaround: Hex "multiline" tests aren't actually
    // multiline. Tests for Modhex fixes that, thus exposing the issue.
    data = data.toLowerCase().replace(/\s/g, "");

    if (delim !== "None") {
        const delimRegex = delim === "Auto" ? /[^cbdefghijklnrtuv]/gi : Utils.regexRep(delim);
        data = data.split(delimRegex);
    } else {
        data = [data];
    }

    let regularHexString = "";
    for (let i = 0; i < data.length; i++) {
        for (const letter of data[i].split("")) {
            regularHexString += HEX_ALPHABET_MAP[MODHEX_ALPHABET_MAP.indexOf(letter)];
        }
    }

View on GitHub (pinned to 4290ea7539)

Solutions

  1. Pass a positive integer for byteLen (typically 2): fromModhex(data, 'Auto', 2).
  2. Coerce and clamp the value: Math.max(1, Math.floor(byteLen)) before calling.
  3. Re-check argument order: signature is (data, delim, byteLen), not (data, byteLen, delim).
  4. Validate in your UI layer that the byte-length selector cannot return 0 or non-integers.

Example fix

// before
fromModhex('cl bf bu', 2);          // 2 is treated as delim, byteLen defaults but...
fromModhex('clbf', 'Auto', 0);       // byteLen=0 -> error

// after
fromModhex('cl bf bu', 'Auto', 2);   // explicit positive integer
const safeByteLen = Math.max(1, Math.floor(Number(byteLen) || 2));
fromModhex(data, 'Auto', safeByteLen);
Defensive patterns

Strategy: validation

Validate before calling

function safeByteLen(n) {
  const i = Math.floor(Number(n));
  return Number.isInteger(i) && i >= 1 ? i : 2;
}

fromModhex(data, 'Auto', safeByteLen(maybeByteLen));

Type guard

function isPositiveIntegerByteLen(x): x is number {
  return typeof x === 'number' && Number.isInteger(x) && x >= 1;
}

Try / catch

try {
  return fromModhex(data, delim, byteLen);
} catch (e) {
  if (e instanceof OperationError && /positive integer/.test(e.message)) {
    return fromModhex(data, delim, 2); // fall back to default
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of gchq/CyberChef@4290ea7539 (2026-08-13). Data as JSON: /api/errors/a2f86a02451eb989. Report an issue: GitHub.