v3.2.0 release notes
Release Date: 2026-09-04
Upstream Base: CyberChef v11.4.0 (unchanged)
Licence: GPL-3.0-or-later
Node: >=24 <27
Image: ghcr.io/doublegate/cyberchef-mcp_v3:3.2.0
Highlights
Section titled “Highlights”Every gate in this project now does what it says it does, and the one documented claim that was a security claim has been corrected.
The release opened by capturing a full measurement baseline against the released v3.1.0 — throughput,
wire bytes, cold start, conformance, suites, coverage — before changing anything. That capture is
docs/internal/measurements/v3.1.0-baseline.md, and
it found four documented claims that were wrong, one of them about the attack surface of the
published image.
Eight findings in docs/internal/v3.2.0-findings-log.md.
The image is not shell-free
Section titled “The image is not shell-free”README and the wiki both said the image contains no shell, and README made it the argument: “the attack surface is the point rather than the size — no shell, no package manager.”
sh /usr/bin/sh apk absentash /usr/bin/ash wget absentbusybox /usr/bin/busybox curl absentnpm /usr/bin/npm BusyBox v1.38.0, ID=wolfiThe no package manager half is true. The no shell half is false, npm is present, and the
base is cgr.dev/chainguard/node:latest, not the distroless variant the docs called it.
If your threat model assumed no shell, it needs revisiting. Nothing about the image changed in this release — what changed is that the documentation now describes it. Whether to move to a genuinely shell-free base is a real question with real risk, and it belongs in a release that measures it.
Three further numbers were stale in live documents: image on disk (726 MB → 453 MB), the gzipped release tarball (~196 MB → 141 MB), and the production package count (432 → 402 or 384, depending on how you count — the defect there is a document quoting a number without its method).
Gates that can now fail
Section titled “Gates that can now fail”| Gate | Was | Now |
|---|---|---|
| Performance benchmarks | “cannot fail on a regression” — its own words | Compares median throughput to a committed baseline; fails on a regression and on a baselined task disappearing |
| Trivy, both workflows | CRITICAL only, with a TODO to restore |
CRITICAL,HIGH — the TODO’s stated precondition was met eight releases ago |
| Helm chart | nothing linted or rendered it | helm lint --strict plus four rendered values paths |
| Operation counts in prose | unchecked | read from OperationConfig.json by check:versions |
| Tool metadata from upstream | unchecked | 7,408 strings screened; blocking in the sync |
The benchmark tolerance was measured, and the first measurement was wrong
Section titled “The benchmark tolerance was measured, and the first measurement was wrong”A gate needs a tolerance and a tolerance has to be measured. The first study reported per-task spreads up to 84% — plausible on a JIT-compiled workload, and wide enough to justify a gate that could never fire. Two causes, both invisible until the numbers were collected rather than printed:
- A task-name collision.
SHA2is registered twice, for 256 and 512, and both emitted tasks calledSHA2 (1KB). The 84% “variance” was two operations taking turns in one slot. - JIT warm-up.
To Base64 (1KB)measured 7,482 ops/s in a cold process against 11,445–12,188 warm; it is the first benchmark registered, so it absorbs the warm-up.
Fixed, the worst per-task spread across four runs is 9.8% and the median 4.3% — which supported a 25% tolerance, until CI disagreed. Three runs on shared runners produced deltas from −25.5% to +99.1% and two false failures on different tasks, with nothing in the diff touching either. The tolerance is 50%: what a cross-machine baseline can honestly support, firing on factor-level regressions and not on the runner. The missing-task check stays exact.
And the gate’s reach is bounded, which is stated rather than left to be discovered. On its first green CI run the runner measured 27–99% faster than the machine the baseline came from. A regression smaller than that offset therefore passes on CI: the gate catches factor-level regressions there, and tolerance-level ones only on the baseline machine. Still strictly better than a check that said in its own output it could not fail — but “gates” and “gates against what” are different claims. A runner-captured baseline is carried forward.
The Trivy TODO
Section titled “The Trivy TODO”Two TODO(PR 7) comments — the only two in fork-owned code — narrowed Trivy to CRITICAL with the
condition restore once the dependency backlog is cleared to zero, written when that backlog was
272 findings. It reached zero in v2.1.1. Measured before restoring: 0 CRITICAL, 0 HIGH on the
published v3.1.0 image.
The PR gate was also quieter than its own report — the Security tab listed HIGHs the whole time while a HIGH could still merge, and the difference between reported and gated is the only part that changes what ships.
Metadata a reviewer cannot see
Section titled “Metadata a reviewer cannot see”Every tool name and description a model reads comes from a tree mirrored verbatim from upstream, and the fork’s review of a sync is a diff review — so the one thing a reviewer cannot catch is the one thing worth screening for. A Unicode TAG-block sequence renders as nothing in a diff.
7,408 strings, four classes screened (TAG-block, control, bidi, zero-width), all zero today. Ordinary non-ASCII — 89 strings with accents and Greek letters — is tolerated deliberately: a rule banning it would fail the first sync adding a cipher name with a diacritic, and then the rule gets removed rather than the metadata fixed.
cyberchef_search is 88.6% smaller
Section titled “cyberchef_search is 88.6% smaller”The planned feature was a response_format: concise | detailed enum across the high-volume tools,
citing a published 65% reduction. Measuring this catalogue first said the plan was aimed at the
wrong tools:
short transform 55 bytesdescribe one operation 3,115 <- the median resultlist one category 18,057search 27,060 <- the outlierlarge transform (64 KB in) 87,423 <- the payload IS the answerA format flag across 504 operation tools buys nothing; their results are the data the caller asked
for. The outlier was cyberchef_search, and a generic flag would not have fixed it: it returned the
full OperationConfig entry per match — more than cyberchef_describe_operation returns for the
same operations. The discovery tool was paying the detail tool’s cost, for operations the caller
had not chosen yet.
"base64" 14 matches 27,060 -> 3,087 bytes (-88.6%)Search now returns names, one-line summaries and an argument count, in the same shape
cyberchef_list_operations uses. detailed: true restores the previous payload — opt-in, because
the default is what every model pays, and the tool description says what the flag costs.
Upgrading
Section titled “Upgrading”docker pull ghcr.io/doublegate/cyberchef-mcp_v3:3.2.0One compatibility change, and calling it anything softer would be wrong. cyberchef_search
returns a different default shape:
before [ {name, module, description, infoURL, inputType, outputType, args, ...}, ... ]after { query, matches, operations: [{operation, summary, args}], next }A caller that parses the previous default must either adapt or pass detailed: true, which
returns the old payload unchanged. Nothing sends that flag automatically, so existing parsers do
break until one of those two things happens.
It ships in a MINOR because the opt-in restores the old contract exactly and because an MCP tool result is consumed by a model rather than by a typed client — but the honest description is a response-contract change with an escape hatch, not “additive”. A caller that reads the result as prose, which is the common case, is unaffected and gets a smaller one.
Nothing else in this release changes observable behaviour.
Verification
Section titled “Verification”npm run lint zero errorsnpm run check:versions versions, package majors and operation counts agreenpm test 241 Node-API + 2,289 operation testsnpm run test:mcp 1,437 tests across 55 filesnpm run test:coverage 95.47 lines / 89.89 branches / 96.41 functions / 96.20 statementsnpm run conformance both eras match the baselinenpm run benchmark:check no regression against the committed baselineEvery gate added here was proven in both directions before it was committed: the benchmark gate by perturbing the baseline, the count gate by drifting a document, the metadata screens against synthesised positives. A gate that has never been seen to fail is a gate nobody has tested.