Skip to content

v2.1.0 release notes

Release Date: 2026-08-31 Upstream Base: CyberChef v11.4.0 (unchanged) Licence: GPL-3.0-or-later Node: >=24 <27

v2.0.0 shipped 524 tools. This release is what happened when they were actually used.

Every finding below came from smoke-testing the published v2.0.0 image — calling all 524 tools in turn, and driving the server with a real MCP SDK client rather than the hand-rolled JSON-RPC every existing test used. The two approaches disagree, and that disagreement was the whole problem: a hand-rolled request proves the server answers; only a client that enforces the protocol proves the answer is usable.

v2.0.0 v2.1.0
Tools with a usable input schema 0 of 524 524 of 524
Symmetric ciphers callable (AES, DES, …) 0 of 31 31 of 31
Operations taking a key/IV 0 of 63 63 of 63
Flow-control tools that work 0 of 10 10 of 10
Tools that crash the whole server 1 0
tools/list payload ~52,000 tokens (describing nothing) ~2,500
Process exit after a request 61,318 ms 1,275 ms
MCP tests 757 798

zod-to-json-schema@3 targets Zod v3 and fails silently against Zod v4. Zod 4 arrived in v1.8.0. From that release onward, every tool this server advertised carried:

{"$schema": "http://json-schema.org/draft-07/schema#"}

No type. No properties. No required. No error, no warning — Zod 4 restructured the internals the converter introspects, so it found nothing and emitted nothing.

Raw JSON-RPC does no schema validation, which is why 524 tools “listed fine” for three releases. The official MCP SDK client rejects the whole response:

524 × invalid_value at tools[N].inputSchema.type — expected "object"

Confirmed against the published images: 483/483 empty on v1.9.0, 524/524 on v2.0.0. A lenient client fared no better in practice — it showed the model tools whose arguments it could not see.

Fixed with Zod 4’s native z.toJSONSchema(); zod-to-json-schema is dropped.

31 symmetric ciphers, including AES, could never succeed

Section titled “31 symmetric ciphers, including AES, could never succeed”

AES, DES, Triple DES, Blowfish, ChaCha, RC2, RC6, SM4, PRESENT, Ascon and Rabbit each declare an argument literally named Input — the input format, Raw or Hex. Sanitised it became input, which the schema then overwrote with the data parameter. The operation therefore received the message text where it expected "Raw":

Input must be one of the following: Raw, Hex.

on every call. Colliding names are now suffixed — input_arg — and the sanitisation lives in one shared toolArgName() rather than the three subtly different copies it had grown.

toggleString arguments carry a value and the encoding it is written in. They were advertised as plain strings while the operation destructures {option, string}, so all 63 failed identically — Cannot read properties of undefined (reading 'option') — supplied or not. Both forms now work:

"key": "00112233445566778899aabbccddeeff"
"key": { "string": "hunter2", "option": "UTF8" }

cyberchef_magic, _fork, _merge, _jump, _conditional_jump, _label, _register, _subsection, _comment, _return — all flow-control operations, all refused by the Node API wrapper:

flowControl operations like Magic are not currently allowed in recipes for chef.bake in the Node API

That is not a property of the operations. src/core/Recipe.mjs runs flow control properly, assembling the opList / numJumps / numRegisters / forkOffset state they need — state NodeRecipe never builds. Recipes now execute on that engine, and all ten behave as they do in the web UI. Verified individually: Fork splits and merges, Return halts the recipe, Jump skips operations, Register substitutes $R0, Subsection applies its branch only to matched regions.

Magic mattered most: it is the correct first move on unknown data, and it failed every time.

One tool call could kill the server for everybody

Section titled “One tool call could kill the server for everybody”

argon2-browser fetches its .wasm by filesystem path, which Node’s fetch rejects — and jq-web’s Emscripten runtime installs a process-wide unhandledRejection handler that calls abort(). So cyberchef_argon2_compare terminated the process. In the all-tools sweep, the 484 tools after it all reported “Not connected”.

Upstream hit this and fixed it for tests only (tests/lib/wasmFetchPolyfill.mjs), which is exactly why the suite passed while the shipped server crashed. The equivalent now lives in the MCP layer, where no upstream sync can remove it.

Two leaked timers, both armed on every request:

  • executeWithTimeoutAndRetry built its Promise.race timeout without keeping the handle, so a 30s timer stayed armed after the operation had already answered. Promise.race does not cancel the loser.
  • The logger’s request-context sweeper was never unref’d.

A call that answered in 1,259 ms held the process open until 61,318 ms. Now 1,275 ms. The shell example that surfaced it went from 187.6 s to 6.4 s.

175 of 504 operations declare a non-string output type, and results were serialised with JSON.stringify(value). From Base64 of SGVsbG8sIENoZWYh returned:

[72,101,108,108,111,44,32,67,104,101,102,33]

