diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json
index 6ad3adb7..cc71d5e9 100644
--- a/src/core/config/Categories.json
+++ b/src/core/config/Categories.json
@@ -300,7 +300,8 @@
"Decode NetBIOS Name",
"Defang URL",
"Fang URL",
- "Defang IP Addresses"
+ "Defang IP Addresses",
+ "Fang IP Addresses"
]
},
{
diff --git a/src/core/operations/FangIPAddresses.mjs b/src/core/operations/FangIPAddresses.mjs
new file mode 100644
index 00000000..9ebf498b
--- /dev/null
+++ b/src/core/operations/FangIPAddresses.mjs
@@ -0,0 +1,56 @@
+/**
+ * @author HarelKatz [github.com/HarelKatz]
+ * @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 a defanged IPv4 or IPv6 address and 'Fangs' it, restoring it to a valid address. The inverse of Defang IP Addresses.
e.g. 192[.]168[.]1[.]1 becomes 192.168.1.1.";
+ 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;
+
+ if (dots) input = input.replace(/\[\.\]/g, ".");
+ if (colons) input = input.replace(/\[:\]/g, ":");
+
+ return input;
+ }
+
+}
+
+export default FangIPAddresses;
diff --git a/tests/operations/tests/FangIPAddresses.mjs b/tests/operations/tests/FangIPAddresses.mjs
new file mode 100644
index 00000000..abb46483
--- /dev/null
+++ b/tests/operations/tests/FangIPAddresses.mjs
@@ -0,0 +1,88 @@
+/**
+ * Fang IP Addresses tests
+ *
+ * @author HarelKatz [github.com/HarelKatz]
+ * @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 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: Multiple defanged IPs in surrounding text",
+ input: "Connect to 10[.]0[.]0[.]1 or 192[.]168[.]1[.]254",
+ expectedOutput: "Connect to 10.0.0.1 or 192.168.1.254",
+ recipeConfig: [
+ {
+ op: "Fang IP Addresses",
+ args: [true, true],
+ },
+ ],
+ },
+ {
+ name: "Fang IP: Plain IP input is unchanged",
+ input: "192.168.1.1",
+ expectedOutput: "192.168.1.1",
+ recipeConfig: [
+ {
+ op: "Fang IP Addresses",
+ args: [true, true],
+ },
+ ],
+ },
+ {
+ name: "Fang IP: Restore dots only",
+ input: "192[.]168[.]1[.]1 ::[:]1",
+ expectedOutput: "192.168.1.1 ::[:]1",
+ recipeConfig: [
+ {
+ op: "Fang IP Addresses",
+ args: [true, false],
+ },
+ ],
+ },
+ {
+ name: "Fang IP: Restore colons only",
+ input: "192[.]168[.]1[.]1 2001[:]db8[:][:]1",
+ expectedOutput: "192[.]168[.]1[.]1 2001:db8::1",
+ recipeConfig: [
+ {
+ op: "Fang IP Addresses",
+ args: [false, true],
+ },
+ ],
+ },
+]);