cyberchef 11.1.0 → 11.3.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.
Files changed (148) hide show
  1. package/AGENTS.md +75 -0
  2. package/CHANGELOG.md +173 -2
  3. package/Dockerfile +2 -2
  4. package/Gruntfile.js +1 -0
  5. package/README.md +7 -6
  6. package/SECURITY.md +3 -1
  7. package/package.json +22 -21
  8. package/src/core/Chef.mjs +2 -0
  9. package/src/core/Dish.mjs +1 -5
  10. package/src/core/Ingredient.mjs +92 -0
  11. package/src/core/Operation.mjs +19 -0
  12. package/src/core/Recipe.mjs +4 -0
  13. package/src/core/Utils.mjs +48 -0
  14. package/src/core/config/Categories.json +22 -6
  15. package/src/core/config/OperationConfig.json +4774 -1228
  16. package/src/core/config/modules/Ciphers.mjs +20 -0
  17. package/src/core/config/modules/Crypto.mjs +10 -0
  18. package/src/core/config/modules/Default.mjs +8 -0
  19. package/src/core/config/modules/File.mjs +16 -0
  20. package/src/core/config/modules/OpModules.mjs +2 -0
  21. package/src/core/config/scripts/generateHTMLEntities.mjs +139 -0
  22. package/src/core/config/scripts/htmlEntityOverrides.mjs +86 -0
  23. package/src/core/dishTypes/DishType.mjs +1 -1
  24. package/src/core/errors/ExcludedOperationError.mjs +1 -1
  25. package/src/core/lib/Arithmetic.mjs +21 -5
  26. package/src/core/lib/BigIntUtils.mjs +0 -1
  27. package/src/core/lib/COBS.mjs +86 -0
  28. package/src/core/lib/Charts.mjs +3 -3
  29. package/src/core/lib/Decimal.mjs +5 -5
  30. package/src/core/lib/HTMLEntities.mjs +2068 -0
  31. package/src/core/lib/Present.mjs +422 -0
  32. package/src/core/lib/Protocol.mjs +8 -6
  33. package/src/core/lib/TEA.mjs +494 -0
  34. package/src/core/lib/TLVParser.mjs +16 -7
  35. package/src/core/lib/Twofish.mjs +608 -0
  36. package/src/core/operations/AsconDecrypt.mjs +112 -0
  37. package/src/core/operations/AsconEncrypt.mjs +108 -0
  38. package/src/core/operations/AsconHash.mjs +49 -0
  39. package/src/core/operations/AsconMAC.mjs +68 -0
  40. package/src/core/operations/AutomatedValidationTestOp.mjs +84 -0
  41. package/src/core/operations/AvroToJSON.mjs +1 -1
  42. package/src/core/operations/BLAKE3.mjs +5 -1
  43. package/src/core/operations/BcryptCompare.mjs +11 -5
  44. package/src/core/operations/BitShiftLeft.mjs +4 -1
  45. package/src/core/operations/Bzip2Compress.mjs +1 -1
  46. package/src/core/operations/DechunkHTTPResponse.mjs +4 -1
  47. package/src/core/operations/ExtendedGCD.mjs +101 -0
  48. package/src/core/operations/FromBase.mjs +2 -1
  49. package/src/core/operations/FromCOBS.mjs +38 -0
  50. package/src/core/operations/FromDecimal.mjs +6 -1
  51. package/src/core/operations/FromHTMLEntity.mjs +2 -1458
  52. package/src/core/operations/GenerateDeBruijnSequence.mjs +8 -0
  53. package/src/core/operations/GenerateHOTP.mjs +21 -6
  54. package/src/core/operations/GenerateImage.mjs +22 -10
  55. package/src/core/operations/GeneratePrime.mjs +154 -0
  56. package/src/core/operations/GenerateTOTP.mjs +24 -7
  57. package/src/core/operations/Gzip.mjs +1 -3
  58. package/src/core/operations/Jsonata.mjs +12 -0
  59. package/src/core/operations/MIMEDecoding.mjs +1 -1
  60. package/src/core/operations/MOD.mjs +62 -0
  61. package/src/core/operations/ModularInverse.mjs +107 -0
  62. package/src/core/operations/PRESENTDecrypt.mjs +94 -0
  63. package/src/core/operations/PRESENTEncrypt.mjs +94 -0
  64. package/src/core/operations/ParityBit.mjs +1 -1
  65. package/src/core/operations/ParseIPv4Header.mjs +1 -1
  66. package/src/core/operations/ParseURI.mjs +18 -5
  67. package/src/core/operations/PseudoRandomNumberGenerator.mjs +2 -1
  68. package/src/core/operations/RandomPrime.mjs +154 -0
  69. package/src/core/operations/RenderPDF.mjs +100 -0
  70. package/src/core/operations/SHA2.mjs +1 -1
  71. package/src/core/operations/SM4Encrypt.mjs +1 -1
  72. package/src/core/operations/SetDifference.mjs +8 -1
  73. package/src/core/operations/SetIntersection.mjs +8 -1
  74. package/src/core/operations/ShowOnMap.mjs +14 -2
  75. package/src/core/operations/TEADecrypt.mjs +98 -0
  76. package/src/core/operations/TEAEncrypt.mjs +98 -0
  77. package/src/core/operations/ToBase.mjs +4 -4
  78. package/src/core/operations/ToBase32.mjs +20 -4
  79. package/src/core/operations/ToBinary.mjs +4 -1
  80. package/src/core/operations/ToCOBS.mjs +38 -0
  81. package/src/core/operations/ToHTMLEntity.mjs +7 -1430
  82. package/src/core/operations/ToHexdump.mjs +7 -1
  83. package/src/core/operations/TwofishDecrypt.mjs +94 -0
  84. package/src/core/operations/TwofishEncrypt.mjs +94 -0
  85. package/src/core/operations/URLEncode.mjs +22 -18
  86. package/src/core/operations/UnescapeUnicodeCharacters.mjs +2 -1
  87. package/src/core/operations/ViewBitPlane.mjs +9 -3
  88. package/src/core/operations/Wrap.mjs +5 -0
  89. package/src/core/operations/XORBruteForce.mjs +4 -1
  90. package/src/core/operations/XORChecksum.mjs +10 -3
  91. package/src/core/operations/XTEADecrypt.mjs +110 -0
  92. package/src/core/operations/XTEAEncrypt.mjs +110 -0
  93. package/src/core/operations/index.mjs +42 -0
  94. package/src/core/vendor/ascon.mjs +162 -0
  95. package/src/core/vendor/htmlEntities/entity.json +2233 -0
  96. package/src/core/vendor/htmlEntities/entity.txt +14 -0
  97. package/src/node/api.mjs +4 -1
  98. package/src/node/index.mjs +105 -0
  99. package/src/web/HTMLOperation.mjs +2 -1
  100. package/src/web/stylesheets/layout/_io.css +8 -0
  101. package/tests/browser/00_nightwatch.js +26 -0
  102. package/tests/browser/02_ops.js +20 -4
  103. package/tests/node/index.mjs +2 -0
  104. package/tests/node/tests/Dish.mjs +19 -0
  105. package/tests/node/tests/NodeDish.mjs +36 -0
  106. package/tests/node/tests/ToHTMLEntity.mjs +82 -0
  107. package/tests/node/tests/Utils.mjs +77 -0
  108. package/tests/node/tests/lib/ChartsProtocolPrototypePollution.mjs +90 -0
  109. package/tests/node/tests/nodeApi.mjs +33 -1
  110. package/tests/node/tests/operations.mjs +26 -1
  111. package/tests/operations/index.mjs +17 -0
  112. package/tests/operations/tests/Arithmetic.mjs +33 -0
  113. package/tests/operations/tests/Ascon.mjs +501 -0
  114. package/tests/operations/tests/AutomatedValidation.mjs +154 -0
  115. package/tests/operations/tests/BLAKE3.mjs +39 -1
  116. package/tests/operations/tests/Base32.mjs +22 -0
  117. package/tests/operations/tests/COBS.mjs +476 -0
  118. package/tests/operations/tests/CharEnc.mjs +2 -2
  119. package/tests/operations/tests/DechunkHTTPResponse.mjs +66 -0
  120. package/tests/operations/tests/ExtendedGCD.mjs +78 -0
  121. package/tests/operations/tests/FromBase.mjs +66 -0
  122. package/tests/operations/tests/FromDecimal.mjs +55 -0
  123. package/tests/operations/tests/GenerateLoremIpsum.mjs +2 -2
  124. package/tests/operations/tests/Gzip.mjs +60 -0
  125. package/tests/operations/tests/HTMLEntity.mjs +126 -0
  126. package/tests/operations/tests/Hash.mjs +44 -0
  127. package/tests/operations/tests/Hexdump.mjs +11 -0
  128. package/tests/operations/tests/Image.mjs +26 -0
  129. package/tests/operations/tests/Jsonata.mjs +23 -0
  130. package/tests/operations/tests/MIMEDecoding.mjs +33 -0
  131. package/tests/operations/tests/MOD.mjs +208 -0
  132. package/tests/operations/tests/Median.mjs +33 -0
  133. package/tests/operations/tests/ModularInverse.mjs +78 -0
  134. package/tests/operations/tests/OTP.mjs +167 -2
  135. package/tests/operations/tests/PRESENT.mjs +465 -0
  136. package/tests/operations/tests/ParseIPv4Header.mjs +11 -0
  137. package/tests/operations/tests/ParseTLV.mjs +41 -0
  138. package/tests/operations/tests/RenderPDF.mjs +55 -0
  139. package/tests/operations/tests/SM2.mjs +1 -1
  140. package/tests/operations/tests/SetDifference.mjs +22 -0
  141. package/tests/operations/tests/SetIntersection.mjs +23 -1
  142. package/tests/operations/tests/ShowOnMap.mjs +61 -0
  143. package/tests/operations/tests/TEA.mjs +566 -0
  144. package/tests/operations/tests/Twofish.mjs +486 -0
  145. package/tests/operations/tests/URLEncodeDecode.mjs +26 -0
  146. package/tests/operations/tests/UnescapeUnicodeCharacters.mjs +88 -0
  147. package/tests/operations/tests/Wrap.mjs +44 -0
  148. package/webpack.config.js +3 -3