Valid JSON, and useless. Results are now presented as the CyberChef UI presents them, and the 61 html-output operations are converted to plain text the same way the Node API already does it.

  • Every log line went to stdout, which the MCP stdio transport reserves for JSON-RPC. The code carried a comment claiming it wrote to stderr; nothing implemented it (pino defaults to fd 1).

  • Generate all hashes silently returned its input — its NTLM/LM step needs OpenSSL’s legacy provider, and the operation swallowed the failure. --openssl-legacy-provider is now set by npm run mcp and by the image’s NODE_OPTIONS.

    Correction (v2.2.0). This fix did not work, and the claim above is wrong. The flag was set, but the runtime image carries no legacy provider module to load — Node printed Unable to load legacy provider. on every start and Generate all hashes still failed, now with error:0308010C rather than silently. One operation was responsible, LM Hash, and v2.2.0 removes the OpenSSL dependency entirely by computing its DES step in JavaScript. See docs/internal/v2.2.0-findings-log.md (F-05).

  • cyberchef_batch destroyed its own error message — its catch block called .length on JSON.stringify(undefined).

  • A flaky CipherSaber2 test that counted characters of random bytes.

Fixing the schemas made the tools usable and revealed their true price at once: correct schemas for 524 tools are ~86,000 tokens, sent on every request before the user has typed anything.

So tools/list is an index by default:

cyberchef_categories 16 categories, counts and examples ~2 KB
cyberchef_list_operations the operations in one category ~8 KB for 50
cyberchef_describe_operation full argument schema for the ones chosen ~1.6 KB each
cyberchef_bake runs any of the 504, by name
CYBERCHEF_TOOL_SURFACE Tools Tokens
index (default) ~24 ~2,500
curated ~100 ~16,600
all 524 ~86,000

Nothing is unreachable, and that is verified rather than asserted: walking every category reaches 504/504 operations and describes 504/504, with zero orphans and zero phantom entries. cyberchef_bake runs any of them whether or not they appear in tools/list.

Magic is exposed in every surface, including index — it is what you reach for before you know what you are looking at, so burying it three calls deep would invert the cost.

This is the one visible change. A client that hard-codes a tool name outside the index will no longer find it in tools/list. Two one-line remedies: CYBERCHEF_TOOL_SURFACE=all, or call the operation through cyberchef_bake.

  • Tutorial — a guided first hour, install to real sample.
  • User Guide — rewritten. It now documents every environment variable the code reads; it previously listed 7 of about 30, and named one of them wrongly (ENABLE_WORKERS, which has always been CYBERCHEF_ENABLE_WORKERS).
  • examples/ — eight runnable, self-asserting scripts covering quickstart, recipes, discovery, forensic triage, saved recipes, batching, multi-client HTTP and shell usage.

The examples are executed by tests/mcp/examples.test.mjs on every change. Documentation that is not run drifts, and this project has the receipt: three documents asserted a ReDoS mitigation was active for four releases after a sync had silently removed it.

Suite What it pins
stdio-client-contract.test.mjs A real MCP SDK client over a real child process. Both the schema and the stdout defects were invisible to every existing test because they all spoke raw JSON-RPC.
tool-surface.test.mjs The three surfaces, and the exhaustive 504/504 reachability proof.
examples.test.mjs Every example, discovered rather than listed, so a new one is covered automatically.

798 MCP tests across 25 files; 241 Node-API and 2,289 operation tests unchanged and green.

Taken: @alexaltea/capstone-js 3.0.5 → 5.0.9 (migrated Disassemble ARM to the 5.x WASM module factory), Chainguard base image digests.

Held, with the evidence recorded in .github/dependabot.yml so they stop being re-proposed:

  • @xmldom/xmldom 0.9 — both mechanical migrations were applied and tested; the resulting DOM still differs enough that CSS selector returns 0 matches and XPath expression 1 of 2.
  • geodesy 2.x — upstream’s own comment blames “cannot load .js modules into a .mjs file”, which is stale (geodesy 2 is "type": "module"). The real blocker is that v2’s LatLon subclasses do not compose: utm.js gives one with toUtm and no toOsGrid, osgridref.js the reverse, and ConvertCoordinates.mjs needs both on one object.
  • jq-web 0.6 — its module namespace exports then, making it thenable; await import() of a thenable module throws, breaking 10 test files.
Terminal window
docker pull ghcr.io/doublegate/cyberchef-mcp_v2:latest

No configuration change is required. If your client hard-codes tool names outside the index, set CYBERCHEF_TOOL_SURFACE=all — or move those calls to cyberchef_bake, which is cheaper and works in every surface.

npm run lint 0 errors
npx vitest run tests/mcp/ 798 passed (25 files)
tests/operations 2289 passed
tests/node 241 passed
npm run testnodeconsumer green
all-524-tool sweep 491 succeed; the rest correctly reject the nonsense input
hierarchy reachability 504/504 reachable, 504/504 describable, 0 orphans
examples/ 8/8 run clean, executed by CI