From 6036d41ae9339ce873bd02f747f7305f3c678e5e Mon Sep 17 00:00:00 2001 From: Alan Myers Date: Sun, 26 Jul 2026 17:21:41 -0400 Subject: [PATCH 1/7] Initial antibot.ts rewrite --- backend/utils/antibot.js | 86 ++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 87 insertions(+) create mode 100644 backend/utils/antibot.js diff --git a/backend/utils/antibot.js b/backend/utils/antibot.js new file mode 100644 index 00000000..6fb0c565 --- /dev/null +++ b/backend/utils/antibot.js @@ -0,0 +1,86 @@ +var { obfuscate } = require("javascript-obfuscator"); + +class Antibot { + clientChecks = []; //tbd + + checkSolves = []; + solves = []; + + hash = Math.floor(Math.random() * 999); + + verifiedDevices = false; + + constructor() { + for (let i = 0; i < 3; i++) { + this.solves.push(this.clientChecks[Math.floor(Math.random() * this.clientChecks.length)]); + } + + for (let i = 0; i < this.solves.length; i++) { + this.checkSolves.push(Math.floor(Math.random() * 999) + ""); + } + } + + verifyMessage(ws, data) { + if (data.kind == "devices") { + this.verifiedDevices = true; + } + + if (data.kind == "hi") { + if (ws.sdata && ws.sdata.user) { + var user = ws.sdata.user; + if (user.operator || user.superuser || user.staff) return false; + } + + if (!data.code || !this.verifiedDevices || !this.verifyCode(data.code)) { + return true; + } + } + } + + generateCode() { + let clientChecksCode = this.solves.map((z, i) => { + return `if(${z[0]} == ${JSON.stringify(z[1])}) parts.push("${this.checkSolves[i]}")`; + }); + + return obfuscate( + ` + let parts = []; + ${clientChecksCode.join(";\n")} + return ${this.hash}*parts.map(z => z.split("").map(z => z.charCodeAt(0)).reduce((a, b)=>a+b)).reduce((a, b) => a + b); + `, + { + compact: true, + controlFlowFlattening: true, + controlFlowFlatteningThreshold: 1, + numbersToExpressions: true, + simplify: true, + stringArrayShuffle: true, + splitStrings: true, + stringArrayThreshold: 0.52, + }, + ).getObfuscatedCode(); + } + + verifyCode(code) { + let solve = this.hash * + this.checkSolves.map((z) => + z.split("").map((z) => z.charCodeAt(0)).reduce((a, b) => a + b) + ).reduce((a, b) => a + b); + + if (solve !== code) { + console.log( + "Antibot failed! Code:", + code, + "Needed:", + solve, + "Hash:", + this.hash, + "Solves:", + this.solves, + ); + } + return solve == code; + } +} + +module.exports = Antibot; diff --git a/package.json b/package.json index f85f0fbc..d95f3e24 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "OurWorldOfText server", "dependencies": { "adm-zip": "^0.5.16", + "javascript-obfuscator": "^5.5.0", "nodemailer": "^7.0.10", "pg": "^8.13.1", "sqlite3": "^5.1.7", From fcb558e73cf5eb1c8ca787bae62de843032350aa Mon Sep 17 00:00:00 2001 From: Alan Myers Date: Sun, 26 Jul 2026 17:38:11 -0400 Subject: [PATCH 2/7] Add client checks to settings --- backend/utils/antibot.js | 12 ++++++++++-- settings_example.json | 6 +++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/backend/utils/antibot.js b/backend/utils/antibot.js index 6fb0c565..83d6a31c 100644 --- a/backend/utils/antibot.js +++ b/backend/utils/antibot.js @@ -1,8 +1,14 @@ var { obfuscate } = require("javascript-obfuscator"); +var fs = require("fs"); +var path = require("path"); -class Antibot { - clientChecks = []; //tbd +var settingsPath = path.resolve(process.cwd(), "nwotdata/settings.json"); +if (!fs.existsSync(settingsPath)) { // Do I even need this? + settingsPath = path.resolve(process.cwd(), "settings_example.json"); +} +var settings = JSON.parse(fs.readFileSync(settingsPath, "utf8")); +class Antibot { checkSolves = []; solves = []; @@ -11,6 +17,8 @@ class Antibot { verifiedDevices = false; constructor() { + this.clientChecks = settings.antibot.client_checks; + for (let i = 0; i < 3; i++) { this.solves.push(this.clientChecks[Math.floor(Math.random() * this.clientChecks.length)]); } diff --git a/settings_example.json b/settings_example.json index de769d78..a3ff651e 100644 --- a/settings_example.json +++ b/settings_example.json @@ -63,5 +63,9 @@ "display_email": "\"Our World of Text\" " }, - "activation_key_days_expire": 3 + "activation_key_days_expire": 3, + + "antibot": { + "client_checks": [] + } } \ No newline at end of file From 34db565c64b9a71dd58b3596a5f75fac6fd8d9b4 Mon Sep 17 00:00:00 2001 From: Alan Myers Date: Sun, 26 Jul 2026 18:01:56 -0400 Subject: [PATCH 3/7] Add antibot status log on server start and make config fully optional --- backend/utils/antibot.js | 12 +++++++++++- runserver.js | 3 +++ settings_example.json | 6 +----- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/backend/utils/antibot.js b/backend/utils/antibot.js index 83d6a31c..a35cbb0a 100644 --- a/backend/utils/antibot.js +++ b/backend/utils/antibot.js @@ -17,7 +17,13 @@ class Antibot { verifiedDevices = false; constructor() { - this.clientChecks = settings.antibot.client_checks; + this.clientChecks = (settings.antibot && settings.antibot.client_checks) || []; + + if (!this.clientChecks.length) { + this.enabled = false; + return; + } + this.enabled = true; for (let i = 0; i < 3; i++) { this.solves.push(this.clientChecks[Math.floor(Math.random() * this.clientChecks.length)]); @@ -29,6 +35,8 @@ class Antibot { } verifyMessage(ws, data) { + if (!this.enabled) return; + if (data.kind == "devices") { this.verifiedDevices = true; } @@ -46,6 +54,7 @@ class Antibot { } generateCode() { + if (!this.enabled) return null; let clientChecksCode = this.solves.map((z, i) => { return `if(${z[0]} == ${JSON.stringify(z[1])}) parts.push("${this.checkSolves[i]}")`; }); @@ -70,6 +79,7 @@ class Antibot { } verifyCode(code) { + if (!this.enabled) return true; let solve = this.hash * this.checkSolves.map((z) => z.split("").map((z) => z.charCodeAt(0)).reduce((a, b) => a + b) diff --git a/runserver.js b/runserver.js index 929c83d4..01edf25b 100644 --- a/runserver.js +++ b/runserver.js @@ -873,6 +873,9 @@ function setupHTTPServer() { async function initializeServer() { console.log("Starting server..."); + var antibotEnabled = settings.antibot && settings.antibot.client_checks && settings.antibot.client_checks.length; + console.log("Antibot", (antibotEnabled ? "enabled" : "disabled")); + if(accountSystem == "uvias") { setupUvias(); await uvias_init(); diff --git a/settings_example.json b/settings_example.json index a3ff651e..de769d78 100644 --- a/settings_example.json +++ b/settings_example.json @@ -63,9 +63,5 @@ "display_email": "\"Our World of Text\" " }, - "activation_key_days_expire": 3, - - "antibot": { - "client_checks": [] - } + "activation_key_days_expire": 3 } \ No newline at end of file From 753d393e29bcde1a8548f46a6400a5c7721f4b0f Mon Sep 17 00:00:00 2001 From: Alan Myers Date: Sun, 26 Jul 2026 18:18:26 -0400 Subject: [PATCH 4/7] Fail if botGlobals are detected --- backend/utils/antibot.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/backend/utils/antibot.js b/backend/utils/antibot.js index a35cbb0a..646b208c 100644 --- a/backend/utils/antibot.js +++ b/backend/utils/antibot.js @@ -18,8 +18,9 @@ class Antibot { constructor() { this.clientChecks = (settings.antibot && settings.antibot.client_checks) || []; + this.botGlobals = (settings.antibot && settings.antibot.bot_globals) || []; - if (!this.clientChecks.length) { + if (!this.clientChecks.length && !this.botGlobals.length) { this.enabled = false; return; } @@ -59,9 +60,14 @@ class Antibot { return `if(${z[0]} == ${JSON.stringify(z[1])}) parts.push("${this.checkSolves[i]}")`; }); + let botGlobalsCode = this.botGlobals.map((g) => { + return `if(typeof ${g} !== "undefined") return 0`; + }); + return obfuscate( ` let parts = []; + ${botGlobalsCode.join(";\n")} ${clientChecksCode.join(";\n")} return ${this.hash}*parts.map(z => z.split("").map(z => z.charCodeAt(0)).reduce((a, b)=>a+b)).reduce((a, b) => a + b); `, From 6409872f1367af5585ad433c2c703da30a115011 Mon Sep 17 00:00:00 2001 From: Alan Myers Date: Sun, 26 Jul 2026 18:52:47 -0400 Subject: [PATCH 5/7] Add antibot into ws handler and add bot detection globals --- runserver.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/runserver.js b/runserver.js index 01edf25b..b5c770fa 100644 --- a/runserver.js +++ b/runserver.js @@ -27,6 +27,7 @@ const utils = require("./backend/utils/utils.js"); const rate_limiter = require("./backend/utils/rate_limiter.js"); const ipaddress = require("./backend/framework/ipaddress.js"); const prompt = require("./backend/utils/prompt.js"); +const Antibot = require("./backend/utils/antibot.js"); const restrictions = require("./backend/utils/restrictions.js"); const frameUtils = require("./backend/framework/utils.js"); const serverUtil = require("./backend/framework/server.js"); @@ -873,9 +874,6 @@ function setupHTTPServer() { async function initializeServer() { console.log("Starting server..."); - var antibotEnabled = settings.antibot && settings.antibot.client_checks && settings.antibot.client_checks.length; - console.log("Antibot", (antibotEnabled ? "enabled" : "disabled")); - if(accountSystem == "uvias") { setupUvias(); await uvias_init(); @@ -903,6 +901,14 @@ async function initializeServer() { global_data.checkCSRF = httpServer.checkCSRF; global_data.createCSRF = httpServer.createCSRF; + var antibot = new Antibot(); + if(antibot.enabled) { + console.log("Antibot enabled (" + antibot.clientChecks.length + " checks, " + antibot.botGlobals.length + " bot globals)"); + } else { + console.log("Antibot disabled"); + } + global_data.antibot = antibot; + if(accountSystem == "local") { await loadEmail(); } @@ -2307,6 +2313,7 @@ async function manageWebsocketConnection(ws, req) { } return send_ws(JSON.stringify(res)); } + if(global_data.antibot.verifyMessage(ws, msg)) return; // Begin calling a websocket function for the necessary request if(!websockets.hasOwnProperty(kind)) { return; From bc497f59598385f50ad35c517f8cface51da03c9 Mon Sep 17 00:00:00 2001 From: Alan Myers Date: Sun, 26 Jul 2026 19:06:50 -0400 Subject: [PATCH 6/7] per-connection challenge --- backend/utils/antibot.js | 39 ++++++++++++++++++++++++++------------- runserver.js | 2 ++ 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/backend/utils/antibot.js b/backend/utils/antibot.js index 646b208c..59ad8644 100644 --- a/backend/utils/antibot.js +++ b/backend/utils/antibot.js @@ -14,8 +14,6 @@ class Antibot { hash = Math.floor(Math.random() * 999); - verifiedDevices = false; - constructor() { this.clientChecks = (settings.antibot && settings.antibot.client_checks) || []; this.botGlobals = (settings.antibot && settings.antibot.bot_globals) || []; @@ -36,22 +34,37 @@ class Antibot { } verifyMessage(ws, data) { - if (!this.enabled) return; + if (!this.enabled) return false; + if (!ws.sdata) return false; - if (data.kind == "devices") { - this.verifiedDevices = true; - } + if (ws.sdata.antibot_verified) return false; - if (data.kind == "hi") { - if (ws.sdata && ws.sdata.user) { - var user = ws.sdata.user; - if (user.operator || user.superuser || user.staff) return false; - } + if (ws.sdata.user && (ws.sdata.user.operator || ws.sdata.user.superuser || ws.sdata.user.staff)) { + ws.sdata.antibot_verified = true; + return false; + } - if (!data.code || !this.verifiedDevices || !this.verifyCode(data.code)) { - return true; + if (data.kind == "antibot_response") { + if (this.verifyCode(data.code)) { + ws.sdata.antibot_verified = true; + return false; } + return true; } + + return true; + } + + sendChallenge(ws) { + if (!this.enabled) return; + if (!ws.sdata) return; + var code = this.generateCode(); + try { + ws.send(JSON.stringify({ + kind: "antibot_challenge", + code: code + })); + } catch(e) {} } generateCode() { diff --git a/runserver.js b/runserver.js index b5c770fa..ccb5c18c 100644 --- a/runserver.js +++ b/runserver.js @@ -2248,6 +2248,8 @@ async function manageWebsocketConnection(ws, req) { initial_user_count })); + global_data.antibot.sendChallenge(ws); + if(client_cursor_pos[world.id]) { var world_cursors = client_cursor_pos[world.id]; for(var csr_channel in world_cursors) { From 3c55ce4ca8b11fcd7c58d86981aa3eb535d9c700 Mon Sep 17 00:00:00 2001 From: Alan Myers Date: Tue, 28 Jul 2026 13:52:53 -0400 Subject: [PATCH 7/7] Fix nwotdata path --- backend/utils/antibot.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/utils/antibot.js b/backend/utils/antibot.js index 59ad8644..a31fec7c 100644 --- a/backend/utils/antibot.js +++ b/backend/utils/antibot.js @@ -2,7 +2,7 @@ var { obfuscate } = require("javascript-obfuscator"); var fs = require("fs"); var path = require("path"); -var settingsPath = path.resolve(process.cwd(), "nwotdata/settings.json"); +var settingsPath = path.resolve(process.cwd(), "../nwotdata/settings.json"); if (!fs.existsSync(settingsPath)) { // Do I even need this? settingsPath = path.resolve(process.cwd(), "settings_example.json"); }