From 8ad1b45a2d46e70e79ab4232c44bf48104235742 Mon Sep 17 00:00:00 2001 From: Collin Laney <95191733+ColDog5044@users.noreply.github.com> Date: Mon, 29 Jun 2026 11:47:38 -0400 Subject: [PATCH] feat: add email defang/fang and IP fang operations --- src/core/config/Categories.json | 5 +- src/core/operations/DefangEmailAddresses.mjs | 85 ++++++++++++++++++++ src/core/operations/FangEmailAddresses.mjs | 68 ++++++++++++++++ src/core/operations/FangIPAddresses.mjs | 72 +++++++++++++++++ tests/operations/tests/DefangEmail.mjs | 43 ++++++++++ tests/operations/tests/FangEmail.mjs | 43 ++++++++++ tests/operations/tests/FangIP.mjs | 61 ++++++++++++++ 7 files changed, 376 insertions(+), 1 deletion(-) create mode 100644 src/core/operations/DefangEmailAddresses.mjs create mode 100644 src/core/operations/FangEmailAddresses.mjs create mode 100644 src/core/operations/FangIPAddresses.mjs create mode 100644 tests/operations/tests/DefangEmail.mjs create mode 100644 tests/operations/tests/FangEmail.mjs create mode 100644 tests/operations/tests/FangIP.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 2879a13a..c8a63839 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -286,7 +286,10 @@ "Decode NetBIOS Name", "Defang URL", "Fang URL", - "Defang IP Addresses" + "Defang IP Addresses", + "Fang IP Addresses", + "Defang email addresses", + "Fang email addresses" ] }, { diff --git a/src/core/operations/DefangEmailAddresses.mjs b/src/core/operations/DefangEmailAddresses.mjs new file mode 100644 index 00000000..db5dbc84 --- /dev/null +++ b/src/core/operations/DefangEmailAddresses.mjs @@ -0,0 +1,85 @@ +/** + * @author Collin Laney [collin.laney@coldogstudios.com] + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import {EMAIL_REGEX} from "../lib/Extract.mjs"; + +/** + * Defang email addresses operation + */ +class DefangEmailAddresses extends Operation { + + /** + * DefangEmailAddresses constructor + */ + constructor() { + super(); + + this.name = "Defang email addresses"; + this.module = "Default"; + this.description = "Takes email addresses and 'Defangs' them, meaning the addresses become invalid, removing the risk of accidentally using them as email links or indicators."; + this.infoURL = "https://isc.sans.edu/forums/diary/Defang+all+the+things/22744/"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Escape @", + type: "boolean", + value: true + }, + { + name: "Escape dots", + type: "boolean", + value: true + }, + { + name: "Process", + type: "option", + value: ["Valid email addresses", "Everything"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [at, dots, process] = args; + + switch (process) { + case "Valid email addresses": + input = input.replace(EMAIL_REGEX, x => { + return defangEmailAddress(x, at, dots); + }); + break; + case "Everything": + input = defangEmailAddress(input, at, dots); + break; + } + + return input; + } +} + + +/** + * Defangs a given email address + * + * @param {string} email + * @param {boolean} at + * @param {boolean} dots + * @returns {string} + */ +function defangEmailAddress(email, at, dots) { + if (at) email = email.replace(/@/g, "[@]"); + if (dots) email = email.replace(/\./g, "[.]"); + + return email; +} + +export default DefangEmailAddresses; diff --git a/src/core/operations/FangEmailAddresses.mjs b/src/core/operations/FangEmailAddresses.mjs new file mode 100644 index 00000000..a034f64f --- /dev/null +++ b/src/core/operations/FangEmailAddresses.mjs @@ -0,0 +1,68 @@ +/** + * @author Collin Laney [collin.laney@coldogstudios.com] + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * Fang email addresses operation + */ +class FangEmailAddresses extends Operation { + + /** + * FangEmailAddresses constructor + */ + constructor() { + super(); + + this.name = "Fang email addresses"; + this.module = "Default"; + this.description = "Takes 'Defanged' email addresses and 'Fangs' them, restoring the alterations that made them invalid."; + this.infoURL = "https://isc.sans.edu/forums/diary/Defang+all+the+things/22744/"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Restore [@] and [at]", + type: "boolean", + value: true + }, + { + name: "Restore [.]", + type: "boolean", + value: true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [at, dots] = args; + + return fangEmailAddress(input, at, dots); + } +} + + +/** + * Fangs a given email address + * + * @param {string} email + * @param {boolean} at + * @param {boolean} dots + * @returns {string} + */ +function fangEmailAddress(email, at, dots) { + if (at) email = email.replace(/\[@\]|\[at\]/gi, "@"); + if (dots) email = email.replace(/\[\.\]/g, "."); + + return email; +} + +export default FangEmailAddresses; diff --git a/src/core/operations/FangIPAddresses.mjs b/src/core/operations/FangIPAddresses.mjs new file mode 100644 index 00000000..9883364b --- /dev/null +++ b/src/core/operations/FangIPAddresses.mjs @@ -0,0 +1,72 @@ +/** + * @author Collin Laney [collin.laney@coldogstudios.com] + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * Fang IP Addresses operation + */ +class FangIPAddresses extends Operation { + + /** + * FangIPAddresses constructor + */ + constructor() { + super(); + + this.name = "Fang IP Addresses"; + this.module = "Default"; + this.description = "Takes 'Defanged' IPv4 or IPv6 addresses and 'Fangs' them, restoring the alterations that made them invalid."; + this.infoURL = "https://isc.sans.edu/forums/diary/Defang+all+the+things/22744/"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Restore [.]", + type: "boolean", + value: true + }, + { + name: "Restore [:]", + type: "boolean", + value: true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [dots, colons] = args; + + input = input.replace(DEFANGED_IPV4_REGEX, x => { + return dots ? x.replace(/\[\.\]/g, ".") : x; + }); + + input = input.replace(DEFANGED_IPV6_REGEX, x => { + return colons ? x.replace(/\[:\]/g, ":") : x; + }); + + return input; + } +} + +export default FangIPAddresses; + + +/** + * Defanged IPv4 regular expression + */ +const DEFANGED_IPV4_REGEX = new RegExp("(?:(?:\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])\\[\\.\\]){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d|\\d)(?:\\/\\d{1,2})?", "g"); + + +/** + * Defanged IPv6 regular expression + */ +const DEFANGED_IPV6_REGEX = new RegExp("(?:[\\dA-Fa-f]{0,4}\\[:\\]){2,7}[\\dA-Fa-f]{0,4}(?:\\/\\d{1,3})?", "g"); diff --git a/tests/operations/tests/DefangEmail.mjs b/tests/operations/tests/DefangEmail.mjs new file mode 100644 index 00000000..66f1bbf0 --- /dev/null +++ b/tests/operations/tests/DefangEmail.mjs @@ -0,0 +1,43 @@ +/** + * Defang email address tests. + * + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Defang email addresses: valid email address", + input: "Contact security@example.com for details.", + expectedOutput: "Contact security[@]example[.]com for details.", + recipeConfig: [ + { + op: "Defang email addresses", + args: [true, true, "Valid email addresses"], + }, + ], + }, + { + name: "Defang email addresses: multiple addresses", + input: "one@example.com firstname.lastname@example.co.uk", + expectedOutput: "one[@]example[.]com firstname[.]lastname[@]example[.]co[.]uk", + recipeConfig: [ + { + op: "Defang email addresses", + args: [true, true, "Valid email addresses"], + }, + ], + }, + { + name: "Defang email addresses: everything", + input: "Use @ and . anywhere", + expectedOutput: "Use [@] and [.] anywhere", + recipeConfig: [ + { + op: "Defang email addresses", + args: [true, true, "Everything"], + }, + ], + }, +]); diff --git a/tests/operations/tests/FangEmail.mjs b/tests/operations/tests/FangEmail.mjs new file mode 100644 index 00000000..a9d4e447 --- /dev/null +++ b/tests/operations/tests/FangEmail.mjs @@ -0,0 +1,43 @@ +/** + * Fang email address tests. + * + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Fang email addresses: defanged email address", + input: "Contact security[@]example[.]com for details.", + expectedOutput: "Contact security@example.com for details.", + recipeConfig: [ + { + op: "Fang email addresses", + args: [true, true], + }, + ], + }, + { + name: "Fang email addresses: defanged with [at]", + input: "Contact security[at]example[.]com for details.", + expectedOutput: "Contact security@example.com for details.", + recipeConfig: [ + { + op: "Fang email addresses", + args: [true, true], + }, + ], + }, + { + name: "Fang email addresses: restore at only", + input: "firstname[.]lastname[@]example[.]com", + expectedOutput: "firstname[.]lastname@example[.]com", + recipeConfig: [ + { + op: "Fang email addresses", + args: [true, false], + }, + ], + }, +]); diff --git a/tests/operations/tests/FangIP.mjs b/tests/operations/tests/FangIP.mjs new file mode 100644 index 00000000..2aa72233 --- /dev/null +++ b/tests/operations/tests/FangIP.mjs @@ -0,0 +1,61 @@ +/** + * Fang IP address tests. + * + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Fang IP: Valid IPV4", + input: "192[.]168[.]1[.]1", + expectedOutput: "192.168.1.1", + recipeConfig: [ + { + op: "Fang IP Addresses", + args: [true, true], + }, + ], + }, { + name: "Fang IP: Valid IPV4 CIDR", + input: "10[.]0[.]0[.]0/24", + expectedOutput: "10.0.0.0/24", + recipeConfig: [ + { + op: "Fang IP Addresses", + args: [true, true], + }, + ], + }, { + name: "Fang IP: Valid IPV6", + input: "2001[:]0db8[:]85a3[:]0000[:]0000[:]8a2e[:]0370[:]7343", + expectedOutput: "2001:0db8:85a3:0000:0000:8a2e:0370:7343", + recipeConfig: [ + { + op: "Fang IP Addresses", + args: [true, true], + }, + ], + }, { + name: "Fang IP: Valid IPV6 Shorthand", + input: "2001[:]db8[:]3c4d[:]15[:][:]1a2f[:]1a2b", + expectedOutput: "2001:db8:3c4d:15::1a2f:1a2b", + recipeConfig: [ + { + op: "Fang IP Addresses", + args: [true, true], + }, + ], + }, { + name: "Fang IP: Does not fang defanged domain", + input: "example[.]com", + expectedOutput: "example[.]com", + recipeConfig: [ + { + op: "Fang IP Addresses", + args: [true, true], + }, + ], + }, +]);