From 50a7613bfd80426910080eae341736910eaf4627 Mon Sep 17 00:00:00 2001
From: HarelKatz <34490550+HarelKatz@users.noreply.github.com>
Date: Fri, 15 May 2026 18:06:21 +0300
Subject: [PATCH] feat: add 'Last only' defang mode to Defang IP Addresses
Adds an opt-in "Last only" boolean argument (default false to preserve
existing behaviour) to the Defang IP Addresses operation. When enabled,
only the final separator of each matched IP is replaced:
- IPv4: 1.2.3.4 -> 1.2.3[.]4 (instead of 1[.]2[.]3[.]4)
- IPv6: 2001:0db8::1 -> 2001:0db8::[:]1
This matches the common SOC-tool default (e.g. Splunk) where the
defanged form stays visually close to the original while still
rendering the address invalid.
---
src/core/operations/DefangIPAddresses.mjs | 36 +++++++++++++++++---
tests/operations/tests/DefangIP.mjs | 40 +++++++++++++++++++++++
2 files changed, 72 insertions(+), 4 deletions(-)
diff --git a/src/core/operations/DefangIPAddresses.mjs b/src/core/operations/DefangIPAddresses.mjs
index a869f114..9e672912 100644
--- a/src/core/operations/DefangIPAddresses.mjs
+++ b/src/core/operations/DefangIPAddresses.mjs
@@ -20,11 +20,17 @@ class DefangIPAddresses extends Operation {
this.name = "Defang IP Addresses";
this.module = "Default";
- this.description = "Takes a IPv4 or IPv6 address and 'Defangs' it, meaning the IP becomes invalid, removing the risk of accidentally utilising it as an IP address.";
+ this.description = "Takes a IPv4 or IPv6 address and 'Defangs' it, meaning the IP becomes invalid, removing the risk of accidentally utilising it as an IP address.
When 'Last only' is enabled, only the final separator of each IP is replaced (e.g. 1.2.3.4 becomes 1.2.3[.]4), keeping the address visually closer to its original form while still rendering it invalid.";
this.infoURL = "https://isc.sans.edu/forums/diary/Defang+all+the+things/22744/";
this.inputType = "string";
this.outputType = "string";
- this.args = [];
+ this.args = [
+ {
+ name: "Last only",
+ type: "boolean",
+ value: false
+ }
+ ];
this.checks = [
{
pattern: "^\\s*(([0-9]{1,3}\\.){3}[0-9]{1,3}|([0-9a-f]{4}:){7}[0-9a-f]{4})\\s*$",
@@ -44,12 +50,14 @@ class DefangIPAddresses extends Operation {
* @returns {string}
*/
run(input, args) {
+ const [lastOnly] = args;
+
input = input.replace(IPV4_REGEX, x => {
- return x.replace(/\./g, "[.]");
+ return defangSeparators(x, ".", "[.]", lastOnly);
});
input = input.replace(IPV6_REGEX, x => {
- return x.replace(/:/g, "[:]");
+ return defangSeparators(x, ":", "[:]", lastOnly);
});
return input;
@@ -59,6 +67,26 @@ class DefangIPAddresses extends Operation {
export default DefangIPAddresses;
+/**
+ * Replaces a separator character with a defanged token.
+ * If lastOnly is true, only the final occurrence is replaced.
+ *
+ * @param {string} match
+ * @param {string} sep
+ * @param {string} token
+ * @param {boolean} lastOnly
+ * @returns {string}
+ */
+function defangSeparators(match, sep, token, lastOnly) {
+ if (!lastOnly) {
+ return match.split(sep).join(token);
+ }
+ const idx = match.lastIndexOf(sep);
+ if (idx === -1) return match;
+ return match.slice(0, idx) + token + match.slice(idx + sep.length);
+}
+
+
/**
* IPV4 regular expression
*/
diff --git a/tests/operations/tests/DefangIP.mjs b/tests/operations/tests/DefangIP.mjs
index 60005c54..8418f476 100644
--- a/tests/operations/tests/DefangIP.mjs
+++ b/tests/operations/tests/DefangIP.mjs
@@ -39,5 +39,45 @@ TestRegister.addTests([
args: [],
},
],
+ }, {
+ name: "Defang IP: Last only — IPV4",
+ input: "192.168.1.1",
+ expectedOutput: "192.168.1[.]1",
+ recipeConfig: [
+ {
+ op: "Defang IP Addresses",
+ args: [true],
+ },
+ ],
+ }, {
+ name: "Defang IP: Last only — IPV6",
+ input: "2001:0db8:85a3:0000:0000:8a2e:0370:7343",
+ expectedOutput: "2001:0db8:85a3:0000:0000:8a2e:0370[:]7343",
+ recipeConfig: [
+ {
+ op: "Defang IP Addresses",
+ args: [true],
+ },
+ ],
+ }, {
+ name: "Defang IP: Last only — IPV6 shorthand",
+ input: "2001:db8:3c4d:15::1a2f:1a2b",
+ expectedOutput: "2001:db8:3c4d:15::1a2f[:]1a2b",
+ recipeConfig: [
+ {
+ op: "Defang IP Addresses",
+ args: [true],
+ },
+ ],
+ }, {
+ name: "Defang IP: Last only — multiple IPs in text",
+ input: "From 10.0.0.1 to 192.168.1.254",
+ expectedOutput: "From 10.0.0[.]1 to 192.168.1[.]254",
+ recipeConfig: [
+ {
+ op: "Defang IP Addresses",
+ args: [true],
+ },
+ ],
},
]);