@@ -24,7 +24,9 @@ import "./tests/Dish.mjs";
24
24
  import "./tests/NodeDish.mjs";
25
25
  import "./tests/Utils.mjs";
26
26
  import "./tests/Categories.mjs";
27
+ import "./tests/ToHTMLEntity.mjs";
27
28
  import "./tests/lib/BigIntUtils.mjs";
29
+ import "./tests/lib/ChartsProtocolPrototypePollution.mjs";
28
30
 
29
31
  const testStatus = {
30
32
  allTestsPassing: true,
@@ -9,4 +9,23 @@ TestRegister.addApiTests([
9
9
  assert(dish.presentAs);
10
10
  }),
11
11
 
12
+ it("Disk - should not error on serialized BigNumber (0)", () => {
13
+ const dish = new Dish({ s: 1, e: 0, c: [0] }, Dish.BIG_NUMBER);
14
+ assert.strictEqual(dish.value.toString(), "0");
15
+ }),
16
+
17
+ it("Dish - should not error on serialized BigNumber (1)", () => {
18
+ const dish = new Dish({ c: [1], e: 0, s: 1 }, Dish.BIG_NUMBER);
19
+ assert.strictEqual(dish.value.toString(), "1");
20
+ }),
21
+
22
+ it("Dish - should not error on serialized BigNumber (-100)", () => {
23
+ const dish = new Dish({ s: -1, e: 2, c: [100] }, Dish.BIG_NUMBER);
24
+ assert.strictEqual(dish.value.toString(), "-100");
25
+ }),
26
+
27
+ it("Dish - should not error on serialized BigNumber (NaN)", () => {
28
+ const dish = new Dish({ s: null, e: null, c: null }, Dish.BIG_NUMBER);
29
+ assert.strictEqual(dish.value.toString(), "NaN");
30
+ }),
12
31
  ]);
