ErrLookup › Background articles › OperationError in CyberChef — "Unknown algorithm version", "Invalid encoding", "Error loading image": one error class behind every operation failure
OperationError in CyberChef — "Unknown algorithm version", "Invalid encoding", "Error loading image": one error class behind every operation failure
OperationError is the single error class CyberChef throws for every operation failure — "Unknown algorithm version", "Invalid encoding", "Error loading image. (${err})", "Invalid key length", and hundreds more. A developer meets it when a recipe or chef.bake / Recipe.run call passes an argument that misses a constrained option list, supplies malformed or truncated input, or triggers an underlying decoder or cipher that rejects the data. This article explains the shared mechanism behind the messages and the fixes that hold across the whole family.
Distilled from 585 documented records across 2 repositories.
Background
CyberChef is built from operations, and every operation contracts to one of two outcomes: it returns its output, or it throws an OperationError. The class is the universal error channel for the operation layer; it is what surfaces in the browser output pane and what a Node-API caller catches from chef.bake or Recipe.run. The host never sees a raw exception from a dependency — operations catch their internals and re-throw as OperationError so a bad input cannot crash the recipe. In the documented family the thrown text takes one of two shapes. The first is a hand-written precondition or validation guard written for that operation: "Unknown algorithm version: ${version}", "Invalid encoding", "The values of a and b can only be integers.", "Plugboard wiring must be 26 unique uppercase letters". The second is a catch-all wrapper that interpolates the underlying library's own message through a template: "Error loading image. (${err})", "Error: ${err.message}", "${err.toString()}", "${error}". The wrapper shape deliberately collapses many internal failure modes into one surfaced message, so the real diagnostic is often the text inside the ${...} placeholder rather than the fixed prose around it.
Why the class exists is tied to how operations receive their arguments. In the browser UI most arguments come from constrained dropdowns (an argSelector or option list), so a value that the operation would reject is effectively unreachable — the user can only pick a declared option. The same guards become live as soon as the operation is driven another way: the Node API, an imported .chr recipe JSON, or a hand-edited opList can pass any string or number, and the operation's internal switch or regex then falls through to its default case and throws. A large share of the documented messages — "Unknown algorithm version", "Unknown input format", "Lorenz model type not recognised", "Unrecognised unit", "Invalid encoding" — exist almost exclusively for this programmatic path, because a label has drifted by case, spelling, trailing space, or CyberChef version.
From the caller's side the family has a consistent feel even when the underlying cause differs. Input-shape failures (wrong charset, wrong length, a letter reused in a bijective map, a non-integer where modular arithmetic expects one) carry precise, hand-written messages that name the violated constraint. Binary-length failures ("Need at least 20 bytes for a TCP Header", "Incorrect input length. Must be a multiple of the block size.", "Invalid key length: ${key.length} bytes") are precondition guards that fire before any real parsing, often because an earlier step truncated the data or the wrong input format was selected. Decode-dependent operations (DitherImage, OpticalCharacterRecognition, AsconDecrypt, BcryptCompare, Argon2, Protobuf Encode) wrap a third-party library and forward its rejection, frequently after only a shallow pre-check — CyberChef's isImage sniff validates magic bytes, not structural integrity, so a header-valid but body-corrupt file still reaches the decoder and fails there.
The documented family is large — 585 records across two repositories — but the best-documented records are all from gchq/CyberChef, and within CyberChef the pattern above is uniform: one class, two message shapes, and a consistent split between hand-written guards and interpolated wrappers. Where the same OperationError name appears in the second repository, message text, trigger conditions, and recoverability are library-specific rather than shared; the cross-family lessons here are the structural ones — normalisation of internal failures, argument provenance, and validation at the trust boundary — not the literal message strings.
Common causes
- Argument string misses a constrained option list.The most common trigger in the family. An argument that must equal one of a declared set of labels — GOST algorithm version, Lorenz model (SZ40/SZ42a/SZ42b), coordinate format, output unit, or text encoding — is misspelled, wrongly cased, trailing-spaced, or carried over from an older CyberChef version. The operation's switch hits its default case and throws. This is rarely reachable in the browser UI, where the value comes from a dropdown; it appears via chef.bake / Recipe.run or imported recipe JSON where any string can be supplied.
- Malformed or wrong-type input data.The input fails an operation-specific regex or type check: a cipher a/b argument that is fractional or negative, a plugboard wiring that is not 26 unique uppercase letters, a Bifid keyword containing non-A-Z characters, a Base64 string with out-of-alphabet characters, or a Lorenz chi-1 lug string of the wrong length or alphabet. The guard is hand-written and names the exact constraint that failed.
- Underlying library or decoder failure, re-wrapped.An operation delegates to a third-party library and catches any exception it raises, forwarding the original message through a template — Jimp image decode (DitherImage), Tesseract.js OCR, argon2, bcrypt.compare, or Protobuf.encode. The surfaced OperationError is generic prose; the real cause (missing OCR assets, malformed bcrypt hash, schema/type mismatch) is inside the interpolated ${err}. A shallow pre-check such as the isImage magic-byte sniff can pass while the full decode still fails.
- Truncated or length-mismatched binary input.Binary material is the wrong length for the operation's contract: fewer than 20 bytes for a Parse TCP header, ciphertext not a multiple of the TEA 8-byte block in ECB/CBC, a wrapped key not a multiple of the GOST block size, or an Ascon key that is not exactly 16 bytes. These are precondition guards that fire before parsing, frequently because a prior step truncated the data or the wrong input-format argument was selected.
- Structural encoding errors.The input is structurally invalid for its declared encoding: a Bech32 string with no '1' separator, Base64 with padding in the wrong position, a Flask session cookie whose payload segment is not URL-safe Base64, or an HOTP secret that is not valid RFC 4648 base32. The decoder rejects the shape before any cryptographic or semantic work begins.
- Cryptographic authentication or tag mismatch.An authenticated cipher such as Ascon-AEAD128 verifies a tag over ciphertext and associated data; a wrong key, wrong or modified nonce, mismatched associated data (empty vs absent), or a single bit flip causes the library to throw rather than emit garbage. The operation collapses every internal reason into a single authentication-failure message, so the specific cause is hidden by design.
What usually fixes it
- Source argument strings from declared option metadata, never hand-type them. For CyberChef, read allowed values from the operation's args definition (e.g. GOSTDecrypt.args[4].value[].name, GOSTEncrypt.args[4].value[].name, Object.keys(CHR_ENC_CODE_PAGES) for encodings) and validate imported recipe JSON against the current build's lists. Regenerate older recipe steps in the UI after upgrading so stored labels match the installed version.
- Validate at the trust boundary before invoking an operation. Apply the operation's own constraints early — /^[A-Z]{26}$/ plus a uniqueness check for Typex wiring, Number.isInteger(v) && v >= 0 for affine a/b, /^[A-Z2-7]+$/ for an HOTP secret, decoded byte length >= 20 for Parse TCP, block-size multiplicity for TEA/GOST unwrap — so errors are caught with context rather than inside the operation.
- Read the interpolated underlying message. When the thrown text contains ${err}, ${err.message}, ${error}, or ${err.toString()}, the fixed prose is generic; the real diagnosis (which codec failed, which .proto field has the wrong type, which argon2 parameter was out of range, whether a fetch of OCR assets 404'd) is in the substituted portion.
- Match both ends of an operation pair. Use the same algorithm, mode, key length, encoding, and associated-data convention on encrypt and decrypt (or encode and decode). Store the mode alongside ciphertext, keep AD identical including empty-vs-absent, and re-derive bytes from hex/base64 immediately before use rather than passing slices of larger buffers.
- Treat cryptographic authentication failures as definitive. An Ascon-AEAD128 auth-failure is authoritative — do not retry with tweaked parameters hoping for output, and never surface partial plaintext. Re-verify key, nonce, associated data, and that the full ciphertext including the trailing tag was copied without truncation.
- Do not trust a magic-byte sniff alone. Operations such as DitherImage and OCR check isImage (signature only) before handing data to a full decoder; a header-valid but body-corrupt file passes the sniff and fails the decode. Pre-validate by attempting a full decode, and avoid chaining byte-mutating operations immediately before decode-dependent ones.
Go deeper
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Documented occurrences
- Unknown algorithm version: ${version}(gchq/CyberChef)
- err.message.replace("Rotor", "Plugboard")(gchq/CyberChef)
- Error loading image. (${err})(gchq/CyberChef)
- Unknown input format '${inFormat}'(gchq/CyberChef)
- Error performing OCR on image. (${err})(gchq/CyberChef)
- Unable to decrypt: authentication failed. The ciphertext, key, nonce, or associated data may be incorrect or tampered with.(gchq/CyberChef)
- Lorenz model type not recognised(gchq/CyberChef)
- CIDR must be less than 32 for IPv4 or 128 for IPv6(gchq/CyberChef)
- ${name} connects ${pair[1]} more than once(gchq/CyberChef)
- The values of a and b can only be integers.(gchq/CyberChef)
- Plugboard wiring must be 26 unique uppercase letters(gchq/CyberChef)
- Invalid ITA2 character : ${errltr}(gchq/CyberChef)
- Unknown algorithm version: ${version}(gchq/CyberChef)
- The key must consist only of letters in the English alphabet(gchq/CyberChef)
- Invalid encoding(gchq/CyberChef)
- Error: ${err.message}(gchq/CyberChef)
- Error: Base64 input contains non-alphabet char(s)(gchq/CyberChef)
- Incorrect input length. Must be a multiple of the block size.(gchq/CyberChef)
- Invalid Base64 payload(gchq/CyberChef)
- Need at least 20 bytes for a TCP Header(gchq/CyberChef)
…and 565 more across the corpus — use search.
Honest provenance: generated on 2026-08-13 from AI-assisted analysis of the linked records. See how records are made.