diff --git a/README.md b/README.md
index 6da7d3ec..50b1a44d 100755
--- a/README.md
+++ b/README.md
@@ -72,6 +72,7 @@ You can use as many operations as you like in simple or complex ways. Some examp
- [Carry out different operations on data of different types][8]
- [Use parts of the input as arguments to operations][9]
- [Perform AES decryption, extracting the IV from the beginning of the cipher stream][10]
+ - [A simpler way to perform the same AES Decryption][13]
- [Automagically detect several layers of nested encoding][12]
@@ -147,6 +148,7 @@ CyberChef is released under the [Apache 2.0 Licence](https://www.apache.org/lice
[7]: https://gchq.github.io/CyberChef/#recipe=Fork('%5C%5Cn','%5C%5Cn',false)From_UNIX_Timestamp('Seconds%20(s)')&input=OTc4MzQ2ODAwCjEwMTI2NTEyMDAKMTA0NjY5NjQwMAoxMDgxMDg3MjAwCjExMTUzMDUyMDAKMTE0OTYwOTYwMA
[8]: https://gchq.github.io/CyberChef/#recipe=Fork('%5C%5Cn','%5C%5Cn',false)Conditional_Jump('1',false,'base64',10)To_Hex('Space')Return()Label('base64')To_Base64('A-Za-z0-9%2B/%3D')&input=U29tZSBkYXRhIHdpdGggYSAxIGluIGl0ClNvbWUgZGF0YSB3aXRoIGEgMiBpbiBpdA
[9]: https://gchq.github.io/CyberChef/#recipe=Register('key%3D(%5B%5C%5Cda-f%5D*)',true,false)Find_/_Replace(%7B'option':'Regex','string':'.*data%3D(.*)'%7D,'$1',true,false,true)RC4(%7B'option':'Hex','string':'$R0'%7D,'Hex','Latin1')&input=aHR0cDovL21hbHdhcmV6LmJpei9iZWFjb24ucGhwP2tleT0wZTkzMmE1YyZkYXRhPThkYjdkNWViZTM4NjYzYTU0ZWNiYjMzNGUzZGIxMQ
- [10]: https://gchq.github.io/CyberChef/#recipe=Register('(.%7B32%7D)',true,false)Drop_bytes(0,32,false)AES_Decrypt(%7B'option':'Hex','string':'1748e7179bd56570d51fa4ba287cc3e5'%7D,%7B'option':'Hex','string':'$R0'%7D,'CTR','Hex','Raw',%7B'option':'Hex','string':''%7D)&input=NTFlMjAxZDQ2MzY5OGVmNWY3MTdmNzFmNWI0NzEyYWYyMGJlNjc0YjNiZmY1M2QzODU0NjM5NmVlNjFkYWFjNDkwOGUzMTljYTNmY2Y3MDg5YmZiNmIzOGVhOTllNzgxZDI2ZTU3N2JhOWRkNmYzMTFhMzk0MjBiODk3OGU5MzAxNGIwNDJkNDQ3MjZjYWVkZjU0MzZlYWY2NTI0MjljMGRmOTRiNTIxNjc2YzdjMmNlODEyMDk3YzI3NzI3M2M3YzcyY2Q4OWFlYzhkOWZiNGEyNzU4NmNjZjZhYTBhZWUyMjRjMzRiYTNiZmRmN2FlYjFkZGQ0Nzc2MjJiOTFlNzJjOWU3MDlhYjYwZjhkYWY3MzFlYzBjYzg1Y2UwZjc0NmZmMTU1NGE1YTNlYzI5MWNhNDBmOWU2MjlhODcyNTkyZDk4OGZkZDgzNDUzNGFiYTc5YzFhZDE2NzY3NjlhN2MwMTBiZjA0NzM5ZWNkYjY1ZDk1MzAyMzcxZDYyOWQ5ZTM3ZTdiNGEzNjFkYTQ2OGYxZWQ1MzU4OTIyZDJlYTc1MmRkMTFjMzY2ZjMwMTdiMTRhYTAxMWQyYWYwM2M0NGY5NTU3OTA5OGExNWUzY2Y5YjQ0ODZmOGZmZTljMjM5ZjM0ZGU3MTUxZjZjYTY1MDBmZTRiODUwYzNmMWMwMmU4MDFjYWYzYTI0NDY0NjE0ZTQyODAxNjE1YjhmZmFhMDdhYzgyNTE0OTNmZmRhN2RlNWRkZjMzNjg4ODBjMmI5NWIwMzBmNDFmOGYxNTA2NmFkZDA3MWE2NmNmNjBlNWY0NmYzYTIzMGQzOTdiNjUyOTYzYTIxYTUzZg
+ [10]: https://gchq.github.io/CyberChef/#recipe=Register('(.%7B32%7D)',true,false,false)Drop_bytes(0,32,false)AES_Decrypt(%7B'option':'Hex','string':'1748e7179bd56570d51fa4ba287cc3e5'%7D,%7B'option':'Hex','string':'$R0'%7D,16,'CTR','Hex','Raw',%7B'option':'Hex','string':''%7D,%7B'option':'Hex','string':''%7D,'Off')&input=NTFlMjAxZDQ2MzY5OGVmNWY3MTdmNzFmNWI0NzEyYWYyMGJlNjc0YjNiZmY1M2QzODU0NjM5NmVlNjFkYWFjNDkwOGUzMTljYTNmY2Y3MDg5YmZiNmIzOGVhOTllNzgxZDI2ZTU3N2JhOWRkNmYzMTFhMzk0MjBiODk3OGU5MzAxNGIwNDJkNDQ3MjZjYWVkZjU0MzZlYWY2NTI0MjljMGRmOTRiNTIxNjc2YzdjMmNlODEyMDk3YzI3NzI3M2M3YzcyY2Q4OWFlYzhkOWZiNGEyNzU4NmNjZjZhYTBhZWUyMjRjMzRiYTNiZmRmN2FlYjFkZGQ0Nzc2MjJiOTFlNzJjOWU3MDlhYjYwZjhkYWY3MzFlYzBjYzg1Y2UwZjc0NmZmMTU1NGE1YTNlYzI5MWNhNDBmOWU2MjlhODcyNTkyZDk4OGZkZDgzNDUzNGFiYTc5YzFhZDE2NzY3NjlhN2MwMTBiZjA0NzM5ZWNkYjY1ZDk1MzAyMzcxZDYyOWQ5ZTM3ZTdiNGEzNjFkYTQ2OGYxZWQ1MzU4OTIyZDJlYTc1MmRkMTFjMzY2ZjMwMTdiMTRhYTAxMWQyYWYwM2M0NGY5NTU3OTA5OGExNWUzY2Y5YjQ0ODZmOGZmZTljMjM5ZjM0ZGU3MTUxZjZjYTY1MDBmZTRiODUwYzNmMWMwMmU4MDFjYWYzYTI0NDY0NjE0ZTQyODAxNjE1YjhmZmFhMDdhYzgyNTE0OTNmZmRhN2RlNWRkZjMzNjg4ODBjMmI5NWIwMzBmNDFmOGYxNTA2NmFkZDA3MWE2NmNmNjBlNWY0NmYzYTIzMGQzOTdiNjUyOTYzYTIxYTUzZg
[11]: https://gchq.github.io/CyberChef/#recipe=XOR(%7B'option':'Hex','string':'3a'%7D,'Standard',false)To_Hexdump(16,false,false)&input=VGhlIGFuc3dlciB0byB0aGUgdWx0aW1hdGUgcXVlc3Rpb24gb2YgbGlmZSwgdGhlIFVuaXZlcnNlLCBhbmQgZXZlcnl0aGluZyBpcyA0Mi4
[12]: https://gchq.github.io/CyberChef/#recipe=Magic(3,false,false)&input=V1VhZ3dzaWFlNm1QOGdOdENDTFVGcENwQ0IyNlJtQkRvREQ4UGFjZEFtekF6QlZqa0syUXN0RlhhS2hwQzZpVVM3UkhxWHJKdEZpc29SU2dvSjR3aGptMWFybTg2NHFhTnE0UmNmVW1MSHJjc0FhWmM1VFhDWWlmTmRnUzgzZ0RlZWpHWDQ2Z2FpTXl1QlY2RXNrSHQxc2NnSjg4eDJ0TlNvdFFEd2JHWTFtbUNvYjJBUkdGdkNLWU5xaU45aXBNcTFaVTFtZ2tkYk51R2NiNzZhUnRZV2hDR1VjOGc5M1VKdWRoYjhodHNoZVpud1RwZ3FoeDgzU1ZKU1pYTVhVakpUMnptcEM3dVhXdHVtcW9rYmRTaTg4WXRrV0RBYzFUb291aDJvSDRENGRkbU5LSldVRHBNd21uZ1VtSzE0eHdtb21jY1BRRTloTTE3MkFQblNxd3hkS1ExNzJSa2NBc3lzbm1qNWdHdFJtVk5OaDJzMzU5d3I2bVMyUVJQ
+ [13]: https://gchq.github.io/CyberChef/#recipe=AES_Decrypt(%7B'option':'Hex','string':'1748e7179bd56570d51fa4ba287cc3e5'%7D,%7B'option':'Hex','string':'$R0'%7D,16,'CTR','Hex','Raw',%7B'option':'Hex','string':''%7D,%7B'option':'Hex','string':''%7D,'From%20start')&input=NTFlMjAxZDQ2MzY5OGVmNWY3MTdmNzFmNWI0NzEyYWYyMGJlNjc0YjNiZmY1M2QzODU0NjM5NmVlNjFkYWFjNDkwOGUzMTljYTNmY2Y3MDg5YmZiNmIzOGVhOTllNzgxZDI2ZTU3N2JhOWRkNmYzMTFhMzk0MjBiODk3OGU5MzAxNGIwNDJkNDQ3MjZjYWVkZjU0MzZlYWY2NTI0MjljMGRmOTRiNTIxNjc2YzdjMmNlODEyMDk3YzI3NzI3M2M3YzcyY2Q4OWFlYzhkOWZiNGEyNzU4NmNjZjZhYTBhZWUyMjRjMzRiYTNiZmRmN2FlYjFkZGQ0Nzc2MjJiOTFlNzJjOWU3MDlhYjYwZjhkYWY3MzFlYzBjYzg1Y2UwZjc0NmZmMTU1NGE1YTNlYzI5MWNhNDBmOWU2MjlhODcyNTkyZDk4OGZkZDgzNDUzNGFiYTc5YzFhZDE2NzY3NjlhN2MwMTBiZjA0NzM5ZWNkYjY1ZDk1MzAyMzcxZDYyOWQ5ZTM3ZTdiNGEzNjFkYTQ2OGYxZWQ1MzU4OTIyZDJlYTc1MmRkMTFjMzY2ZjMwMTdiMTRhYTAxMWQyYWYwM2M0NGY5NTU3OTA5OGExNWUzY2Y5YjQ0ODZmOGZmZTljMjM5ZjM0ZGU3MTUxZjZjYTY1MDBmZTRiODUwYzNmMWMwMmU4MDFjYWYzYTI0NDY0NjE0ZTQyODAxNjE1YjhmZmFhMDdhYzgyNTE0OTNmZmRhN2RlNWRkZjMzNjg4ODBjMmI5NWIwMzBmNDFmOGYxNTA2NmFkZDA3MWE2NmNmNjBlNWY0NmYzYTIzMGQzOTdiNjUyOTYzYTIxYTUzZg
diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json
index 0e99e291..f000ffde 100644
--- a/src/core/config/Categories.json
+++ b/src/core/config/Categories.json
@@ -49,6 +49,7 @@
"Escape Unicode Characters",
"Unescape Unicode Characters",
"Normalise Unicode",
+ "Escape Smart Characters",
"To Quoted Printable",
"From Quoted Printable",
"To Punycode",
diff --git a/src/core/operations/EscapeSmartCharacters.mjs b/src/core/operations/EscapeSmartCharacters.mjs
new file mode 100644
index 00000000..39444e94
--- /dev/null
+++ b/src/core/operations/EscapeSmartCharacters.mjs
@@ -0,0 +1,129 @@
+/**
+ * @author HarelKatz [github.com/HarelKatz]
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+
+/**
+ * Escape Smart Characters operation
+ */
+class EscapeSmartCharacters extends Operation {
+
+ /**
+ * EscapeSmartCharacters constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Escape Smart Characters";
+ this.module = "Default";
+ this.description = "Converts smart (typographic) Unicode characters — e.g. smart quotes, em/en dashes, ellipses, ©, ®, ™, arrows — into their plain ASCII equivalents.
Characters with no ASCII mapping (e.g. ☣) are handled according to the 'Unmappable characters' option.
e.g. “Hello” — world… becomes \"Hello\" -- world...";
+ this.infoURL = "";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ name: "Unmappable characters",
+ type: "option",
+ value: ["Include", "Remove", "Replace with '.'"]
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const [unmappable] = args;
+ let result = "";
+ for (const ch of input) {
+ if (ch.codePointAt(0) < 128) {
+ result += ch;
+ } else if (Object.prototype.hasOwnProperty.call(SMART_MAP, ch)) {
+ result += SMART_MAP[ch];
+ } else {
+ switch (unmappable) {
+ case "Remove":
+ break;
+ case "Replace with '.'":
+ result += ".";
+ break;
+ case "Include":
+ default:
+ result += ch;
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+}
+
+const SMART_MAP = {
+ // Smart double quotes
+ "“": "\"", // “ left double quotation mark
+ "”": "\"", // ” right double quotation mark
+ "„": "\"", // „ double low-9 quotation mark
+ "‟": "\"", // ‟ double high-reversed-9 quotation mark
+ "″": "\"", // ″ double prime
+
+ // Smart single quotes / apostrophes
+ "‘": "'", // ‘ left single quotation mark
+ "’": "'", // ’ right single quotation mark / apostrophe
+ "‚": "'", // ‚ single low-9 quotation mark
+ "‛": "'", // ‛ single high-reversed-9 quotation mark
+ "′": "'", // ′ prime
+
+ // Dashes & hyphens
+ "‐": "-", // ‐ hyphen
+ "‑": "-", // ‑ non-breaking hyphen
+ "‒": "-", // ‒ figure dash
+ "–": "-", // – en dash
+ "—": "--", // — em dash
+ "―": "--", // ― horizontal bar
+
+ // Ellipsis
+ "…": "...", // …
+
+ // Trademark / copyright symbols
+ "©": "(c)", // ©
+ "®": "(r)", // ®
+ "™": "(tm)", // ™
+
+ // Arrows
+ "←": "<--", // ←
+ "→": "-->", // →
+ "↑": "^", // ↑
+ "↓": "v", // ↓
+ "↔": "<->", // ↔
+ "⇐": "<==", // ⇐
+ "⇒": "==>", // ⇒
+ "⇔": "<=>", // ⇔
+
+ // Guillemets
+ "«": "<<", // «
+ "»": ">>", // »
+ "‹": "<", // ‹
+ "›": ">", // ›
+
+ // Math & misc symbols
+ "×": "x", // ×
+ "÷": "/", // ÷
+ "±": "+/-", // ±
+ "•": "*", // •
+ "·": ".", // ·
+
+ // Non-ASCII spaces
+ "\u00A0": " ", // NBSP
+ "\u2002": " ", // en space
+ "\u2003": " ", // em space
+ "\u2009": " ", // thin space
+ "\u200A": " " // hair space
+};
+
+export default EscapeSmartCharacters;
diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs
index c44270fd..44792560 100644
--- a/tests/operations/index.mjs
+++ b/tests/operations/index.mjs
@@ -69,6 +69,7 @@ import "./tests/DropNthBytes.mjs";
import "./tests/ECDSA.mjs";
import "./tests/ELFInfo.mjs";
import "./tests/Enigma.mjs";
+import "./tests/EscapeSmartCharacters.mjs";
import "./tests/ExtractAudioMetadata.mjs";
import "./tests/ExtractEmailAddresses.mjs";
import "./tests/ExtractHashes.mjs";
diff --git a/tests/operations/tests/EscapeSmartCharacters.mjs b/tests/operations/tests/EscapeSmartCharacters.mjs
new file mode 100644
index 00000000..428c3ad8
--- /dev/null
+++ b/tests/operations/tests/EscapeSmartCharacters.mjs
@@ -0,0 +1,132 @@
+/**
+ * Escape Smart Characters tests
+ *
+ * @author HarelKatz [github.com/HarelKatz]
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+import TestRegister from "../../lib/TestRegister.mjs";
+
+TestRegister.addTests([
+ {
+ "name": "Escape Smart Characters: smart quotes and apostrophes",
+ "input": "“Hello,” she said, ‘yes.’",
+ "expectedOutput": "\"Hello,\" she said, 'yes.'",
+ "recipeConfig": [
+ {
+ "op": "Escape Smart Characters",
+ "args": ["Include"]
+ }
+ ]
+ },
+ {
+ "name": "Escape Smart Characters: em dash, en dash and ellipsis",
+ "input": "page 1–3 — wait…",
+ "expectedOutput": "page 1-3 -- wait...",
+ "recipeConfig": [
+ {
+ "op": "Escape Smart Characters",
+ "args": ["Include"]
+ }
+ ]
+ },
+ {
+ "name": "Escape Smart Characters: trademark symbols",
+ "input": "Foo© Bar® Baz™",
+ "expectedOutput": "Foo(c) Bar(r) Baz(tm)",
+ "recipeConfig": [
+ {
+ "op": "Escape Smart Characters",
+ "args": ["Include"]
+ }
+ ]
+ },
+ {
+ "name": "Escape Smart Characters: arrows and guillemets",
+ "input": "← → ↔ ⇒ « »",
+ "expectedOutput": "<-- --> <-> ==> << >>",
+ "recipeConfig": [
+ {
+ "op": "Escape Smart Characters",
+ "args": ["Include"]
+ }
+ ]
+ },
+ {
+ "name": "Escape Smart Characters: math and misc",
+ "input": "3 × 4 ÷ 2 = 6, ±0.5 • item",
+ "expectedOutput": "3 x 4 / 2 = 6, +/-0.5 * item",
+ "recipeConfig": [
+ {
+ "op": "Escape Smart Characters",
+ "args": ["Include"]
+ }
+ ]
+ },
+ {
+ "name": "Escape Smart Characters: NBSP becomes regular space",
+ "input": "a b c",
+ "expectedOutput": "a b c",
+ "recipeConfig": [
+ {
+ "op": "Escape Smart Characters",
+ "args": ["Include"]
+ }
+ ]
+ },
+ {
+ "name": "Escape Smart Characters: unmappable Include preserves char",
+ "input": "warning: ☣ hazard",
+ "expectedOutput": "warning: ☣ hazard",
+ "recipeConfig": [
+ {
+ "op": "Escape Smart Characters",
+ "args": ["Include"]
+ }
+ ]
+ },
+ {
+ "name": "Escape Smart Characters: unmappable Remove drops char",
+ "input": "warning: ☣ hazard",
+ "expectedOutput": "warning: hazard",
+ "recipeConfig": [
+ {
+ "op": "Escape Smart Characters",
+ "args": ["Remove"]
+ }
+ ]
+ },
+ {
+ "name": "Escape Smart Characters: unmappable Replace substitutes dot",
+ "input": "warning: ☣ hazard",
+ "expectedOutput": "warning: . hazard",
+ "recipeConfig": [
+ {
+ "op": "Escape Smart Characters",
+ "args": ["Replace with '.'"]
+ }
+ ]
+ },
+ {
+ "name": "Escape Smart Characters: pure ASCII passes through",
+ "input": "hello world! 123",
+ "expectedOutput": "hello world! 123",
+ "recipeConfig": [
+ {
+ "op": "Escape Smart Characters",
+ "args": ["Include"]
+ }
+ ]
+ },
+ {
+ "name": "Escape Smart Characters: empty input",
+ "input": "",
+ "expectedOutput": "",
+ "recipeConfig": [
+ {
+ "op": "Escape Smart Characters",
+ "args": ["Include"]
+ }
+ ]
+ }
+]);