@@ -65,6 +65,42 @@ TestRegister.addApiTests([
65
65
  assert.strictEqual(result.toString(), "493e8136b759370a415ef2cf2f7a69690441ff86592aba082bc2e2e0");
66
66
  }),
67
67
 
68
+ it("Composable Dish: toBase32 should support non-BMP Unicode alphabets", () => {
69
+ const alphabet = "🀇🀈🀉🀊🀋🀌🀍🀎🀏🀙🀚🀛🀜🀝🀞🀟🀠🀡🀐🀑🀒🀓🀔🀕🀖🀗🀘🀀🀁🀂🀃🀅";
70
+
71
+ const result = new Dish("hello")
72
+ .apply(toBase32, {alphabet})
73
+ .toString();
74
+
75
+ // Should not contain replacement characters
76
+ assert.equal(result.includes("�"), false);
77
+
78
+ // Should contain only symbols from the alphabet
79
+ for (const ch of Array.from(result)) {
80
+ assert.ok(Array.from(alphabet).includes(ch));
81
+ }
82
+
83
+ // "hello" => 8 Base32 symbols
84
+ assert.equal(Array.from(result).length, 8);
85
+ }),
86
+
87
+ it("Composable Dish: toBase32 should omit padding for 32-character Unicode alphabets", () => {
88
+ const alphabet = "🀇🀈🀉🀊🀋🀌🀍🀎🀏🀙🀚🀛🀜🀝🀞🀟🀠🀡🀐🀑🀒🀓🀔🀕🀖🀗🀘🀀🀁🀂🀃🀅";
89
+
90
+ const result = new Dish("hell")
91
+ .apply(toBase32, {alphabet})
92
+ .toString();
93
+
94
+ // Should not leak undefined from array indexing
95
+ assert.equal(result.includes("undefined"), false);
96
+
97
+ // Should not contain replacement characters
98
+ assert.equal(result.includes("�"), false);
99
+
100
+ // Unpadded Base32 output for 4-byte input should be 7 symbols
101
+ assert.equal(Array.from(result).length, 7);
102
+ }),
103
+
68
104
  it("Dish translation: ArrayBuffer to ArrayBuffer", () => {
69
105
  const dish = new Dish(new ArrayBuffer(10), 4);
70
106
  dish.get("array buffer");
@@ -0,0 +1,82 @@
1
+ import TestRegister from "../../lib/TestRegister.mjs";
2
+ import ToHTMLEntity from "../../../src/core/operations/ToHTMLEntity.mjs";
3
+ import FromHTMLEntity from "../../../src/core/operations/FromHTMLEntity.mjs";
4
+ import { HTML_ENTITY_LOOKUP, HTML_ENTITY_REVERSE_LOOKUP } from "../../../src/core/lib/HTMLEntities.mjs";
5
+ import it from "../assertionHandler.mjs";
6
+ import assert from "assert";
7
+ import { readFileSync } from "fs";
8
+ import { fileURLToPath } from "url";
9
+ import path from "path";
10
+
11
+ // Vendored WHATWG named character reference set (https://html.spec.whatwg.org/entities.json).
12
+ const SPEC = JSON.parse(readFileSync(path.join(
13
+ path.dirname(fileURLToPath(import.meta.url)),
14
+ "../../../src/core/vendor/htmlEntities/entity.json"), "utf8"));
15
+ const specByName = {};
16
+ for (const [key, val] of Object.entries(SPEC))
17
+ if (key.endsWith(";")) specByName[key.slice(1, -1)] = val.codepoints;
18
+
19
+ TestRegister.addApiTests([
20
+ it("To HTML Entity: every named entity in the table is well-formed", () => {
21
+ // "Convert all characters" emits an entity for every code point, so a
22
+ // correct table yields an unbroken stream of entity tokens. A malformed
23
+ // value such as "≱;" or "ε," leaves stray characters between
24
+ // tokens, which the walk below flags and reports with surrounding context.
25
+ let input = "";
26
+ for (let cp = 0; cp <= 0xFFFF; cp++) {
27
+ if (cp >= 0xD800 && cp <= 0xDFFF) continue; // skip surrogate range
28
+ input += String.fromCodePoint(cp);
29
+ }
30
+ const output = new ToHTMLEntity().run(input, [true, "Named entities"]);
31
+
32
+ const tokenRe = /&#[0-9]+;|&#x[0-9a-fA-F]+;|&[A-Za-z][A-Za-z0-9]*;/y;
33
+ const malformed = [];
34
+ let pos = 0;
35
+ while (pos < output.length) {
36
+ tokenRe.lastIndex = pos;
37
+ if (tokenRe.exec(output)) {
38
+ pos = tokenRe.lastIndex;
39
+ } else {
40
+ malformed.push(output.slice(Math.max(0, pos - 12), pos + 12));
41
+ pos++;
42
+ }
43
+ }
44
+ assert.deepStrictEqual(malformed, [], `Malformed entity value(s) near: ${JSON.stringify(malformed)}`);
45
+ }),
46
+
47
+ it("HTML Entity: named encoding round-trips through From HTML Entity", () => {
48
+ // Because both operations share one lookup table, encoding a character to
49
+ // a named entity and decoding it must return the original character for
50
+ // every BMP code point: FromHTMLEntity(ToHTMLEntity(x)) === x.
51
+ const toOp = new ToHTMLEntity(),
52
+ fromOp = new FromHTMLEntity();
53
+ const mismatches = [];
54
+ for (let cp = 0; cp <= 0xFFFF; cp++) {
55
+ if (cp >= 0xD800 && cp <= 0xDFFF) continue; // skip surrogate range
56
+ const char = String.fromCodePoint(cp);
57
+ const encoded = toOp.run(char, [true, "Named entities"]);
58
+ const decoded = fromOp.run(encoded, []);
59
+ if (decoded !== char)
60
+ mismatches.push(`U+${cp.toString(16).toUpperCase().padStart(4, "0")} -> ${encoded} -> U+${decoded.codePointAt(0).toString(16).toUpperCase()}`);
61
+ }
62
+ assert.deepStrictEqual(mismatches, [], `Round-trip failed for: ${JSON.stringify(mismatches.slice(0, 20))}`);
63
+ }),
64
+
65
+ it("HTML Entity: every table entry is conformant with the WHATWG spec", () => {
66
+ // Both lookup tables must agree with entities.json: every encode name is a
67
+ // real spec name mapping to exactly that code point, and every decode name
68
+ // maps to the spec code point.
69
+ const violations = [];
70
+ for (const [cp, name] of Object.entries(HTML_ENTITY_LOOKUP)) {
71
+ const spec = specByName[name];
72
+ if (!spec || spec.length !== 1 || spec[0] !== Number(cp))
73
+ violations.push(`encode ${cp} -> &${name}; (spec: ${spec ? JSON.stringify(spec) : "none"})`);
74
+ }
75
+ for (const [name, cp] of Object.entries(HTML_ENTITY_REVERSE_LOOKUP)) {
76
+ const spec = specByName[name];
77
+ if (!spec || spec.length !== 1 || spec[0] !== cp)
78
+ violations.push(`decode &${name}; -> ${cp} (spec: ${spec ? JSON.stringify(spec) : "none"})`);
79
+ }
80
+ assert.deepStrictEqual(violations, [], `Spec violations: ${JSON.stringify(violations.slice(0, 20))}`);
81
+ }),
82
+ ]);
@@ -26,4 +26,81 @@ TestRegister.addApiTests([
26
26
  "\x7e...",
27
27
  );
28
28
  }),
29
+
30
+ it("Utils: should parse normal pretty recipes", () => {
31
+ assert.deepStrictEqual(
32
+ Utils.parseRecipeConfig("From_Base64('A-Za-z0-9+/=',true)To_Hex('Space')"),
33
+ [
34
+ {
35
+ op: "From Base64",
36
+ args: ["A-Za-z0-9+/=", true],
37
+ },
38
+ {
39
+ op: "To Hex",
40
+ args: ["Space"],
41
+ },
42
+ ],
43
+ );
44
+ }),
45
+
46
+ it("Utils: should parse pretty recipe options", () => {
47
+ assert.deepStrictEqual(
48
+ Utils.parseRecipeConfig("A(/disabled/breakpoint)"),
49
+ [
50
+ {
51
+ op: "A",
52
+ args: [],
53
+ disabled: true,
54
+ breakpoint: true,
55
+ },
56
+ ],
57
+ );
58
+ }),
59
+
60
+ it("Utils: should parse escaped quotes and backslashes in pretty recipes", () => {
61
+ assert.deepStrictEqual(
62
+ Utils.parseRecipeConfig("A('\\'\\\\')"),
63
+ [
64
+ {
65
+ op: "A",
66
+ args: ["'\\"],
67
+ },
68
+ ],
69
+ );
70
+ }),
71
+
72
+ it("Utils: should parse large valid quoted pretty recipe arguments", () => {
73
+ const value = "x".repeat(10000);
74
+
75
+ assert.deepStrictEqual(
76
+ Utils.parseRecipeConfig(`A('${value}')`),
77
+ [
78
+ {
79
+ op: "A",
80
+ args: [value],
81
+ },
82
+ ],
83
+ );
84
+ }),
85
+
86
+ it("Utils: should reject malformed pretty recipes with unmatched quotes", () => {
87
+ assert.throws(
88
+ () => Utils.parseRecipeConfig("A(" + "'".repeat(10000)),
89
+ /Invalid recipe/,
90
+ );
91
+ }),
92
+
93
+ it("Utils: should reject malformed pretty recipes with malformed parentheses", () => {
94
+ assert.throws(
95
+ () => Utils.parseRecipeConfig("A("),
96
+ /Invalid recipe/,
97
+ );
98
+ }),
99
+
100
+ it("Utils: should reject malformed pretty recipes with malformed escapes", () => {
101
+ assert.throws(
102
+ () => Utils.parseRecipeConfig("A('" + "\\".repeat(10000)),
103
+ /Invalid recipe/,
104
+ );
105
+ }),
29
106
  ]);
