cyberchef 10.24.0 → 11.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@
3
3
  {
4
4
  "name": "CyberChef",
5
5
  // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
6
- "image": "mcr.microsoft.com/devcontainers/javascript-node:1-18-bookworm",
6
+ "image": "mcr.microsoft.com/devcontainers/javascript-node:24-trixie",
7
7
 
8
8
  // Features to add to the dev container. More info: https://containers.dev/features.
9
9
  "features": {
package/.nvmrc CHANGED
@@ -1 +1 @@
1
- 18
1
+ 24
package/CHANGELOG.md CHANGED
@@ -13,6 +13,22 @@ All major and minor version changes will be documented in this file. Details of
13
13
 
14
14
  ## Details
15
15
 
16
+ ## [11.0.0] - 2026-04-28
17
+ - Revert sitemap to v8.0.X to fix build/deploy on master [@GCHQDeveloper581] | [#2348]
18
+ - Node version update from 22 to 24 [@lzandman] [@GCHQDeveloper581] | [#2347]
19
+ - Fix XSS in Show Base64 offsets [@C85297] | [#2346]
20
+ - Make compatible with node >=22 [@GCHQDeveloper581] | [#2273]
21
+ - Fix(node): enable asynchronous operation support in Node.js API [@engin0223] [@GCHQDeveloper581] | [#2342]
22
+ - Feature: Change to nginx-unprivileged image for better kubernetes support [@hsolberg] | [#1922]
23
+
24
+ Breaking changes:
25
+ - Minimum supported node version - now v24 (was v16)
26
+ - Change of exported port on Docker Container - now 8080 (was 80)
27
+ - Node API now exports "bake" and "execute" functions as async.
28
+
29
+ <details>
30
+ <summary>Click to expand v10 minor versions</summary>
31
+
16
32
  ### [10.24.0] - 2026-04-27
17
33
  - Update CONTRIBUTING.md [@GCHQDeveloper581] | [#2333]
18
34
  - Fix, and link, Fernet tests [@GCHQDeveloper581] | [#2335]
@@ -277,6 +293,8 @@ All major and minor version changes will be documented in this file. Details of
277
293
  - Added 'Levenshtein Distance' operation [@mikecat] | [#1498]
278
294
  - Added 'Swap case' operation [@mikecat] | [#1499]
279
295
 
296
+ </details>
297
+
280
298
  ## [10.0.0] - 2023-03-22
281
299
  - [Full details explained here](https://github.com/gchq/CyberChef/wiki/Character-encoding,-EOL-separators,-and-editor-features)
282
300
  - Status bars added to the Input and Output [@n1474335] | [#1405]
@@ -620,6 +638,7 @@ All major and minor version changes will be documented in this file. Details of
620
638
  ## [4.0.0] - 2016-11-28
621
639
  - Initial open source commit [@n1474335] | [b1d73a72](https://github.com/gchq/CyberChef/commit/b1d73a725dc7ab9fb7eb789296efd2b7e4b08306)
622
640
 
641
+ [11.0.0]: https://github.com/gchq/CyberChef/releases/tag/v11.0.0
623
642
  [10.24.0]: https://github.com/gchq/CyberChef/releases/tag/v10.24.0
624
643
  [10.23.0]: https://github.com/gchq/CyberChef/releases/tag/v10.23.0
625
644
  [10.22.0]: https://github.com/gchq/CyberChef/releases/tag/v10.22.0
@@ -901,6 +920,9 @@ All major and minor version changes will be documented in this file. Details of
901
920
  [@BjoernAkAManf]: https://github.com/BjoernAkAManf
902
921
  [@ko80240]: https://github.com/ko80240
903
922
  [@BigYellowHammer]: https://github.com/BigYellowHammer
923
+ [@hsolberg]: https://github.com/hsolberg
924
+ [@lzandman]: https://github.com/lzandman
925
+ [@engin0223]: https://github.com/engin0223
904
926
 
905
927
 
906
928
  [8ad18b]: https://github.com/gchq/CyberChef/commit/8ad18bc7db6d9ff184ba3518686293a7685bf7b7
@@ -1192,4 +1214,10 @@ All major and minor version changes will be documented in this file. Details of
1192
1214
  [#2315]: https://github.com/gchq/CyberChef/pull/2315
1193
1215
  [#2313]: https://github.com/gchq/CyberChef/pull/2313
1194
1216
  [#2311]: https://github.com/gchq/CyberChef/pull/2311
1217
+ [#2348]: https://github.com/gchq/CyberChef/pull/2348
1218
+ [#2347]: https://github.com/gchq/CyberChef/pull/2347
1219
+ [#2346]: https://github.com/gchq/CyberChef/pull/2346
1220
+ [#2273]: https://github.com/gchq/CyberChef/pull/2273
1221
+ [#2342]: https://github.com/gchq/CyberChef/pull/2342
1222
+ [#1922]: https://github.com/gchq/CyberChef/pull/1922
1195
1223
 
package/Dockerfile CHANGED
@@ -4,7 +4,7 @@
4
4
  # Modifier --platform=$BUILDPLATFORM limits the platform to "BUILDPLATFORM" during buildx multi-platform builds
5
5
  # This is because npm "chromedriver" package is not compatiable with all platforms
6
6
  # For more info see: https://docs.docker.com/build/building/multi-platform/#cross-compilation
7
- FROM --platform=$BUILDPLATFORM node:18-alpine AS builder
7
+ FROM --platform=$BUILDPLATFORM node:24-alpine AS builder
8
8
 
9
9
  WORKDIR /app
10
10
 
@@ -27,7 +27,7 @@ RUN npm run build
27
27
  #########################################
28
28
  # Package static build files into nginx #
29
29
  #########################################
30
- FROM nginx:stable-alpine AS cyberchef
30
+ FROM nginxinc/nginx-unprivileged:stable-alpine AS cyberchef
31
31
 
32
32
  LABEL maintainer="GCHQ <oss@gchq.gov.uk>"
33
33
 
package/Gruntfile.js CHANGED
@@ -6,7 +6,7 @@ const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPl
6
6
  const glob = require("glob");
7
7
  const path = require("path");
8
8
 
9
- const nodeFlags = "--experimental-modules --experimental-json-modules --experimental-specifier-resolution=node --no-warnings --no-deprecation";
9
+ const nodeFlags = "--no-warnings --no-deprecation";
10
10
 
11
11
  /**
12
12
  * Grunt configuration for building the app in various formats.
package/README.md CHANGED
@@ -36,7 +36,7 @@ docker build --tag cyberchef --ulimit nofile=10000 .
36
36
  ```
37
37
  2. Run the docker container
38
38
  ```bash
39
- docker run -it -p 8080:80 cyberchef
39
+ docker run -it -p 8080:8080 cyberchef
40
40
  ```
41
41
  3. Navigate to `http://localhost:8080` in your browser
42
42
 
@@ -45,7 +45,7 @@ docker run -it -p 8080:80 cyberchef
45
45
  If you prefer to skip the build process, you can use the pre-built image
46
46
 
47
47
  ```bash
48
- docker run -it -p 8080:80 ghcr.io/gchq/cyberchef:latest
48
+ docker run -it -p 8080:8080 ghcr.io/gchq/cyberchef:latest
49
49
  ```
50
50
 
51
51
  Just like before, navigate to `http://localhost:8080` in your browser.
@@ -120,7 +120,7 @@ CyberChef is built to support
120
120
 
121
121
  ## Node.js support
122
122
 
123
- CyberChef is built to fully support Node.js `v16`. For more information, see the ["Node API" wiki page](https://github.com/gchq/CyberChef/wiki/Node-API)
123
+ CyberChef is built to fully support Node.js `v24`. For more information, see the ["Node API" wiki page](https://github.com/gchq/CyberChef/wiki/Node-API)
124
124
 
125
125
 
126
126
  ## Contributing
package/babel.config.js CHANGED
@@ -16,6 +16,9 @@ module.exports = function(api) {
16
16
  "regenerator": true
17
17
  }
18
18
  ]
19
- ]
19
+ ],
20
+ "generatorOpts": {
21
+ "importAttributesKeyword": "with"
22
+ }
20
23
  };
21
24
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cyberchef",
3
- "version": "10.24.0",
3
+ "version": "11.0.0",
4
4
  "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
5
5
  "author": "n1474335 <n1474335@gmail.com>",
6
6
  "homepage": "https://gchq.github.io/CyberChef",
@@ -36,7 +36,7 @@
36
36
  "browserslist": [
37
37
  "Chrome >= 50",
38
38
  "Firefox >= 38",
39
- "node >= 16"
39
+ "node >= 24"
40
40
  ],
41
41
  "devDependencies": {
42
42
  "@babel/eslint-parser": "^7.28.6",
@@ -55,14 +55,14 @@
55
55
  "chromedriver": "^146.0.6",
56
56
  "cli-progress": "^3.12.0",
57
57
  "colors": "^1.4.0",
58
- "compression-webpack-plugin": "^11.1.0",
59
- "copy-webpack-plugin": "^13.0.1",
58
+ "compression-webpack-plugin": "^12.0.0",
59
+ "copy-webpack-plugin": "^14.0.0",
60
60
  "core-js": "^3.49.0",
61
- "cspell": "^8.19.4",
62
- "css-loader": "7.1.4",
61
+ "cspell": "^9.7.0",
62
+ "css-loader": "^7.1.4",
63
63
  "eslint": "^9.39.4",
64
64
  "eslint-plugin-jsdoc": "^50.8.0",
65
- "globals": "^15.15.0",
65
+ "globals": "^17.4.0",
66
66
  "grunt": "^1.6.2",
67
67
  "grunt-chmod": "~1.1.1",
68
68
  "grunt-concurrent": "^3.0.0",
@@ -87,8 +87,8 @@
87
87
  "sitemap": "^8.0.3",
88
88
  "terser": "^5.46.2",
89
89
  "webpack": "^5.106.2",
90
- "webpack-bundle-analyzer": "^4.10.2",
91
- "webpack-dev-server": "5.0.4",
90
+ "webpack-bundle-analyzer": "^5.0.0",
91
+ "webpack-dev-server": "^5.0.4",
92
92
  "webpack-node-externals": "^3.0.0",
93
93
  "worker-loader": "^3.0.8"
94
94
  },
@@ -164,11 +164,12 @@
164
164
  "notepack.io": "^3.0.1",
165
165
  "ntlm": "^0.1.3",
166
166
  "nwmatcher": "^1.4.4",
167
- "otpauth": "9.3.6",
167
+ "otpauth": "^9.5.0",
168
168
  "path": "^0.12.7",
169
169
  "popper.js": "^1.16.1",
170
170
  "process": "^0.11.10",
171
171
  "protobufjs": "^7.5.5",
172
+ "punycode.js": "^2.3.1",
172
173
  "qr-image": "^3.2.0",
173
174
  "reflect-metadata": "^0.2.2",
174
175
  "rison": "^0.1.1",
@@ -194,18 +195,21 @@
194
195
  "start": "npx grunt dev",
195
196
  "build": "npx grunt prod",
196
197
  "node": "npx grunt node",
197
- "repl": "node --experimental-modules --experimental-json-modules --experimental-specifier-resolution=node --no-experimental-fetch --no-warnings src/node/repl.mjs",
198
- "test": "npx grunt configTests && node --experimental-modules --experimental-json-modules --no-warnings --no-deprecation --openssl-legacy-provider --no-experimental-fetch tests/node/index.mjs && node --experimental-modules --experimental-json-modules --no-warnings --no-deprecation --openssl-legacy-provider --no-experimental-fetch --trace-uncaught tests/operations/index.mjs",
198
+ "repl": "node --no-warnings src/node/repl.mjs",
199
+ "test": "npx grunt configTests && node --no-warnings --no-deprecation --openssl-legacy-provider tests/node/index.mjs && node --no-warnings --no-deprecation --openssl-legacy-provider --trace-uncaught tests/operations/index.mjs",
199
200
  "testnodeconsumer": "npx grunt testnodeconsumer",
200
201
  "testui": "npx grunt testui",
201
202
  "testuidev": "npx nightwatch --env=dev",
202
203
  "lint": "npx grunt lint",
203
204
  "lint:grammar": "cspell ./src",
204
205
  "postinstall": "npx grunt exec:fixCryptoApiImports && npx grunt exec:fixSnackbarMarkup",
205
- "newop": "node --experimental-modules --experimental-json-modules src/core/config/scripts/newOperation.mjs",
206
- "minor": "node --experimental-modules --experimental-json-modules src/core/config/scripts/newMinorVersion.mjs && npm version minor --git-tag-version=false && echo \"Updated to version v$(npm pkg get version | xargs), please create a pull request and once merged use 'npm run tag'\"",
206
+ "newop": "node src/core/config/scripts/newOperation.mjs",
207
+ "minor": "node src/core/config/scripts/newMinorVersion.mjs && npm version minor --git-tag-version=false && echo \"Updated to version v$(npm pkg get version | xargs), please create a pull request and once merged use 'npm run tag'\"",
207
208
  "tag": "git tag -s \"v$(npm pkg get version | xargs)\" -m \"$(npm pkg get version | xargs)\" && echo \"Created v$(npm pkg get version | xargs), now check and push the tag\"",
208
209
  "getheapsize": "node -e 'console.log(`node heap limit = ${require(\"v8\").getHeapStatistics().heap_size_limit / (1024 * 1024)} Mb`)'",
209
210
  "setheapsize": "export NODE_OPTIONS=--max_old_space_size=2048"
211
+ },
212
+ "engines": {
213
+ "node": ">=24 <25"
210
214
  }
211
215
  }
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import Chef from "./Chef.mjs";
10
- import OperationConfig from "./config/OperationConfig.json" assert {type: "json"};
10
+ import OperationConfig from "./config/OperationConfig.json" with { type: "json" };
11
11
  import OpModules from "./config/modules/OpModules.mjs";
12
12
  import loglevelMessagePrefix from "loglevel-message-prefix";
13
13
 
@@ -4,7 +4,7 @@
4
4
  * @license Apache-2.0
5
5
  */
6
6
 
7
- import OperationConfig from "./config/OperationConfig.json" assert {type: "json"};
7
+ import OperationConfig from "./config/OperationConfig.json" with { type: "json" };
8
8
  import OperationError from "./errors/OperationError.mjs";
9
9
  import Operation from "./Operation.mjs";
10
10
  import DishError from "./errors/DishError.mjs";
@@ -1,4 +1,4 @@
1
- import OperationConfig from "../config/OperationConfig.json" assert {type: "json"};
1
+ import OperationConfig from "../config/OperationConfig.json" with { type: "json" };
2
2
  import Utils, { isWorkerEnvironment } from "../Utils.mjs";
3
3
  import Recipe from "../Recipe.mjs";
4
4
  import Dish from "../Dish.mjs";
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import Operation from "../Operation.mjs";
8
- import punycode from "punycode";
8
+ import punycode from "punycode.js";
9
9
 
10
10
  /**
11
11
  * From Punycode operation
@@ -77,84 +77,84 @@ class ShowBase64Offsets extends Operation {
77
77
  staticSection = offset0.slice(0, -3);
78
78
  offset0 = "<span data-toggle='tooltip' data-placement='top' title='" +
79
79
  Utils.escapeHtml(fromBase64(staticSection, alphabet).slice(0, -2)) + "'>" +
80
- staticSection + "</span>" +
81
- "<span class='hl5'>" + offset0.substr(offset0.length - 3, 1) + "</span>" +
82
- "<span class='hl3'>" + offset0.substr(offset0.length - 2) + "</span>";
80
+ Utils.escapeHtml(staticSection) + "</span>" +
81
+ "<span class='hl5'>" + Utils.escapeHtml(offset0.substr(offset0.length - 3, 1)) + "</span>" +
82
+ "<span class='hl3'>" + Utils.escapeHtml(offset0.substr(offset0.length - 2)) + "</span>";
83
83
  } else if (len0 % 4 === 3) {
84
84
  staticSection = offset0.slice(0, -2);
85
85
  offset0 = "<span data-toggle='tooltip' data-placement='top' title='" +
86
86
  Utils.escapeHtml(fromBase64(staticSection, alphabet).slice(0, -1)) + "'>" +
87
- staticSection + "</span>" +
88
- "<span class='hl5'>" + offset0.substr(offset0.length - 2, 1) + "</span>" +
89
- "<span class='hl3'>" + offset0.substr(offset0.length - 1) + "</span>";
87
+ Utils.escapeHtml(staticSection) + "</span>" +
88
+ "<span class='hl5'>" + Utils.escapeHtml(offset0.substr(offset0.length - 2, 1)) + "</span>" +
89
+ "<span class='hl3'>" + Utils.escapeHtml(offset0.substr(offset0.length - 1)) + "</span>";
90
90
  } else {
91
91
  staticSection = offset0;
92
92
  offset0 = "<span data-toggle='tooltip' data-placement='top' title='" +
93
93
  Utils.escapeHtml(fromBase64(staticSection, alphabet)) + "'>" +
94
- staticSection + "</span>";
94
+ Utils.escapeHtml(staticSection) + "</span>";
95
95
  }
96
96
 
97
97
  if (!showVariable) {
98
- offset0 = staticSection;
98
+ offset0 = Utils.escapeHtml(staticSection);
99
99
  }
100
100
 
101
101
 
102
102
  // Highlight offset 1
103
- padding = "<span class='hl3'>" + offset1.substr(0, 1) + "</span>" +
104
- "<span class='hl5'>" + offset1.substr(1, 1) + "</span>";
103
+ padding = "<span class='hl3'>" + Utils.escapeHtml(offset1.substr(0, 1)) + "</span>" +
104
+ "<span class='hl5'>" + Utils.escapeHtml(offset1.substr(1, 1)) + "</span>";
105
105
  offset1 = offset1.substr(2);
106
106
  if (len1 % 4 === 2) {
107
107
  staticSection = offset1.slice(0, -3);
108
108
  offset1 = padding + "<span data-toggle='tooltip' data-placement='top' title='" +
109
109
  Utils.escapeHtml(fromBase64("AA" + staticSection, alphabet).slice(1, -2)) + "'>" +
110
- staticSection + "</span>" +
111
- "<span class='hl5'>" + offset1.substr(offset1.length - 3, 1) + "</span>" +
112
- "<span class='hl3'>" + offset1.substr(offset1.length - 2) + "</span>";
110
+ Utils.escapeHtml(staticSection) + "</span>" +
111
+ "<span class='hl5'>" + Utils.escapeHtml(offset1.substr(offset1.length - 3, 1)) + "</span>" +
112
+ "<span class='hl3'>" + Utils.escapeHtml(offset1.substr(offset1.length - 2)) + "</span>";
113
113
  } else if (len1 % 4 === 3) {
114
114
  staticSection = offset1.slice(0, -2);
115
115
  offset1 = padding + "<span data-toggle='tooltip' data-placement='top' title='" +
116
116
  Utils.escapeHtml(fromBase64("AA" + staticSection, alphabet).slice(1, -1)) + "'>" +
117
- staticSection + "</span>" +
118
- "<span class='hl5'>" + offset1.substr(offset1.length - 2, 1) + "</span>" +
119
- "<span class='hl3'>" + offset1.substr(offset1.length - 1) + "</span>";
117
+ Utils.escapeHtml(staticSection) + "</span>" +
118
+ "<span class='hl5'>" + Utils.escapeHtml(offset1.substr(offset1.length - 2, 1)) + "</span>" +
119
+ "<span class='hl3'>" + Utils.escapeHtml(offset1.substr(offset1.length - 1)) + "</span>";
120
120
  } else {
121
121
  staticSection = offset1;
122
122
  offset1 = padding + "<span data-toggle='tooltip' data-placement='top' title='" +
123
123
  Utils.escapeHtml(fromBase64("AA" + staticSection, alphabet).slice(1)) + "'>" +
124
- staticSection + "</span>";
124
+ Utils.escapeHtml(staticSection) + "</span>";
125
125
  }
126
126
 
127
127
  if (!showVariable) {
128
- offset1 = staticSection;
128
+ offset1 = Utils.escapeHtml(staticSection);
129
129
  }
130
130
 
131
131
  // Highlight offset 2
132
- padding = "<span class='hl3'>" + offset2.substr(0, 2) + "</span>" +
133
- "<span class='hl5'>" + offset2.substr(2, 1) + "</span>";
132
+ padding = "<span class='hl3'>" + Utils.escapeHtml(offset2.substr(0, 2)) + "</span>" +
133
+ "<span class='hl5'>" + Utils.escapeHtml(offset2.substr(2, 1)) + "</span>";
134
134
  offset2 = offset2.substr(3);
135
135
  if (len2 % 4 === 2) {
136
136
  staticSection = offset2.slice(0, -3);
137
137
  offset2 = padding + "<span data-toggle='tooltip' data-placement='top' title='" +
138
138
  Utils.escapeHtml(fromBase64("AAA" + staticSection, alphabet).slice(2, -2)) + "'>" +
139
- staticSection + "</span>" +
140
- "<span class='hl5'>" + offset2.substr(offset2.length - 3, 1) + "</span>" +
141
- "<span class='hl3'>" + offset2.substr(offset2.length - 2) + "</span>";
139
+ Utils.escapeHtml(staticSection) + "</span>" +
140
+ "<span class='hl5'>" + Utils.escapeHtml(offset2.substr(offset2.length - 3, 1)) + "</span>" +
141
+ "<span class='hl3'>" + Utils.escapeHtml(offset2.substr(offset2.length - 2)) + "</span>";
142
142
  } else if (len2 % 4 === 3) {
143
143
  staticSection = offset2.slice(0, -2);
144
144
  offset2 = padding + "<span data-toggle='tooltip' data-placement='top' title='" +
145
145
  Utils.escapeHtml(fromBase64("AAA" + staticSection, alphabet).slice(2, -2)) + "'>" +
146
- staticSection + "</span>" +
147
- "<span class='hl5'>" + offset2.substr(offset2.length - 2, 1) + "</span>" +
148
- "<span class='hl3'>" + offset2.substr(offset2.length - 1) + "</span>";
146
+ Utils.escapeHtml(staticSection) + "</span>" +
147
+ "<span class='hl5'>" + Utils.escapeHtml(offset2.substr(offset2.length - 2, 1)) + "</span>" +
148
+ "<span class='hl3'>" + Utils.escapeHtml(offset2.substr(offset2.length - 1)) + "</span>";
149
149
  } else {
150
150
  staticSection = offset2;
151
151
  offset2 = padding + "<span data-toggle='tooltip' data-placement='top' title='" +
152
152
  Utils.escapeHtml(fromBase64("AAA" + staticSection, alphabet).slice(2)) + "'>" +
153
- staticSection + "</span>";
153
+ Utils.escapeHtml(staticSection) + "</span>";
154
154
  }
155
155
 
156
156
  if (!showVariable) {
157
- offset2 = staticSection;
157
+ offset2 = Utils.escapeHtml(staticSection);
158
158
  }
159
159
 
160
160
  return (showVariable ? "Characters highlighted in <span class='hl5'>green</span> could change if the input is surrounded by more data." +
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import Operation from "../Operation.mjs";
8
- import punycode from "punycode";
8
+ import punycode from "punycode.js";
9
9
 
10
10
  /**
11
11
  * To Punycode operation
@@ -88,16 +88,17 @@ class NodeRecipe {
88
88
  * @param {NodeDish} dish
89
89
  * @returns {NodeDish}
90
90
  */
91
- execute(dish) {
92
- return this.opList.reduce((prev, curr) => {
93
- // CASE where opList item is op and args
91
+ async execute(dish) {
92
+ let prev = dish;
93
+ for (const curr of this.opList) {
94
94
  if (Object.prototype.hasOwnProperty.call(curr, "op") &&
95
95
  Object.prototype.hasOwnProperty.call(curr, "args")) {
96
- return curr.op(prev, curr.args);
96
+ prev = await curr.op(prev, curr.args);
97
+ } else {
98
+ prev = await curr(prev);
97
99
  }
98
- // CASE opList item is just op.
99
- return curr(prev);
100
- }, dish);
100
+ }
101
+ return prev;
101
102
  }
102
103
  }
103
104
 
package/src/node/api.mjs CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  import NodeDish from "./NodeDish.mjs";
12
12
  import NodeRecipe from "./NodeRecipe.mjs";
13
- import OperationConfig from "../core/config/OperationConfig.json" assert {type: "json"};
13
+ import OperationConfig from "../core/config/OperationConfig.json" with { type: "json" };
14
14
  import { sanitise, removeSubheadingsFromArray, sentenceToCamelCase } from "./apiUtils.mjs";
15
15
  import ExcludedOperationError from "../core/errors/ExcludedOperationError.mjs";
16
16
 
@@ -324,10 +324,10 @@ export function help(input) {
324
324
  * @returns {NodeDish} of the result
325
325
  * @throws {TypeError} if invalid recipe given.
326
326
  */
327
- export function bake(input, recipeConfig) {
328
- const recipe = new NodeRecipe(recipeConfig);
327
+ export async function bake(input, recipeConfig) {
328
+ const recipe = new NodeRecipe(recipeConfig);
329
329
  const dish = ensureIsDish(input);
330
- return recipe.execute(dish);
330
+ return await recipe.execute(dish);
331
331
  }
332
332
 
333
333
 
package/src/web/index.js CHANGED
@@ -17,8 +17,8 @@ import * as CanvasComponents from "../core/lib/CanvasComponents.mjs";
17
17
 
18
18
  // CyberChef
19
19
  import App from "./App.mjs";
20
- import Categories from "../core/config/Categories.json" assert {type: "json"};
21
- import OperationConfig from "../core/config/OperationConfig.json" assert {type: "json"};
20
+ import Categories from "../core/config/Categories.json" with { type: "json" };
21
+ import OperationConfig from "../core/config/OperationConfig.json" with { type: "json" };
22
22
 
23
23
 
24
24
  /**
@@ -1,5 +1,5 @@
1
1
  import sm from "sitemap";
2
- import OperationConfig from "../../core/config/OperationConfig.json" assert { type: "json" };
2
+ import OperationConfig from "../../core/config/OperationConfig.json" with { type: "json" };
3
3
 
4
4
  /**
5
5
  * Generates an XML sitemap for all CyberChef operations and a number of recipes.
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Polyfill for Node.js 22+ where globalThis.fetch is built-in but rejects
3
+ * bare filesystem paths. WASM libraries like argon2-browser call fetch() with
4
+ * an absolute path (e.g. "/path/to/argon2.wasm") expecting a browser-style
5
+ * fallback, but Node.js 22's fetch throws synchronously for non-URL strings.
6
+ *
7
+ * This wrapper intercepts such calls and serves the file via Node's fs module,
8
+ * returning a synthetic Response so the WASM module loads correctly.
9
+ */
10
+
11
+ import { readFile } from "fs/promises";
12
+
13
+ if (globalThis.fetch) {
14
+ const originalFetch = globalThis.fetch;
15
+ globalThis.fetch = async function patchedFetch(url, options) {
16
+ const urlStr = typeof url === "string" ?
17
+ url :
18
+ url instanceof URL ?
19
+ url.href :
20
+ String(url);
21
+ // Intercept bare filesystem paths (absolute POSIX or Windows)
22
+ if (urlStr.startsWith("/") || /^[A-Za-z]:[/\\]/.test(urlStr)) {
23
+ const buffer = await readFile(urlStr);
24
+ return new Response(buffer, {
25
+ status: 200,
26
+ headers: { "Content-Type": "application/wasm" },
27
+ });
28
+ }
29
+ return originalFetch(url, options);
30
+ };
31
+ }
@@ -8,9 +8,9 @@
8
8
 
9
9
  const assert = require("assert");
10
10
 
11
- require("cyberchef").then(chef => {
11
+ require("cyberchef").then(async chef => {
12
12
 
13
- const d = chef.bake("Testing, 1 2 3", [
13
+ const d = await chef.bake("Testing, 1 2 3", [
14
14
  chef.toHex,
15
15
  chef.reverse,
16
16
  {
@@ -9,7 +9,7 @@ import assert from "assert";
9
9
  import chef from "cyberchef";
10
10
  import { bake, toHex, reverse, unique, multiply } from "cyberchef";
11
11
 
12
- const a = bake("Testing, 1 2 3", [
12
+ const a = await bake("Testing, 1 2 3", [
13
13
  toHex,
14
14
  reverse,
15
15
  {
@@ -28,7 +28,7 @@ const a = bake("Testing, 1 2 3", [
28
28
 
29
29
  assert.equal(a.value, "630957449041920");
30
30
 
31
- const b = chef.bake("Testing, 1 2 3", [
31
+ const b = await chef.bake("Testing, 1 2 3", [
32
32
  chef.toHex,
33
33
  chef.reverse,
34
34
  {
@@ -1,6 +1,6 @@
1
1
  import TestRegister from "../../lib/TestRegister.mjs";
2
- import Categories from "../../../src/core/config/Categories.json" assert {type: "json"};
3
- import OperationConfig from "../../../src/core/config/OperationConfig.json" assert {type: "json"};
2
+ import Categories from "../../../src/core/config/Categories.json" with { type: "json" };
3
+ import OperationConfig from "../../../src/core/config/OperationConfig.json" with { type: "json" };
4
4
  import it from "../assertionHandler.mjs";
5
5
  import assert from "assert";
6
6
 
@@ -170,77 +170,77 @@ TestRegister.addApiTests([
170
170
  assert(chef.bake);
171
171
  }),
172
172
 
173
- it("chef.bake: should return NodeDish", () => {
174
- const result = chef.bake("input", "to base 64");
173
+ it("chef.bake: should return NodeDish", async () => {
174
+ const result = await chef.bake("input", "to base 64");
175
175
  assert(result instanceof NodeDish);
176
176
  }),
177
177
 
178
- it("chef.bake: should take an input and an op name and perform it", () => {
179
- const result = chef.bake("some input", "to base 32");
178
+ it("chef.bake: should take an input and an op name and perform it", async () => {
179
+ const result = await chef.bake("some input", "to base 32");
180
180
  assert.strictEqual(result.toString(), "ONXW2ZJANFXHA5LU");
181
181
  }),
182
182
 
183
- it("chef.bake: should complain if recipe isnt a valid object", () => {
184
- assert.throws(() => chef.bake("some input", 3264), {
183
+ it("chef.bake: should complain if recipe isnt a valid object", async () => {
184
+ await assert.rejects(() => chef.bake("some input", 3264), {
185
185
  name: "TypeError",
186
186
  message: "Recipe can only contain function names or functions"
187
187
  });
188
188
  }),
189
189
 
190
- it("chef.bake: Should complain if string op is invalid", () => {
191
- assert.throws(() => chef.bake("some input", "not a valid operation"), {
190
+ it("chef.bake: Should complain if string op is invalid", async () => {
191
+ await assert.rejects(() => chef.bake("some input", "not a valid operation"), {
192
192
  name: "TypeError",
193
193
  message: "Couldn't find an operation with name 'not a valid operation'."
194
194
  });
195
195
  }),
196
196
 
197
- it("chef.bake: Should take an input and an operation and perform it", () => {
198
- const result = chef.bake("https://google.com/search?q=help", chef.parseURI);
197
+ it("chef.bake: Should take an input and an operation and perform it", async () => {
198
+ const result = await chef.bake("https://google.com/search?q=help", chef.parseURI);
199
199
  assert.strictEqual(result.toString(), "Protocol:\thttps:\nHostname:\tgoogle.com\nPath name:\t/search\nArguments:\n\tq = help\n");
200
200
  }),
201
201
 
202
- it("chef.bake: Should complain if an invalid operation is inputted", () => {
203
- assert.throws(() => chef.bake("https://google.com/search?q=help", () => {}), {
202
+ it("chef.bake: Should complain if an invalid operation is inputted", async () => {
203
+ await assert.rejects(() => chef.bake("https://google.com/search?q=help", () => {}), {
204
204
  name: "TypeError",
205
205
  message: "Inputted function not a Chef operation."
206
206
  });
207
207
  }),
208
208
 
209
- it("chef.bake: accepts an array of operation names and performs them all in order", () => {
210
- const result = chef.bake("https://google.com/search?q=that's a complicated question", ["URL encode", "URL decode", "Parse URI"]);
209
+ it("chef.bake: accepts an array of operation names and performs them all in order", async () => {
210
+ const result = await chef.bake("https://google.com/search?q=that's a complicated question", ["URL encode", "URL decode", "Parse URI"]);
211
211
  assert.strictEqual(result.toString(), "Protocol:\thttps:\nHostname:\tgoogle.com\nPath name:\t/search\nArguments:\n\tq = that's a complicated question\n");
212
212
  }),
213
213
 
214
- it("chef.bake: forgiving with operation names", () =>{
215
- const result = chef.bake("https://google.com/search?q=that's a complicated question", ["urlencode", "url decode", "parseURI"]);
214
+ it("chef.bake: forgiving with operation names", async () =>{
215
+ const result = await chef.bake("https://google.com/search?q=that's a complicated question", ["urlencode", "url decode", "parseURI"]);
216
216
  assert.strictEqual(result.toString(), "Protocol:\thttps:\nHostname:\tgoogle.com\nPath name:\t/search\nArguments:\n\tq = that's a complicated question\n");
217
217
  }),
218
218
 
219
- it("chef.bake: forgiving with operation names", () =>{
220
- const result = chef.bake("hello", ["to base 64"]);
219
+ it("chef.bake: forgiving with operation names", async () =>{
220
+ const result = await chef.bake("hello", ["to base 64"]);
221
221
  assert.strictEqual(result.toString(), "aGVsbG8=");
222
222
  }),
223
223
 
224
- it("chef.bake: if recipe is empty array, return input as dish", () => {
225
- const result = chef.bake("some input", []);
224
+ it("chef.bake: if recipe is empty array, return input as dish", async () => {
225
+ const result = await chef.bake("some input", []);
226
226
  assert.strictEqual(result.toString(), "some input");
227
227
  assert(result instanceof NodeDish, "Result is not instance of NodeDish");
228
228
  }),
229
229
 
230
- it("chef.bake: accepts an array of operations as recipe", () => {
231
- const result = chef.bake("https://google.com/search?q=that's a complicated question", [chef.URLEncode, chef.URLDecode, chef.parseURI]);
230
+ it("chef.bake: accepts an array of operations as recipe", async () => {
231
+ const result = await chef.bake("https://google.com/search?q=that's a complicated question", [chef.URLEncode, chef.URLDecode, chef.parseURI]);
232
232
  assert.strictEqual(result.toString(), "Protocol:\thttps:\nHostname:\tgoogle.com\nPath name:\t/search\nArguments:\n\tq = that's a complicated question\n");
233
233
  }),
234
234
 
235
- it("should complain if an invalid operation is inputted as part of array", () => {
236
- assert.throws(() => chef.bake("something", [() => {}]), {
235
+ it("should complain if an invalid operation is inputted as part of array", async () => {
236
+ await assert.rejects(() => chef.bake("something", [() => {}]), {
237
237
  name: "TypeError",
238
238
  message: "Inputted function not a Chef operation."
239
239
  });
240
240
  }),
241
241
 
242
- it("chef.bake: should take single JSON object describing op and args OBJ", () => {
243
- const result = chef.bake("some input", {
242
+ it("chef.bake: should take single JSON object describing op and args OBJ", async () => {
243
+ const result = await chef.bake("some input", {
244
244
  op: chef.toHex,
245
245
  args: {
246
246
  Delimiter: "Colon"
@@ -249,23 +249,23 @@ TestRegister.addApiTests([
249
249
  assert.strictEqual(result.toString(), "73:6f:6d:65:20:69:6e:70:75:74");
250
250
  }),
251
251
 
252
- it("chef.bake: should take single JSON object desribing op with optional args", () => {
253
- const result = chef.bake("some input", {
252
+ it("chef.bake: should take single JSON object desribing op with optional args", async () => {
253
+ const result = await chef.bake("some input", {
254
254
  op: chef.toHex,
255
255
  });
256
256
  assert.strictEqual(result.toString(), "73 6f 6d 65 20 69 6e 70 75 74");
257
257
  }),
258
258
 
259
- it("chef.bake: should take single JSON object describing op and args ARRAY", () => {
260
- const result = chef.bake("some input", {
259
+ it("chef.bake: should take single JSON object describing op and args ARRAY", async () => {
260
+ const result = await chef.bake("some input", {
261
261
  op: chef.toHex,
262
262
  args: ["Colon"]
263
263
  });
264
264
  assert.strictEqual(result.toString(), "73:6f:6d:65:20:69:6e:70:75:74");
265
265
  }),
266
266
 
267
- it("chef.bake: should error if op in JSON is not chef op", () => {
268
- assert.throws(() => chef.bake("some input", {
267
+ it("chef.bake: should error if op in JSON is not chef op", async () => {
268
+ await assert.rejects(() => chef.bake("some input", {
269
269
  op: () => {},
270
270
  args: ["Colon"],
271
271
  }), {
@@ -274,8 +274,8 @@ TestRegister.addApiTests([
274
274
  });
275
275
  }),
276
276
 
277
- it("chef.bake: should take multiple ops in JSON object form, some ops by string", () => {
278
- const result = chef.bake("some input", [
277
+ it("chef.bake: should take multiple ops in JSON object form, some ops by string", async () => {
278
+ const result = await chef.bake("some input", [
279
279
  {
280
280
  op: chef.toHex,
281
281
  args: ["Colon"]
@@ -290,8 +290,8 @@ TestRegister.addApiTests([
290
290
  assert.strictEqual(result.toString(), "67;63;72;66;146;72;66;144;72;66;65;72;62;60;72;66;71;72;66;145;72;67;60;72;67;65;72;67;64");
291
291
  }),
292
292
 
293
- it("chef.bake: should take multiple ops in JSON object form, some without args", () => {
294
- const result = chef.bake("some input", [
293
+ it("chef.bake: should take multiple ops in JSON object form, some without args", async () => {
294
+ const result = await chef.bake("some input", [
295
295
  {
296
296
  op: chef.toHex,
297
297
  },
@@ -305,8 +305,8 @@ TestRegister.addApiTests([
305
305
  assert.strictEqual(result.toString(), "67;63;40;66;146;40;66;144;40;66;65;40;62;60;40;66;71;40;66;145;40;67;60;40;67;65;40;67;64");
306
306
  }),
307
307
 
308
- it("chef.bake: should handle op with multiple args", () => {
309
- const result = chef.bake("some input", {
308
+ it("chef.bake: should handle op with multiple args", async () => {
309
+ const result = await chef.bake("some input", {
310
310
  op: "to morse code",
311
311
  args: {
312
312
  formatOptions: "Dash/Dot",
@@ -317,13 +317,13 @@ TestRegister.addApiTests([
317
317
  assert.strictEqual(result.toString(), "DotDotDot\\DashDashDash\\DashDash\\Dot,DotDot\\DashDot\\DotDashDashDot\\DotDotDash\\Dash");
318
318
  }),
319
319
 
320
- it("chef.bake: should take compact JSON format from Chef Website as recipe", () => {
321
- const result = chef.bake("some input", [{"op": "To Morse Code", "args": ["Dash/Dot", "Backslash", "Comma"]}, {"op": "Hex to PEM", "args": ["SOMETHING"]}, {"op": "To Snake case", "args": [false]}]);
320
+ it("chef.bake: should take compact JSON format from Chef Website as recipe", async () => {
321
+ const result = await chef.bake("some input", [{"op": "To Morse Code", "args": ["Dash/Dot", "Backslash", "Comma"]}, {"op": "Hex to PEM", "args": ["SOMETHING"]}, {"op": "To Snake case", "args": [false]}]);
322
322
  assert.strictEqual(result.toString(), "begin_something_anananaaaaak_da_aaak_da_aaaaananaaaaaaan_da_aaaaaaanan_da_aaak_end_something");
323
323
  }),
324
324
 
325
- it("chef.bake: should accept Clean JSON format from Chef website as recipe", () => {
326
- const result = chef.bake("some input", [
325
+ it("chef.bake: should accept Clean JSON format from Chef website as recipe", async () => {
326
+ const result = await chef.bake("some input", [
327
327
  { "op": "To Morse Code",
328
328
  "args": ["Dash/Dot", "Backslash", "Comma"] },
329
329
  { "op": "Hex to PEM",
@@ -334,8 +334,8 @@ TestRegister.addApiTests([
334
334
  assert.strictEqual(result.toString(), "begin_something_anananaaaaak_da_aaak_da_aaaaananaaaaaaan_da_aaaaaaanan_da_aaak_end_something");
335
335
  }),
336
336
 
337
- it("chef.bake: should accept Clean JSON format from Chef website - args optional", () => {
338
- const result = chef.bake("some input", [
337
+ it("chef.bake: should accept Clean JSON format from Chef website - args optional", async () => {
338
+ const result = await chef.bake("some input", [
339
339
  { "op": "To Morse Code" },
340
340
  { "op": "Hex to PEM",
341
341
  "args": ["SOMETHING"] },
@@ -345,31 +345,28 @@ TestRegister.addApiTests([
345
345
  assert.strictEqual(result.toString(), "begin_something_aaaaaaaaaaaaaa_end_something");
346
346
  }),
347
347
 
348
- it("chef.bake: should accept operation names from Chef Website which contain forward slash", () => {
349
- const result = chef.bake("I'll have the test salmon", [
348
+ it("chef.bake: should accept operation names from Chef Website which contain forward slash", async () => {
349
+ const result = await chef.bake("I'll have the test salmon", [
350
350
  { "op": "Find / Replace",
351
351
  "args": [{ "option": "Regex", "string": "test" }, "good", true, false, true, false]}
352
352
  ]);
353
353
  assert.strictEqual(result.toString(), "I'll have the good salmon");
354
354
  }),
355
355
 
356
- it("chef.bake: should accept operation names from Chef Website which contain a hyphen", () => {
357
- const result = chef.bake("I'll have the test salmon", [
356
+ it("chef.bake: should accept operation names from Chef Website which contain a hyphen", async () => {
357
+ const result = await chef.bake("I'll have the test salmon", [
358
358
  { "op": "Adler-32 Checksum",
359
359
  "args": [] }
360
360
  ]);
361
361
  assert.strictEqual(result.toString(), "6e4208f8");
362
362
  }),
363
363
 
364
- it("chef.bake: should accept operation names from Chef Website which contain a period", () => {
365
- const result = chef.bake("30 13 02 01 05 16 0e 41 6e 79 62 6f 64 79 20 74 68 65 72 65 3f", [
364
+ it("chef.bake: should accept operation names from Chef Website which contain a period", async () => {
365
+ const result = await chef.bake("30 13 02 01 05 16 0e 41 6e 79 62 6f 64 79 20 74 68 65 72 65 3f", [
366
366
  { "op": "Parse ASN.1 hex string",
367
367
  "args": [0, 32] }
368
368
  ]);
369
- assert.strictEqual(result.toString(), `SEQUENCE
370
- INTEGER 05
371
- IA5String 'Anybody there?'
372
- `);
369
+ assert.strictEqual(result.toString(), `SEQUENCE\n INTEGER 05\n IA5String 'Anybody there?'\n`);
373
370
  }),
374
371
 
375
372
  it("Excluded operations: throw a sensible error when you try and call one", () => {
@@ -381,16 +378,16 @@ TestRegister.addApiTests([
381
378
  }
382
379
  }),
383
380
 
384
- it("chef.bake: cannot accept flowControl operations in recipe", () => {
385
- assert.throws(() => chef.bake("some input", "magic"), {
381
+ it("chef.bake: cannot accept flowControl operations in recipe", async () => {
382
+ await assert.rejects(() => chef.bake("some input", "magic"), {
386
383
  name: "TypeError",
387
384
  message: "flowControl operations like Magic are not currently allowed in recipes for chef.bake in the Node API"
388
385
  });
389
- assert.throws(() => chef.bake("some input", magic), {
386
+ await assert.rejects(() => chef.bake("some input", magic), {
390
387
  name: "TypeError",
391
388
  message: "flowControl operations like Magic are not currently allowed in recipes for chef.bake in the Node API"
392
389
  });
393
- assert.throws(() => chef.bake("some input", ["to base 64", "magic"]), {
390
+ await assert.rejects(() => chef.bake("some input", ["to base 64", "magic"]), {
394
391
  name: "TypeError",
395
392
  message: "flowControl operations like Magic are not currently allowed in recipes for chef.bake in the Node API"
396
393
  });
@@ -11,6 +11,7 @@
11
11
  * @license Apache-2.0
12
12
  */
13
13
 
14
+ import "../lib/wasmFetchPolyfill.mjs";
14
15
  import { setLongTestFailure, logTestReport } from "../lib/utils.mjs";
15
16
 
16
17
  import TestRegister from "../lib/TestRegister.mjs";
@@ -116,4 +116,15 @@ TestRegister.addTests([
116
116
  },
117
117
  ],
118
118
  },
119
+ {
120
+ name: "Show Base64 offsets: escapes static output",
121
+ input: "\x00\x10\x83\x10\x51\x87",
122
+ expectedOutput: "&lt;script&gt;\n&lt;AQmsBRk66\n&lt;ia1AEIM6",
123
+ recipeConfig: [
124
+ {
125
+ op: "Show Base64 offsets",
126
+ args: ["<script>ale(1)/.ABCDEFGHIJKLMNOPQRSTUVWXYZbdfghjkmnoquvwxyz023456", false, "Raw"],
127
+ },
128
+ ],
129
+ },
119
130
  ]);