@@ -0,0 +1,90 @@
1
+ import TestRegister from "../../../lib/TestRegister.mjs";
2
+ import {getSeriesValues} from "../../../../src/core/lib/Charts.mjs";
3
+ import {objToTable} from "../../../../src/core/lib/Protocol.mjs";
4
+ import SeriesChart from "../../../../src/core/operations/SeriesChart.mjs";
5
+ import ParseUDP from "../../../../src/core/operations/ParseUDP.mjs";
6
+ import it from "../../assertionHandler.mjs";
7
+ import assert from "assert";
8
+
9
+ const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
10
+
11
+ TestRegister.addApiTests([
12
+ it("Charts: should not pollute Object.prototype from a __proto__ series name", () => {
13
+ const xVal = "<img src=x onerror=alert(1)>";
14
+ delete Object.prototype[xVal];
15
+
16
+ try {
17
+ const result = getSeriesValues(`__proto__,${xVal},1`, "\n", ",", false);
18
+
19
+ assert.equal(Object.prototype[xVal], undefined);
20
+ assert.deepEqual(result.xValues, [xVal]);
21
+ assert.equal(result.series.length, 1);
22
+ assert.equal(result.series[0].name, "__proto__");
23
+ assert.equal(Object.getPrototypeOf(result.series[0].data), null);
24
+ assert(hasOwn(result.series[0].data, xVal));
25
+ assert.equal(result.series[0].data[xVal], 1);
26
+ } finally {
27
+ delete Object.prototype[xVal];
28
+ }
29
+ }),
30
+
31
+ it("Charts: should keep __proto__ x-axis names as own data keys", () => {
32
+ const result = getSeriesValues("safe,__proto__,1", "\n", ",", false);
33
+
34
+ assert.equal(result.series.length, 1);
35
+ assert.equal(Object.getPrototypeOf(result.series[0].data), null);
36
+ assert(hasOwn(result.series[0].data, "__proto__"));
37
+ assert.equal(result.series[0].data.__proto__, 1);
38
+ }),
39
+
40
+ it("Protocol: should ignore inherited properties when rendering tables", () => {
41
+ const inheritedKey = "<img src=x onerror=alert(1)>";
42
+ delete Object.prototype[inheritedKey];
43
+
44
+ try {
45
+ Object.prototype[inheritedKey] = "polluted";
46
+
47
+ const html = objToTable({safe: "value"});
48
+
49
+ assert(!html.includes(inheritedKey));
50
+ assert(!html.includes("polluted"));
51
+ assert(html.includes("safe"));
52
+ assert(html.includes("value"));
53
+ } finally {
54
+ delete Object.prototype[inheritedKey];
55
+ }
56
+ }),
57
+
58
+ it("Protocol: should escape table keys and scalar values", () => {
59
+ const obj = {
60
+ "<b>field</b>": "<img src=x onerror=alert(1)>",
61
+ };
62
+
63
+ const html = objToTable(obj);
64
+
65
+ assert(!html.includes("<b>field</b>"));
66
+ assert(!html.includes("<img src=x onerror=alert(1)>"));
67
+ assert(html.includes("&lt;b&gt;field&lt;/b&gt;"));
68
+ assert(html.includes("&lt;img src=x onerror=alert(1)&gt;"));
69
+ }),
70
+
71
+ it("Series chart and Parse UDP: should not expose polluted prototype data as HTML", () => {
72
+ const xVal = "<img src=x onerror=alert(document.domain)>";
73
+ delete Object.prototype[xVal];
74
+
75
+ try {
76
+ const chartHtml = new SeriesChart().run(
77
+ `__proto__,${xVal},1`,
78
+ ["Line feed", "Comma", "", 1, "red"]
79
+ );
80
+ assert.equal(Object.prototype[xVal], undefined);
81
+
82
+ const parseUDP = new ParseUDP();
83
+ const tableHtml = parseUDP.present(parseUDP.run(chartHtml, ["Raw"]));
84
+
85
+ assert(!/<img|onerror|alert\(/.test(tableHtml));
86
+ } finally {
87
+ delete Object.prototype[xVal];
88
+ }
89
+ }),
90
+ ]);
@@ -109,6 +109,38 @@ TestRegister.addApiTests([
109
109
  assert.equal(3 + result, 35);
110
110
  }),
111
111
 
112
+ it("toBase32: should support non-BMP Unicode alphabets", () => {
113
+ const alphabet = "🀇🀈🀉🀊🀋🀌🀍🀎🀏🀙🀚🀛🀜🀝🀞🀟🀠🀡🀐🀑🀒🀓🀔🀕🀖🀗🀘🀀🀁🀂🀃🀅";
114
+
115
+ const result = chef.toBase32("hello", {alphabet}).toString();
116
+
117
+ // Should not contain replacement characters
118
+ assert.equal(result.includes("�"), false);
119
+
120
+ // Should contain only symbols from the alphabet
121
+ for (const ch of Array.from(result)) {
122
+ assert.ok(Array.from(alphabet).includes(ch));
123
+ }
124
+
125
+ // "hello" => 8 Base32 symbols
126
+ assert.equal(Array.from(result).length, 8);
127
+ }),
128
+
129
+ it("toBase32: should omit padding for 32-character Unicode alphabets", () => {
130
+ const alphabet = "🀇🀈🀉🀊🀋🀌🀍🀎🀏🀙🀚🀛🀜🀝🀞🀟🀠🀡🀐🀑🀒🀓🀔🀕🀖🀗🀘🀀🀁🀂🀃🀅";
131
+
132
+ const result = chef.toBase32("hell", {alphabet}).toString();
133
+
134
+ // Should not leak undefined from array indexing
135
+ assert.equal(result.includes("undefined"), false);
136
+
137
+ // Should not contain replacement characters
138
+ assert.equal(result.includes("�"), false);
139
+
140
+ // Unpadded Base32 output for 4-byte input should be 7 symbols
141
+ assert.equal(Array.from(result).length, 7);
142
+ }),
143
+
112
144
  it("chef.help: should exist", () => {
113
145
  assert(chef.help);
114
146
  }),
@@ -136,7 +168,7 @@ TestRegister.addApiTests([
136
168
 
137
169
  it("chef.help: returns multiple results", () => {
138
170
  const result = chef.help("base 64");
139
- assert.strictEqual(result.length, 13);
171
+ assert.strictEqual(result.length, 14);
140
172
  }),
141
173
 
142
174
  it("chef.help: looks in description for matches too", () => {
@@ -264,6 +264,18 @@ Full hash: $2a$10$ODeP1.6fMsb.ENk2ngPUCO7qTGVPyHA9TqDVcyupyed8FjsiF65L6`;
264
264
  assert.strictEqual(result.toString(), "Fit as a Fiddle");
265
265
  }),
266
266
 
267
+ it("Bzip2 Compress: round-trips through the Node API", async () => {
268
+ const compressed = await chef.bzip2Compress("The quick brown fox.");
269
+ const result = await chef.bzip2Decompress(compressed);
270
+ assert.strictEqual(result.toString(), "The quick brown fox.");
271
+ }),
272
+
273
+ it("Avro to JSON: decodes an object container file through the Node API", async () => {
274
+ const avro = chef.fromHex("4f626a0104166176726f2e736368656d6196017b2274797065223a227265636f7264222c226e616d65223a22736d616c6c222c226669656c6473223a5b7b226e616d65223a226e616d65222c2274797065223a22737472696e67227d5d7d146176726f2e636f646563086e756c6c004e0247632e3702e5b75cdab9a62f1541020e0c6d796e616d654e0247632e3702e5b75cdab9a62f1541");
275
+ const result = await chef.avroToJSON(avro);
276
+ assert.strictEqual(result.toString(), "{\n \"name\": \"myname\"\n}");
277
+ }),
278
+
267
279
  it("cartesianProduct: binary string", () => {
268
280
  const result = cartesianProduct("1:2\\n\\n3:4", {
269
281
  itemDelimiter: ":",
@@ -605,8 +617,9 @@ Top Drawer`, {
605
617
 
606
618
  it("Generate HOTP", () => {
607
619
  const result = chef.generateHOTP("JBSWY3DPEHPK3PXP", {
620
+ name: "Account",
608
621
  });
609
- const expected = `URI: otpauth://hotp/?secret=JBSWY3DPEHPK3PXP&algorithm=SHA1&digits=6&counter=0
622
+ const expected = `URI: otpauth://hotp/Account?secret=JBSWY3DPEHPK3PXP&algorithm=SHA1&digits=6&counter=0
610
623
 
611
624
  Password: 282760`;
612
625
  assert.strictEqual(result.toString(), expected);
@@ -728,6 +741,18 @@ Arguments:
728
741
  assert.strictEqual(result.toString(), expected);
729
742
  }),
730
743
 
744
+ it("Parse URI with constructor and __proto__ arguments", () => {
745
+ const result = chef.parseURI("https://example.com/?constructor=ok&__proto__=hello");
746
+ const expected = `Protocol: https:
747
+ Hostname: example.com
748
+ Path name: /
749
+ Arguments:
750
+ \tconstructor = ok
751
+ \t__proto__ = hello
752
+ `;
753
+ assert.strictEqual(result.toString(), expected);
754
+ }),
755
+
731
756
  it("Parse user agent", () => {
732
757
  const result = chef.parseUserAgent("Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0 ");
733
758
  const expected = `Browser
@@ -20,6 +20,9 @@ import "./tests/A1Z26CipherDecode.mjs";
20
20
  import "./tests/AESKeyWrap.mjs";
21
21
  import "./tests/AlternatingCaps.mjs";
22
22
  import "./tests/AnalyseUUID.mjs";
23
+ import "./tests/Arithmetic.mjs";
24
+ import "./tests/Ascon.mjs";
25
+ import "./tests/AutomatedValidation.mjs";
23
26
  import "./tests/AvroToJSON.mjs";
24
27
  import "./tests/BCD.mjs";
25
28
  import "./tests/BLAKE2b.mjs";
@@ -41,6 +44,7 @@ import "./tests/ByteRepr.mjs";
41
44
  import "./tests/CBORDecode.mjs";
42
45
  import "./tests/CBOREncode.mjs";
43
46
  import "./tests/CMAC.mjs";
47
+ import "./tests/COBS.mjs";
44
48
  import "./tests/CRCChecksum.mjs";
45
49
  import "./tests/CSV.mjs";
46
50
  import "./tests/CaesarBoxCipher.mjs";
@@ -64,6 +68,7 @@ import "./tests/ConvertLeetSpeak.mjs";
64
68
  import "./tests/ConvertToNATOAlphabet.mjs";
65
69
  import "./tests/Crypt.mjs";
66
70
  import "./tests/DateTime.mjs";
71
+ import "./tests/DechunkHTTPResponse.mjs";
67
72
  import "./tests/DefangIP.mjs";
68
73
  import "./tests/DisassembleARM.mjs";
69
74
  import "./tests/DropNthBytes.mjs";
@@ -71,6 +76,7 @@ import "./tests/ECDSA.mjs";
71
76
  import "./tests/ELFInfo.mjs";
72
77
  import "./tests/Enigma.mjs";
73
78
  import "./tests/EscapeSmartCharacters.mjs";
79
+ import "./tests/ExtendedGCD.mjs";
74
80
  import "./tests/ExtractAudioMetadata.mjs";
75
81
  import "./tests/ExtractEmailAddresses.mjs";
76
82
  import "./tests/ExtractHashes.mjs";
@@ -81,6 +87,7 @@ import "./tests/FlaskSession.mjs";
81
87
  import "./tests/FletcherChecksum.mjs";
82
88
  import "./tests/Float.mjs";
83
89
  import "./tests/Fork.mjs";
90
+ import "./tests/FromBase.mjs";
84
91
  import "./tests/FromDecimal.mjs";
85
92
  import "./tests/GOST.mjs";
86
93
  import "./tests/GenerateAllChecksums.mjs";
@@ -93,6 +100,7 @@ import "./tests/Gunzip.mjs";
93
100
  import "./tests/Gzip.mjs";
94
101
  import "./tests/HASSH.mjs";
95
102
  import "./tests/HKDF.mjs";
103
+ import "./tests/HTMLEntity.mjs";
96
104
  import "./tests/Hash.mjs";
97
105
  import "./tests/HaversineDistance.mjs";
98
106
  import "./tests/Hex.mjs";
@@ -121,10 +129,13 @@ import "./tests/LevenshteinDistance.mjs";
121
129
  import "./tests/Lorenz.mjs";
122
130
  import "./tests/LuhnChecksum.mjs";
123
131
  import "./tests/MIMEDecoding.mjs";
132
+ import "./tests/MOD.mjs";
124
133
  import "./tests/MS.mjs";
125
134
  import "./tests/Magic.mjs";
126
135
  import "./tests/Media.mjs";
136
+ import "./tests/Median.mjs";
127
137
  import "./tests/Modhex.mjs";
138
+ import "./tests/ModularInverse.mjs";
128
139
  import "./tests/MorseCode.mjs";
129
140
  import "./tests/MultipleBombe.mjs";
130
141
  import "./tests/MurmurHash3.mjs";
@@ -136,6 +147,7 @@ import "./tests/PEMtoHex.mjs";
136
147
  import "./tests/PGP.mjs";
137
148
  import "./tests/PHP.mjs";
138
149
  import "./tests/PHPSerialize.mjs";
150
+ import "./tests/PRESENT.mjs";
139
151
  import "./tests/ParityBit.mjs";
140
152
  import "./tests/ParseCSR.mjs";
141
153
  import "./tests/ParseEthernetFrame.mjs";
@@ -163,6 +175,7 @@ import "./tests/Register.mjs";
163
175
  import "./tests/RegularExpression.mjs";
164
176
  import "./tests/RemoveANSIEscapeCodes.mjs";
165
177
  import "./tests/RenderMarkdown.mjs";
178
+ import "./tests/RenderPDF.mjs";
166
179
  import "./tests/RisonEncodeDecode.mjs";
167
180
  import "./tests/Rotate.mjs";
168
181
  import "./tests/SIGABA.mjs";
@@ -174,6 +187,7 @@ import "./tests/SeqUtils.mjs";
174
187
  import "./tests/SetDifference.mjs";
175
188
  import "./tests/SetIntersection.mjs";
176
189
  import "./tests/SetUnion.mjs";
190
+ import "./tests/ShowOnMap.mjs";
177
191
  import "./tests/Shuffle.mjs";
178
192
  // Cannot test operations that use the File type yet
179
193
  // import "./tests/SplitColourChannels.mjs";
@@ -184,15 +198,18 @@ import "./tests/StripUDPHeader.mjs";
184
198
  import "./tests/Subsection.mjs";
185
199
  import "./tests/SwapCase.mjs";
186
200
  import "./tests/SymmetricDifference.mjs";
201
+ import "./tests/TEA.mjs";
187
202
  import "./tests/TakeNthBytes.mjs";
188
203
  import "./tests/Template.mjs";
189
204
  import "./tests/TextEncodingBruteForce.mjs";
190
205
  import "./tests/TextIntegerConverter.mjs";
191
206
  import "./tests/ToFromInsensitiveRegex.mjs";
192
207
  import "./tests/TranslateDateTimeFormat.mjs";
208
+ import "./tests/Twofish.mjs";
193
209
  import "./tests/Typex.mjs";
194
210
  import "./tests/URLEncodeDecode.mjs";
195
211
  import "./tests/UnescapeString.mjs";
212
+ import "./tests/UnescapeUnicodeCharacters.mjs";
196
213
  import "./tests/Unicode.mjs";
197
214
  import "./tests/Wrap.mjs";
198
215
  import "./tests/XORChecksum.mjs";
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Tests for arithmetical operations
3
+ *
4
+ * @copyright Crown Copyright 2026
5
+ * @license Apache-2.0
6
+ */
7
+
8
+ import TestRegister from "../../lib/TestRegister.mjs";
9
+
10
+ TestRegister.addTests([
11
+ {
12
+ name: "Subtract",
13
+ input: "321,123,test",
14
+ expectedOutput: "198",
15
+ recipeConfig: [
16
+ {
17
+ "op": "Subtract",
18
+ "args": ["Comma"]
19
+ },
20
+ ],
21
+ },
22
+ {
23
+ name: "Subtract - no valid input",
24
+ input: "test",
25
+ expectedOutput: "NaN",
26
+ recipeConfig: [
27
+ {
28
+ "op": "Subtract",
29
+ "args": ["Comma"]
30
+ },
31
+ ],
32
+ },
33
+ ]);