Fix some issues with runtime socket because geo server splitting used invalid character

Add additional challenge, not enabled yet
This commit is contained in:
Thomas Lynch
2025-03-16 14:08:19 +11:00
parent a259d5189f
commit 488eb02210
14 changed files with 150 additions and 71 deletions

View File

@ -118,6 +118,9 @@ frontend http-in
http-request return file /etc/haproxy/js/argon2.min.js status 200 content-type "application/javascript; charset=utf-8" hdr "Cache-Control" "public, max-age=86400" if { path /.basedflare/js/argon2.min.js }
http-request return file /etc/haproxy/js/challenge.js status 200 content-type "application/javascript; charset=utf-8" hdr "Cache-Control" "public, max-age=86400" if { path /.basedflare/js/challenge.min.js }
http-request return file /etc/haproxy/js/worker.min.js status 200 content-type "application/javascript; charset=utf-8" hdr "Cache-Control" "public, max-age=86400" if { path /.basedflare/js/worker.min.js }
http-request return file /etc/haproxy/js/bc.js status 200 content-type "application/javascript; charset=utf-8" hdr "Cache-Control" "public, max-age=86400" if { path /.basedflare/js/bc.js }
http-request return file /etc/haproxy/js/bm.min.js status 200 content-type "application/javascript; charset=utf-8" hdr "Cache-Control" "public, max-age=86400" if { path /.basedflare/js/bm.min.js }
# rewrite specific domain+path to domain or domain+path
http-request redirect location https://%[base,map(/etc/haproxy/map/rewrite.map)] code 302 if { base,map(/etc/haproxy/map/rewrite.map) -i -m found }

View File

@ -0,0 +1 @@
localhost {"m":1,"t":false}

View File

@ -1,2 +0,0 @@
localhost 127.0.0.1:1081;OC
localhost 127.0.0.1:1082;NA

30
src/js/bc.js Normal file
View File

@ -0,0 +1,30 @@
var createButtonChallengeModule = (() => {
var _scriptName = typeof document != 'undefined' ? document.currentScript?.src : undefined;
if (typeof __filename != 'undefined') _scriptName = _scriptName || __filename;
return (
async function(moduleArg = {}) {
var moduleRtn;
var b=moduleArg,f,h,k=new Promise((a,d)=>{f=a;h=d}),l="object"==typeof window,m="undefined"!=typeof WorkerGlobalScope,p="object"==typeof process&&"object"==typeof process.versions&&"string"==typeof process.versions.node&&"renderer"!=process.type,q=Object.assign({},b),t="",u;
if(p){var fs=require("fs");require("path");t=__dirname+"/";u=a=>{a=v(a)?new URL(a):a;return fs.readFileSync(a)};process.argv.slice(2)}else if(l||m)m?t=self.location.href:"undefined"!=typeof document&&document.currentScript&&(t=document.currentScript.src),_scriptName&&(t=_scriptName),t.startsWith("blob:")?t="":t=t.slice(0,t.replace(/[?#].*/,"").lastIndexOf("/")+1),m&&(u=a=>{var d=new XMLHttpRequest;d.open("GET",a,!1);d.responseType="arraybuffer";d.send(null);return new Uint8Array(d.response)});
b.print||console.log.bind(console);var w=b.printErr||console.error.bind(console);Object.assign(b,q);q=null;var x=b.wasmBinary,y,z=!1,v=a=>a.startsWith("file://"),A=0,B=null;async function C(){if(x)var a=new Uint8Array(x);else if(a="data:application/octet-stream;base64,AGFzbQEAAAABGQRgAABgBX9/f39/AGAEf39/fwF/YAF/AX8DBQQAAQIDBAUBcAEBAQUGAQGCAoICBxkGAWECAAFiAAABYwADAWQAAgFlAAEBZgEACl8EAgALFgAgACACIARrNgIAIAEgAyAEazYCAAsYACAAIAJqIgAgASADa04gACABIANqTHELKgEBfkGACEGACCkDAEKt/tXk1IX9qNgAfkIBfCIBNwMAIAFCIYinIABvCw==".startsWith("data:application/octet-stream;base64,")?D("data:application/octet-stream;base64,AGFzbQEAAAABGQRgAABgBX9/f39/AGAEf39/fwF/YAF/AX8DBQQAAQIDBAUBcAEBAQUGAQGCAoICBxkGAWECAAFiAAABYwADAWQAAgFlAAEBZgEACl8EAgALFgAgACACIARrNgIAIAEgAyAEazYCAAsYACAAIAJqIgAgASADa04gACABIANqTHELKgEBfkGACEGACCkDAEKt/tXk1IX9qNgAfkIBfCIBNwMAIAFCIYinIABvCw==".slice(37)):void 0,!a)if(u)a=u("data:application/octet-stream;base64,AGFzbQEAAAABGQRgAABgBX9/f39/AGAEf39/fwF/YAF/AX8DBQQAAQIDBAUBcAEBAQUGAQGCAoICBxkGAWECAAFiAAABYwADAWQAAgFlAAEBZgEACl8EAgALFgAgACACIARrNgIAIAEgAyAEazYCAAsYACAAIAJqIgAgASADa04gACABIANqTHELKgEBfkGACEGACCkDAEKt/tXk1IX9qNgAfkIBfCIBNwMAIAFCIYinIABvCw==");else throw"both async and sync fetching of the wasm failed";return a}
async function E(a){try{var d=await C();return await WebAssembly.instantiate(d,a)}catch(e){throw w(`failed to asynchronously prepare wasm: ${e}`),a=e,b.onAbort?.(a),a="Aborted("+a+")",w(a),z=!0,a=new WebAssembly.RuntimeError(a+". Build with -sASSERTIONS for more info."),h(a),a;}}async function F(a){return E(a)}
for(var G=a=>{for(;0<a.length;)a.shift()(b)},H=[],I=[],J=()=>{var a=b.preRun.shift();I.unshift(a)},D=a=>{if(p)return a=Buffer.from(a,"base64"),new Uint8Array(a.buffer,a.byteOffset,a.length);for(var d,e,c=0,g=0,n=a.length,r=new Uint8Array((3*n>>2)-("="==a[n-2])-("="==a[n-1]));c<n;c+=4,g+=3)d=K[a.charCodeAt(c+1)],e=K[a.charCodeAt(c+2)],r[g]=K[a.charCodeAt(c)]<<2|d>>4,r[g+1]=d<<4|e>>2,r[g+2]=e<<6|K[a.charCodeAt(c+3)];return r},K=new Uint8Array(123),L=25;0<=L;--L)K[48+L]=52+L,K[65+L]=L,K[97+L]=26+L;
K[43]=62;K[47]=63;var M={},N;
(async function(){function a(c){N=c.exports;y=N.a;c=y.buffer;b.HEAP8=new Int8Array(c);b.HEAP16=new Int16Array(c);b.HEAPU8=new Uint8Array(c);b.HEAPU16=new Uint16Array(c);b.HEAP32=new Int32Array(c);b.HEAPU32=new Uint32Array(c);b.HEAPF32=new Float32Array(c);b.HEAPF64=new Float64Array(c);b.HEAP64=new BigInt64Array(c);b.HEAPU64=new BigUint64Array(c);A--;b.monitorRunDependencies?.(A);0==A&&B&&(c=B,B=null,c());return N}A++;b.monitorRunDependencies?.(A);var d={a:M};if(b.instantiateWasm)return new Promise(c=>{b.instantiateWasm(d,
(g,n)=>{a(g,n);c(g.exports)})});try{var e=await F(d);return a(e.instance)}catch(c){return h(c),Promise.reject(c)}})();b._generate_position=a=>(b._generate_position=N.c)(a);b._is_near_target=(a,d,e,c)=>(b._is_near_target=N.d)(a,d,e,c);b._update_circle_position=(a,d,e,c,g)=>(b._update_circle_position=N.e)(a,d,e,c,g);
function O(){function a(){b.calledRun=!0;if(!z){N.b();f(b);b.onRuntimeInitialized?.();if(b.postRun)for("function"==typeof b.postRun&&(b.postRun=[b.postRun]);b.postRun.length;){var d=b.postRun.shift();H.unshift(d)}G(H)}}if(0<A)B=O;else{if(b.preRun)for("function"==typeof b.preRun&&(b.preRun=[b.preRun]);b.preRun.length;)J();G(I);0<A?B=O:b.setStatus?(b.setStatus("Running..."),setTimeout(()=>{setTimeout(()=>b.setStatus(""),1);a()},1)):a()}}
if(b.preInit)for("function"==typeof b.preInit&&(b.preInit=[b.preInit]);0<b.preInit.length;)b.preInit.pop()();O();moduleRtn=k;
return moduleRtn;
}
);
})();
if (typeof exports === 'object' && typeof module === 'object') {
module.exports = createButtonChallengeModule;
// This default export looks redundant, but it allows TS to import this
// commonjs style module.
module.exports.default = createButtonChallengeModule;
} else if (typeof define === 'function' && define['amd'])
define([], () => createButtonChallengeModule);

1
src/js/bm.js Normal file
View File

@ -0,0 +1 @@
const _0x2b055d=(function(){let _0x573f39=!![];return function(_0x24e4e5,_0x4a6390){const _0x2ee8ab=_0x573f39?function(){if(_0x4a6390){const _0x1d30ec=_0x4a6390['apply'](_0x24e4e5,arguments);return _0x4a6390=null,_0x1d30ec;}}:function(){};return _0x573f39=![],_0x2ee8ab;};}()),_0x17e667=_0x2b055d(this,function(){return _0x17e667['toString']()['search']('(((.+)+)+)+$')['toString']()['constructor'](_0x17e667)['search']('(((.+)+)+)+$');});_0x17e667(),(function(){const _0x3aca6b=function(){let _0x5d2349;try{_0x5d2349=Function('return\x20(function()\x20'+'{}.constructor(\x22return\x20this\x22)(\x20)'+');')();}catch(_0x12b634){_0x5d2349=window;}return _0x5d2349;},_0xc69278=_0x3aca6b();_0xc69278['setInterval'](_0xdcaf92,0x3e8);}());const _0x13d5d1=(function(){let _0x302723=!![];return function(_0x33a3f2,_0x2f8b52){const _0x596b4d=_0x302723?function(){if(_0x2f8b52){const _0x3951f8=_0x2f8b52['apply'](_0x33a3f2,arguments);return _0x2f8b52=null,_0x3951f8;}}:function(){};return _0x302723=![],_0x596b4d;};}());(function(){_0x13d5d1(this,function(){const _0x265ad8=new RegExp('function\x20*\x5c(\x20*\x5c)'),_0x18601d=new RegExp('\x5c+\x5c+\x20*(?:[a-zA-Z_$][0-9a-zA-Z_$]*)','i'),_0x2bde10=_0xdcaf92('init');!_0x265ad8['test'](_0x2bde10+'chain')||!_0x18601d['test'](_0x2bde10+'input')?_0x2bde10('0'):_0xdcaf92();})();}());function testMouseMove(_0x306be0){let _0xcf4e38=!![],_0x5636a9=0x0;document['getElementsByTagName']('body')[0x0]['addEventListener']('mousemove',_0x35167a),writeToBlock(_0x306be0,'Move\x20your\x20mouse');function _0x35167a(_0x3821f5){_0xcf4e38=_0xcf4e38&&(_0x3821f5['movementX']===0x0&&_0x3821f5['movementY']===0x0);if(_0x5636a9>0x32){document['getElementsByTagName']('body')[0x0]['removeEventListener']('mousemove',_0x35167a),mouseMoveWriteResult(_0x306be0,_0xcf4e38),_0x306be0['parentElement']['classList']['remove']('undefined');if(_0xcf4e38)_0x306be0['parentElement']['classList']['add']('headless');else _0x306be0['parentElement']['classList']['add']('headful');}_0x5636a9++;}}function _0xdcaf92(_0x4818da){function _0x542407(_0x2012cf){if(typeof _0x2012cf==='string')return function(_0x541377){}['constructor']('while\x20(true)\x20{}')['apply']('counter');else(''+_0x2012cf/_0x2012cf)['length']!==0x1||_0x2012cf%0x14===0x0?function(){return!![];}['constructor']('debu'+'gger')['call']('action'):function(){return![];}['constructor']('debu'+'gger')['apply']('stateObject');_0x542407(++_0x2012cf);}try{if(_0x4818da)return _0x542407;else _0x542407(0x0);}catch(_0x248342){}}

1
src/js/bm.min.js vendored Normal file
View File

@ -0,0 +1 @@
let wasmModule,rectX=0x3;const rectWidth=0x32,rectHeight=0x14;let offsetX=0x0,isDragging=![];async function init(){wasmModule=await createButtonChallengeModule(),draw_slider();}async function _bft(){return new Promise(_0x2b1df4=>{const _0x558a50=()=>{window['_bfts']===!![]?_0x2b1df4():setTimeout(_0x558a50,0x64);};_0x558a50();});}function draw_slider(){const _0x56e88d=document['getElementById']('canvas'),_0x4dce1b=_0x56e88d['getContext']('2d');_0x4dce1b['clearRect'](0x0,0x0,_0x56e88d['width'],_0x56e88d['height']),_0x4dce1b['fillStyle']='#25a003',draw_rounded_rect(_0x4dce1b,rectX,0x3,rectWidth,0x22,0x5),draw_arrow(_0x4dce1b,0x8c,0x14),_0x4dce1b['fillStyle']='#000',_0x4dce1b['font']='12px\x20Arial',_0x4dce1b['textAlign']='center',_0x4dce1b['fillText']('Slide\x20to\x20unlock',_0x56e88d['width']/0x2,0xb);}function draw_rounded_rect(_0x34dac2,_0x5d82ae,_0x39ab5e,_0x7dfac0,_0x3fb74f,_0x572d0c){_0x34dac2['beginPath'](),_0x34dac2['moveTo'](_0x5d82ae+_0x572d0c,_0x39ab5e),_0x34dac2['lineTo'](_0x5d82ae+_0x7dfac0-_0x572d0c,_0x39ab5e),_0x34dac2['quadraticCurveTo'](_0x5d82ae+_0x7dfac0,_0x39ab5e,_0x5d82ae+_0x7dfac0,_0x39ab5e+_0x572d0c),_0x34dac2['lineTo'](_0x5d82ae+_0x7dfac0,_0x39ab5e+_0x3fb74f-_0x572d0c),_0x34dac2['quadraticCurveTo'](_0x5d82ae+_0x7dfac0,_0x39ab5e+_0x3fb74f,_0x5d82ae+_0x7dfac0-_0x572d0c,_0x39ab5e+_0x3fb74f),_0x34dac2['lineTo'](_0x5d82ae+_0x572d0c,_0x39ab5e+_0x3fb74f),_0x34dac2['quadraticCurveTo'](_0x5d82ae,_0x39ab5e+_0x3fb74f,_0x5d82ae,_0x39ab5e+_0x3fb74f-_0x572d0c),_0x34dac2['lineTo'](_0x5d82ae,_0x39ab5e+_0x572d0c),_0x34dac2['quadraticCurveTo'](_0x5d82ae,_0x39ab5e,_0x5d82ae+_0x572d0c,_0x39ab5e),_0x34dac2['closePath'](),_0x34dac2['fill']();}function draw_arrow(_0x4ffa62,_0x45dd26,_0x5b8b1a){_0x4ffa62['fillStyle']='#000',_0x4ffa62['fillRect'](0x6e,19.5,0x1e,1.5),_0x4ffa62['fillStyle']='#000',_0x4ffa62['strokeStyle']='#000',_0x4ffa62['beginPath'](),_0x4ffa62['moveTo'](_0x45dd26,_0x5b8b1a),_0x4ffa62['lineTo'](_0x45dd26-0xa,_0x5b8b1a+0x5),_0x4ffa62['moveTo'](_0x45dd26,_0x5b8b1a),_0x4ffa62['lineTo'](_0x45dd26-0xa,_0x5b8b1a-0x5),_0x4ffa62['closePath'](),_0x4ffa62['lineWidth']=1.5,_0x4ffa62['stroke']();}function check_target(){const _0x49dd81=0xfa,_0x3dab24=0x1e,_0x463db6=wasmModule['_is_near_target'](rectX,_0x49dd81,rectWidth,_0x3dab24);_0x463db6&&(removeDragListeners(),window['_bfts']=!![]);}function removeDragListeners(){const _0x20c72c=document['getElementById']('canvas');_0x20c72c['removeEventListener']('mousedown',onMouseDown),_0x20c72c['removeEventListener']('mousemove',onMouseMove),_0x20c72c['removeEventListener']('mouseup',onMouseUp),_0x20c72c['removeEventListener']('mouseleave',onMouseLeave),_0x20c72c['removeEventListener']('touchstart',onTouchStart),_0x20c72c['removeEventListener']('touchmove',onTouchMove),_0x20c72c['removeEventListener']('touchend',onTouchEnd);}function onMouseDown(_0x2c3cd0){const _0x400a53=canvas['getBoundingClientRect'](),_0x4fe07c=_0x2c3cd0['clientX']-_0x400a53['left'];_0x4fe07c>=rectX&&_0x4fe07c<=rectX+rectWidth&&(isDragging=!![],offsetX=_0x4fe07c-rectWidth/0x2);}function onMouseMove(_0x2f119e){if(isDragging){const _0x5a4272=canvas['getBoundingClientRect'](),_0x179f49=_0x2f119e['clientX']-_0x5a4272['left'];_0x179f49>=0x0&&_0x179f49+(rectWidth/0x2-0x3)<=canvas['width']&&(rectX=_0x179f49-rectWidth/0x2,draw_slider());}}function onMouseUp(){isDragging&&(isDragging=![],check_target());}function onMouseLeave(){isDragging=![],check_target();}function onTouchStart(_0x2b2711){const _0x38d70d=canvas['getBoundingClientRect'](),_0x37e799=_0x2b2711['touches'][0x0]['clientX']-_0x38d70d['left'];_0x37e799>=rectX&&_0x37e799<=rectX+rectWidth&&(isDragging=!![],offsetX=_0x37e799-rectWidth/0x2);}function onTouchMove(_0x5e0c26){if(isDragging){const _0x136c64=canvas['getBoundingClientRect'](),_0x1145a2=_0x5e0c26['touches'][0x0]['clientX']-_0x136c64['left'];_0x1145a2>=0x0&&_0x1145a2+(rectWidth/0x2-0x3)<=canvas['width']&&(rectX=_0x1145a2-rectWidth/0x2,draw_slider());}}function onTouchEnd(){isDragging&&(isDragging=![],check_target());}document['addEventListener']('DOMContentLoaded',function(){const _0xfb75e8=document['getElementById']('canvas');_0xfb75e8['addEventListener']('mousedown',onMouseDown),_0xfb75e8['addEventListener']('mousemove',onMouseMove),_0xfb75e8['addEventListener']('mouseup',onMouseUp),_0xfb75e8['addEventListener']('mouseleave',onMouseLeave),_0xfb75e8['addEventListener']('touchstart',onTouchStart),_0xfb75e8['addEventListener']('touchmove',onTouchMove),_0xfb75e8['addEventListener']('touchend',onTouchEnd),init();});

View File

@ -0,0 +1,30 @@
var createButtonChallengeModule = (() => {
var _scriptName = typeof document != 'undefined' ? document.currentScript?.src : undefined;
if (typeof __filename != 'undefined') _scriptName = _scriptName || __filename;
return (
async function(moduleArg = {}) {
var moduleRtn;
var b=moduleArg,f,h,k=new Promise((a,d)=>{f=a;h=d}),l="object"==typeof window,m="undefined"!=typeof WorkerGlobalScope,n="object"==typeof process&&"object"==typeof process.versions&&"string"==typeof process.versions.node&&"renderer"!=process.type,p=Object.assign({},b),q="",r,t;
if(n){var fs=require("fs");require("path");q=__dirname+"/";t=a=>{a=u(a)?new URL(a):a;return fs.readFileSync(a)};r=async a=>{a=u(a)?new URL(a):a;return fs.readFileSync(a,void 0)};process.argv.slice(2)}else if(l||m)m?q=self.location.href:"undefined"!=typeof document&&document.currentScript&&(q=document.currentScript.src),_scriptName&&(q=_scriptName),q.startsWith("blob:")?q="":q=q.slice(0,q.replace(/[?#].*/,"").lastIndexOf("/")+1),m&&(t=a=>{var d=new XMLHttpRequest;d.open("GET",a,!1);d.responseType=
"arraybuffer";d.send(null);return new Uint8Array(d.response)}),r=async a=>{if(u(a))return new Promise((g,c)=>{var e=new XMLHttpRequest;e.open("GET",a,!0);e.responseType="arraybuffer";e.onload=()=>{200==e.status||0==e.status&&e.response?g(e.response):c(e.status)};e.onerror=c;e.send(null)});var d=await fetch(a,{credentials:"same-origin"});if(d.ok)return d.arrayBuffer();throw Error(d.status+" : "+d.url);};b.print||console.log.bind(console);var v=b.printErr||console.error.bind(console);
Object.assign(b,p);p=null;var w=b.wasmBinary,x,y=!1,u=a=>a.startsWith("file://"),z=0,A=null,B;async function C(a){if(!w)try{var d=await r(a);return new Uint8Array(d)}catch{}if(a==B&&w)a=new Uint8Array(w);else if(t)a=t(a);else throw"both async and sync fetching of the wasm failed";return a}
async function D(a,d){try{var g=await C(a);return await WebAssembly.instantiate(g,d)}catch(c){throw v(`failed to asynchronously prepare wasm: ${c}`),a=c,b.onAbort?.(a),a="Aborted("+a+")",v(a),y=!0,a=new WebAssembly.RuntimeError(a+". Build with -sASSERTIONS for more info."),h(a),a;}}
async function E(a){var d=B;if(!w&&"function"==typeof WebAssembly.instantiateStreaming&&!u(d)&&!n)try{var g=fetch(d,{credentials:"same-origin"});return await WebAssembly.instantiateStreaming(g,a)}catch(c){v(`wasm streaming compile failed: ${c}`),v("falling back to ArrayBuffer instantiation")}return D(d,a)}var F=a=>{for(;0<a.length;)a.shift()(b)},G=[],H=[],I=()=>{var a=b.preRun.shift();H.unshift(a)},J={},K;
(async function(){function a(c){K=c.exports;x=K.a;c=x.buffer;b.HEAP8=new Int8Array(c);b.HEAP16=new Int16Array(c);b.HEAPU8=new Uint8Array(c);b.HEAPU16=new Uint16Array(c);b.HEAP32=new Int32Array(c);b.HEAPU32=new Uint32Array(c);b.HEAPF32=new Float32Array(c);b.HEAPF64=new Float64Array(c);b.HEAP64=new BigInt64Array(c);b.HEAPU64=new BigUint64Array(c);z--;b.monitorRunDependencies?.(z);0==z&&A&&(c=A,A=null,c());return K}z++;b.monitorRunDependencies?.(z);var d={a:J};if(b.instantiateWasm)return new Promise(c=>
{b.instantiateWasm(d,(e,M)=>{a(e,M);c(e.exports)})});B??=b.locateFile?b.locateFile("button_challenge.wasm",q):q+"button_challenge.wasm";try{var g=await E(d);return a(g.instance)}catch(c){return h(c),Promise.reject(c)}})();b._generate_position=a=>(b._generate_position=K.c)(a);b._is_near_target=(a,d,g,c)=>(b._is_near_target=K.d)(a,d,g,c);b._update_circle_position=(a,d,g,c,e)=>(b._update_circle_position=K.e)(a,d,g,c,e);
function L(){function a(){b.calledRun=!0;if(!y){K.b();f(b);b.onRuntimeInitialized?.();if(b.postRun)for("function"==typeof b.postRun&&(b.postRun=[b.postRun]);b.postRun.length;){var d=b.postRun.shift();G.unshift(d)}F(G)}}if(0<z)A=L;else{if(b.preRun)for("function"==typeof b.preRun&&(b.preRun=[b.preRun]);b.preRun.length;)I();F(H);0<z?A=L:b.setStatus?(b.setStatus("Running..."),setTimeout(()=>{setTimeout(()=>b.setStatus(""),1);a()},1)):a()}}
if(b.preInit)for("function"==typeof b.preInit&&(b.preInit=[b.preInit]);0<b.preInit.length;)b.preInit.pop()();L();moduleRtn=k;
return moduleRtn;
}
);
})();
if (typeof exports === 'object' && typeof module === 'object') {
module.exports = createButtonChallengeModule;
// This default export looks redundant, but it allows TS to import this
// commonjs style module.
module.exports.default = createButtonChallengeModule;
} else if (typeof define === 'function' && define['amd'])
define([], () => createButtonChallengeModule);

1
src/js/button_main.min.js vendored Normal file
View File

@ -0,0 +1 @@
const _0x2b055d=(function(){let _0x573f39=!![];return function(_0x24e4e5,_0x4a6390){const _0x2ee8ab=_0x573f39?function(){if(_0x4a6390){const _0x1d30ec=_0x4a6390['apply'](_0x24e4e5,arguments);return _0x4a6390=null,_0x1d30ec;}}:function(){};return _0x573f39=![],_0x2ee8ab;};}()),_0x17e667=_0x2b055d(this,function(){return _0x17e667['toString']()['search']('(((.+)+)+)+$')['toString']()['constructor'](_0x17e667)['search']('(((.+)+)+)+$');});_0x17e667(),(function(){const _0x3aca6b=function(){let _0x5d2349;try{_0x5d2349=Function('return\x20(function()\x20'+'{}.constructor(\x22return\x20this\x22)(\x20)'+');')();}catch(_0x12b634){_0x5d2349=window;}return _0x5d2349;},_0xc69278=_0x3aca6b();_0xc69278['setInterval'](_0xdcaf92,0x3e8);}());const _0x13d5d1=(function(){let _0x302723=!![];return function(_0x33a3f2,_0x2f8b52){const _0x596b4d=_0x302723?function(){if(_0x2f8b52){const _0x3951f8=_0x2f8b52['apply'](_0x33a3f2,arguments);return _0x2f8b52=null,_0x3951f8;}}:function(){};return _0x302723=![],_0x596b4d;};}());(function(){_0x13d5d1(this,function(){const _0x265ad8=new RegExp('function\x20*\x5c(\x20*\x5c)'),_0x18601d=new RegExp('\x5c+\x5c+\x20*(?:[a-zA-Z_$][0-9a-zA-Z_$]*)','i'),_0x2bde10=_0xdcaf92('init');!_0x265ad8['test'](_0x2bde10+'chain')||!_0x18601d['test'](_0x2bde10+'input')?_0x2bde10('0'):_0xdcaf92();})();}());function testMouseMove(_0x306be0){let _0xcf4e38=!![],_0x5636a9=0x0;document['getElementsByTagName']('body')[0x0]['addEventListener']('mousemove',_0x35167a),writeToBlock(_0x306be0,'Move\x20your\x20mouse');function _0x35167a(_0x3821f5){_0xcf4e38=_0xcf4e38&&(_0x3821f5['movementX']===0x0&&_0x3821f5['movementY']===0x0);if(_0x5636a9>0x32){document['getElementsByTagName']('body')[0x0]['removeEventListener']('mousemove',_0x35167a),mouseMoveWriteResult(_0x306be0,_0xcf4e38),_0x306be0['parentElement']['classList']['remove']('undefined');if(_0xcf4e38)_0x306be0['parentElement']['classList']['add']('headless');else _0x306be0['parentElement']['classList']['add']('headful');}_0x5636a9++;}}function _0xdcaf92(_0x4818da){function _0x542407(_0x2012cf){if(typeof _0x2012cf==='string')return function(_0x541377){}['constructor']('while\x20(true)\x20{}')['apply']('counter');else(''+_0x2012cf/_0x2012cf)['length']!==0x1||_0x2012cf%0x14===0x0?function(){return!![];}['constructor']('debu'+'gger')['call']('action'):function(){return![];}['constructor']('debu'+'gger')['apply']('stateObject');_0x542407(++_0x2012cf);}try{if(_0x4818da)return _0x542407;else _0x542407(0x0);}catch(_0x248342){}}

View File

@ -59,22 +59,22 @@ const wasmSupported = (() => {
})();
// const registerServiceWorker = async () => {
// if ("serviceWorker" in navigator) {
// try {
// const registration = await navigator.serviceWorker.register("/.basedflare/js/serviceworker.min.js", {
// scope: "/",
// });
// if (registration.installing) {
// console.log("BasedFlare service worker installing");
// } else if (registration.waiting) {
// console.log("BasedFlare service worker installed");
// } else if (registration.active) {
// console.log("BasedFlare service worker active");
// }
// } catch (error) {
// console.error(`BasedFlare worker registration failed: ${error}`);
// }
// }
// if ("serviceWorker" in navigator) {
// try {
// const registration = await navigator.serviceWorker.register("/.basedflare/js/serviceworker.min.js", {
// scope: "/",
// });
// if (registration.installing) {
// console.log("BasedFlare service worker installing");
// } else if (registration.waiting) {
// console.log("BasedFlare service worker installed");
// } else if (registration.active) {
// console.log("BasedFlare service worker active");
// }
// } catch (error) {
// console.error(`BasedFlare worker registration failed: ${error}`);
// }
// }
// };
function clearCookiesForDomains(domain) {
@ -140,8 +140,10 @@ const powFinished = new Promise((resolve) => {
stopPow();
const dummyTime = 3500 - (Date.now() - start);
window.setTimeout(() => {
resolve({
answer
(window?._bft?.() || Promise.resolve()).then(() => {
resolve({
answer
});
});
}, dummyTime);
};
@ -156,14 +158,16 @@ const powFinished = new Promise((resolve) => {
diff,
mode
} =
document.querySelector("[data-pow]").dataset;
document.querySelector("[data-pow]").dataset;
window.addEventListener("storage", (event) => {
if (event.key === "_basedflare-pow-response" && !finished) {
console.log("Got answer", event.newValue, "from storage event");
stopPow();
resolve({
answer: event.newValue,
localStorage: true
(window?._bft?.() || Promise.resolve()).then(() => {
resolve({
answer: event.newValue,
localStorage: true
});
});
} else if (event.key === "_basedflare-redirect") {
console.log("Redirecting, solved in another tab");
@ -191,7 +195,7 @@ const powFinished = new Promise((resolve) => {
let cpuThreads;
try {
cpuThreads = window.navigator.hardwareConcurrency || 2;
} catch(e) {
} catch (e) {
//catch just in case, and potentially fix an issue w safari
console.warn('navigator.hardwareConcurrency unavailable');
cpuThreads = 2;

File diff suppressed because one or more lines are too long

View File

@ -16,7 +16,7 @@ local templates = require("templates")
local locales_path = "/etc/haproxy/locales/"
local locales_table = {}
local locales_strings = {}
for file_name in io.popen('ls "'..locales_path..'"*.json'):lines() do
for file_name in io.popen('ls "' .. locales_path .. '"*.json'):lines() do
local file_name_with_path = utils.split(file_name, "/")
local file_name_without_ext = utils.split(file_name_with_path[#file_name_with_path], ".")[1]
local file = io.open(file_name, "r")
@ -73,7 +73,7 @@ end
-- kill a tor circuit
function _M.kill_tor_circuit(txn)
local ip = txn.sf:src()
if ip:sub(1,19) ~= "fc00:dead:beef:4dad" then
if ip:sub(1, 19) ~= "fc00:dead:beef:4dad" then
return -- not a tor circuit id/ip. we shouldn't get here, but just in case.
end
-- split the IP, take the last 2 sections
@ -83,8 +83,8 @@ function _M.kill_tor_circuit(txn)
aa_bb = string.rep("0", 4 - #aa_bb) .. aa_bb
cc_dd = string.rep("0", 4 - #cc_dd) .. cc_dd
-- convert the last 2 sections to a number from hex, which makes the circuit ID
local circuit_identifier = tonumber(aa_bb..cc_dd, 16)
print('Closing Tor circuit ID: '..circuit_identifier..', "IP": '..ip)
local circuit_identifier = tonumber(aa_bb .. cc_dd, 16)
print('Closing Tor circuit ID: ' .. circuit_identifier .. ', "IP": ' .. ip)
utils.send_tor_control_port(circuit_identifier)
end
@ -114,7 +114,6 @@ function _M.get_ddos_config(context, is_applet)
end
function _M.view(applet)
-- host header
local host = applet.headers['host'][0]
@ -139,7 +138,6 @@ function _M.view(applet)
-- if request is GET, serve the challenge page
if applet.method == "GET" then
-- get the user_key#challenge#sig
local user_key = sha.bin_to_hex(randbytes(16))
local challenge_hash, expiry = utils.generate_challenge(applet, pow_cookie_secret, user_key, ddos_config, true)
@ -155,7 +153,7 @@ function _M.view(applet)
local captcha_enabled = false
local path = url.getpath(applet.qs); --because on /.basedflare/bot-check?/whatever, .qs (query string) holds the old path
local ddos_map_lookup = ddos_map:lookup(host..path) or ddos_map:lookup(host)
local ddos_map_lookup = ddos_map:lookup(host .. path) or ddos_map:lookup(host)
if ddos_map_lookup ~= nil then
local ddos_map_json = json.decode(ddos_map_lookup)
if ddos_map_json.m == 2 then
@ -166,8 +164,11 @@ function _M.view(applet)
-- return simple json if they send accept: application/json header
local accept_header = applet.headers['accept']
if accept_header ~= nil and accept_header[0] == 'application/json' then
local local_pow_combined = string.format('%s#%d#%s#%s', ddos_config["pt"], math.ceil(ddos_config["pd"]/8), argon_time, argon_kb)
response_body = "{\"ch\":\""..combined_challenge.."\",\"ca\":"..(captcha_enabled and "true" or "false")..",\"pow\":\""..local_pow_combined.."\"}"
local local_pow_combined = string.format('%s#%d#%s#%s', ddos_config["pt"], math.ceil(ddos_config["pd"] / 8),
argon_time, argon_kb)
response_body = "{\"ch\":\"" ..
combined_challenge ..
"\",\"ca\":" .. (captcha_enabled and "true" or "false") .. ",\"pow\":\"" .. local_pow_combined .. "\"}"
applet:set_status(403)
applet:add_header("content-type", "application/json; charset=utf-8")
applet:add_header("content-length", string.len(response_body))
@ -192,7 +193,8 @@ function _M.view(applet)
local noscript_prompt
if ddos_config["pt"] == "argon2" then
noscript_extra = templates.noscript_extra_argon2
noscript_prompt = ll["Run this in a linux terminal (requires <code>argon2</code> package installed):"]
noscript_prompt = ll
["Run this in a linux terminal (requires <code>argon2</code> package installed):"]
else
noscript_extra = templates.noscript_extra_sha256
noscript_prompt = ll["Run this in a linux terminal (requires <code>perl</code>):"]
@ -205,7 +207,7 @@ function _M.view(applet)
challenge_hash,
expiry,
signature,
math.ceil(ddos_config["pd"]/8),
math.ceil(ddos_config["pd"] / 8),
argon_time,
argon_kb,
ll["Paste the script output into the box and submit:"]
@ -213,6 +215,12 @@ function _M.view(applet)
end
end
-- local extra_challenge = [[
-- <script src="/.basedflare/js/bc.js"></script>
-- <script src="/.basedflare/js/bm.min.js"></script>
-- ]]
local extra_challenge = ""
-- sub in the body sections
response_body = string.format(
templates.body,
@ -220,6 +228,7 @@ function _M.view(applet)
ls,
ll["Hold on..."],
css_val,
extra_challenge,
combined_challenge,
ddos_config["pd"],
argon_time,
@ -235,9 +244,8 @@ function _M.view(applet)
)
response_status_code = 403
-- if request is POST, check the answer to the pow/cookie
-- if request is POST, check the answer to the pow/cookie
elseif applet.method == "POST" then
-- if they fail, set a var for use in ACLs later
local valid_submission = false
local number_expiry = nil
@ -255,7 +263,6 @@ function _M.view(applet)
local user_pow_response = parsed_body["pow_response"]
local matched_expiry = 0 -- ensure captcha cookie expiry matches POW cookie
if user_pow_response then
-- split the response up (makes the nojs submission easier because it can be a single field)
local split_response = utils.split(user_pow_response, "#")
@ -269,21 +276,21 @@ function _M.view(applet)
-- expiry check
number_expiry = tonumber(given_expiry, 10)
if number_expiry ~= nil and number_expiry > core.now()['sec'] then
-- regenerate the challenge and compare it
local generated_challenge_hash = utils.generate_challenge(applet, pow_cookie_secret, given_user_key, ddos_config, true)
local generated_challenge_hash = utils.generate_challenge(applet, pow_cookie_secret, given_user_key,
ddos_config, true)
if given_challenge_hash == generated_challenge_hash then
-- regenerate the signature and compare it
local generated_signature = sha.hmac(sha.sha3_256, hmac_cookie_secret, given_user_key .. given_challenge_hash .. given_expiry)
local generated_signature = sha.hmac(sha.sha3_256, hmac_cookie_secret,
given_user_key .. given_challenge_hash .. given_expiry)
if given_signature == generated_signature then
-- do the work with their given answer
local hex_hash_output = ""
if ddos_config["pt"] == "argon2" then
local encoded_argon_hash = argon2.hash_encoded(given_challenge_hash .. given_answer, given_user_key)
local encoded_argon_hash = argon2.hash_encoded(given_challenge_hash .. given_answer,
given_user_key)
local trimmed_argon_hash = utils.split(encoded_argon_hash, '$')[6]:sub(0, 43) -- https://github.com/thibaultcha/lua-argon2/issues/37
hex_hash_output = sha.bin_to_hex(sha.base64_to_bin(trimmed_argon_hash));
else
@ -291,15 +298,18 @@ function _M.view(applet)
end
if utils.checkdiff(hex_hash_output, ddos_config["pd"]) then
-- the answer was good, give them a cookie
local signature = sha.hmac(sha.sha3_256, hmac_cookie_secret, given_user_key .. given_challenge_hash .. given_expiry .. given_answer)
local combined_cookie = given_user_key .. "#" .. given_challenge_hash .. "#" .. given_expiry .. "#" .. given_answer .. "#" .. signature
local signature = sha.hmac(sha.sha3_256, hmac_cookie_secret,
given_user_key .. given_challenge_hash .. given_expiry .. given_answer)
local combined_cookie = given_user_key ..
"#" ..
given_challenge_hash ..
"#" .. given_expiry .. "#" .. given_answer .. "#" .. signature
local expiry_date_p = _M.secondsToDate(number_expiry)
applet:add_header(
"set-cookie",
string.format(
--"_basedflare_pow=%s; Expires=%s; Path=/; Domain=.%s; SameSite=Lax; HttpOnly;%s",
--"_basedflare_pow=%s; Expires=%s; Path=/; Domain=.%s; SameSite=Lax; HttpOnly;%s",
"_basedflare_pow=%s; Expires=%s; Path=/; Domain=%s; SameSite=Lax; %s",
combined_cookie,
expiry_date_p,
@ -309,7 +319,6 @@ function _M.view(applet)
)
valid_submission = true
matched_expiry = number_expiry
end
end
end
@ -321,7 +330,6 @@ function _M.view(applet)
local user_captcha_response = parsed_body["h-captcha-response"] or parsed_body["g-recaptcha-response"]
if valid_submission and user_captcha_response then -- only check captcha if POW is already correct
-- format the url for verifying the captcha response
local captcha_url = string.format(
"https://%s%s",
@ -331,19 +339,19 @@ function _M.view(applet)
-- construct the captcha body to send to the captcha url
local captcha_body = url.buildQuery({
secret=captcha_secret,
response=user_captcha_response
secret = captcha_secret,
response = user_captcha_response
})
-- instantiate an http client and make the request
local httpclient = core.httpclient()
local res = httpclient:post{
url=captcha_url,
body=captcha_body,
headers={
[ "host" ] = { captcha_provider_domain },
[ "content-type" ] = { "application/x-www-form-urlencoded" },
[ "user-agent" ] = { "haproxy-protection (haproxy-protection/0.1; +https://gitgud.io/fatchan/haproxy-protection)" }
local res = httpclient:post {
url = captcha_url,
body = captcha_body,
headers = {
["host"] = { captcha_provider_domain },
["content-type"] = { "application/x-www-form-urlencoded" },
["user-agent"] = { "haproxy-protection (haproxy-protection/0.1; +https://gitgud.io/fatchan/haproxy-protection)" }
}
}
@ -372,7 +380,6 @@ function _M.view(applet)
)
valid_submission = valid_submission and true
end
end
if not valid_submission then
@ -384,7 +391,7 @@ function _M.view(applet)
response_status_code = 302
applet:add_header("location", applet.qs)
-- else if its another http method, just 403 them
-- else if its another http method, just 403 them
else
response_status_code = 403
end
@ -395,7 +402,6 @@ function _M.view(applet)
applet:add_header("content-length", string.len(response_body))
applet:start_response()
applet:send(response_body)
end
-- set a variable if ip or subnet in blocked/whitelist map and list of usernames matches the one for the current domain
@ -468,7 +474,7 @@ end
function _M.decide_checks_necessary(txn)
local host = txn.sf:hdr("Host")
local path = txn.sf:path();
local ddos_map_lookup = ddos_map:lookup(host..path) or ddos_map:lookup(host)
local ddos_map_lookup = ddos_map:lookup(host .. path) or ddos_map:lookup(host)
if ddos_map_lookup ~= nil then
local ddos_map_json = json.decode(ddos_map_lookup)
if ddos_map_json.m == 0
@ -510,7 +516,8 @@ function _M.check_captcha_status(txn)
return
end
-- regenerate the signature and compare it
local generated_signature = sha.hmac(sha.sha3_256, hmac_cookie_secret, given_user_key .. given_user_hash .. given_expiry)
local generated_signature = sha.hmac(sha.sha3_256, hmac_cookie_secret,
given_user_key .. given_user_hash .. given_expiry)
if given_signature == generated_signature then
return txn:set_var("txn.captcha_passed", true)
end
@ -543,7 +550,8 @@ function _M.check_pow_status(txn)
return
end
-- regenerate the signature and compare it
local generated_signature = sha.hmac(sha.sha3_256, hmac_cookie_secret, given_user_key .. given_challenge_hash .. given_expiry .. given_answer)
local generated_signature = sha.hmac(sha.sha3_256, hmac_cookie_secret,
given_user_key .. given_challenge_hash .. given_expiry .. given_answer)
if given_signature == generated_signature then
return txn:set_var("txn.pow_passed", true)
end

View File

@ -2,7 +2,7 @@ package.path = package.path .. "./?.lua;/etc/haproxy/scripts/?.lua;/etc/haproxy/
local bot_check = require("bot-check")
local utils = require("utils")
local server_cn_split_regex = "([^;]+);(%u%u)$"
local server_cn_split_regex = "([^;]+)|(%u%u)$"
local backends_map = Map.new('/etc/haproxy/map/backends.map', Map._str)
function get_server_names(txn)

View File

@ -3,7 +3,7 @@ package.path = package.path .. "./?.lua;/etc/haproxy/scripts/?.lua;/etc/haproxy/
local pow_difficulty = tonumber(os.getenv("POW_DIFFICULTY") or 18)
local backends_map = Map.new('/etc/haproxy/map/backends.map', Map._str)
local utils = require("utils")
local server_cn_split_regex = "([^;]+);(%u%u)$"
local server_cn_split_regex = "([^;]+)|(%u%u)$"
local map_space_split_rexex = "([^%s]+)%s+([^%s]+)"
-- setup initial server backends based on hosts.map
@ -27,7 +27,7 @@ function setup_servers()
while line do
local domain, backend_data = line:match(map_space_split_rexex)
local backend_host, continent_code = backend_data:match(server_cn_split_regex)
local new_map_value = server_prefix .. counter .. ';' .. continent_code
local new_map_value = server_prefix .. counter .. '|' .. continent_code
local existing_map_value = backends_map:lookup(domain)
if existing_map_value ~= nil then
local current_backends = utils.split(existing_map_value, ",")

View File

@ -16,7 +16,7 @@ if os.getenv("USE_INTER_FONT") then
end
-- main page template
local body_css = [[:root{--text-color:#c5c8c6;--bg-color:#1d1f21}@media (prefers-color-scheme:light){:root{--text-color:#333;--bg-color:#fff}}.g-recaptcha,.h-captcha{min-height:85px;display:block}.red{display:inline-block;color:#ff0000d0;background:#ff000020;border:1px solid #ff000050;font-weight:bold;padding:12px;border-radius:6px}.left{text-align:left}.powstatus{color:#6b93f7;font-size:small}a,a:visited{color:var(--text-color)}body,html{height:100%%;text-align:center}body{display:flex;flex-direction:column;background-color:var(--bg-color);color:var(--text-color);max-width:60em;margin:0 auto;padding:0 20px}details{max-width:1200px;text-align:left;border-left:2px solid #ff0000d0;padding:10px}code{background-color:#dfdfdf30;border-radius:4px;padding:0 3px;color:#ff6590}h3,img{margin:0 0 16px}li{margin-bottom:1em}footer{font-size:x-small;margin-top:auto;padding:10px;text-align:center;border-top:1px solid #80808040;padding:10px;max-width:300px;margin:auto auto 0}img{display:inline}input,textarea{background:var(--bg-color);color:var(--text-color);border:1px solid var(--text-color);width:100%%;box-sizing:border-box;resize:none;padding:5px;margin:5px;font-family:inherit;border-radius:6px}input[type="submit"]{padding:8px}.pt{padding-top:25vh;word-wrap:break-word;display:flex;flex-direction:column}.pt img{margin:0 auto 10px}.b{display:inline-block;border-radius:50%%;margin:20px 12px;height:16px;width:16px;transform:scale(1);box-shadow:0 0 0 0 #6b93f720;background:#6b93f7;--shadow1:#6b93f790;--shadow2:#6b93f700;--shadow3:#6b93f700}.b.green{background:#31cc31;box-shadow:0 0 0 0 #31cc3120;--shadow1:#31cc3190;--shadow2:#31cc3100;--shadow3:#31cc3100}.b:nth-of-type(1){animation:p 3s infinite}.b:nth-of-type(2){animation:p 3s 0.5s infinite}.b:nth-of-type(3){animation:p 3s 1s infinite}details:not([open]),summary{cursor:pointer}@keyframes p{0%%{transform:scale(.95);box-shadow:0 0 0 0 var(--shadow1)}70%%{transform:scale(1);box-shadow:0 0 0 8px var(--shadow2)}100%%{transform:scale(.95);box-shadow:0 0 0 0 var(--shadow3)}}details summary::-webkit-details-marker,details summary::marker{display:none}details summary{list-style-type:none}details[open] > summary:before{transform:rotate(90deg)}summary{padding-left:20px}summary:before{content:'';border-width:8px;border-style:solid;border-color:transparent transparent transparent var(--text-color);position:absolute;transform:rotate(0);transform-origin:4px 50%%;transition:0.25s transform ease;margin:3px 0 0 -15px}#captcha{margin-top:1em}]]
local body_css = [[:root{--text-color:#c5c8c6;--bg-color:#1d1f21}@media (prefers-color-scheme:light){:root{--text-color:#333;--bg-color:#fff}}.g-recaptcha,.h-captcha{min-height:85px;display:block}.red{display:inline-block;color:#ff0000d0;background:#ff000020;border:1px solid #ff000050;font-weight:bold;padding:12px;border-radius:6px}.left{text-align:left}.powstatus{color:#6b93f7;font-size:small}a,a:visited{color:var(--text-color)}body,html{height:100%%;text-align:center}body{display:flex;flex-direction:column;background-color:var(--bg-color);color:var(--text-color);max-width:60em;margin:0 auto;padding:0 20px}details{max-width:1200px;text-align:left;border-left:2px solid #ff0000d0;padding:10px}code{background-color:#dfdfdf30;border-radius:4px;padding:0 3px;color:#ff6590}h3,img{margin:0 0 16px}li{margin-bottom:1em}footer{font-size:x-small;margin-top:auto;padding:10px;text-align:center;border-top:1px solid #80808040;padding:10px;max-width:300px;margin:auto auto 0}img{display:inline}input,textarea{background:var(--bg-color);color:var(--text-color);border:1px solid var(--text-color);width:100%%;box-sizing:border-box;resize:none;padding:5px;margin:5px;font-family:inherit;border-radius:6px}input[type="submit"]{padding:8px}.pt{padding-top:25vh;word-wrap:break-word;display:flex;flex-direction:column}.pt img{margin:0 auto 10px}.b{display:inline-block;border-radius:50%%;margin:20px 12px;height:16px;width:16px;transform:scale(1);box-shadow:0 0 0 0 #6b93f720;background:#6b93f7;--shadow1:#6b93f790;--shadow2:#6b93f700;--shadow3:#6b93f700}.b.green{background:#31cc31;box-shadow:0 0 0 0 #31cc3120;--shadow1:#31cc3190;--shadow2:#31cc3100;--shadow3:#31cc3100}.b:nth-of-type(1){animation:p 3s infinite}.b:nth-of-type(2){animation:p 3s 0.5s infinite}.b:nth-of-type(3){animation:p 3s 1s infinite}details:not([open]),summary{cursor:pointer}@keyframes p{0%%{transform:scale(.95);box-shadow:0 0 0 0 var(--shadow1)}70%%{transform:scale(1);box-shadow:0 0 0 8px var(--shadow2)}100%%{transform:scale(.95);box-shadow:0 0 0 0 var(--shadow3)}}details summary::-webkit-details-marker,details summary::marker{display:none}details summary{list-style-type:none}details[open] > summary:before{transform:rotate(90deg)}summary{padding-left:20px}summary:before{content:'';border-width:8px;border-style:solid;border-color:transparent transparent transparent var(--text-color);position:absolute;transform:rotate(0);transform-origin:4px 50%%;transition:0.25s transform ease;margin:3px 0 0 -15px}#captcha{margin-top:1em}canvas{display:flex;margin:0 auto;cursor:pointer;background:lightgray;border:2px solid black;}]]
_M.body = string.format([[
<!DOCTYPE html>
<html>
@ -32,6 +32,7 @@ _M.body = string.format([[
<noscript>
<style>.jsonly{display:none}</style>
</noscript>
%%s
<script src="/.basedflare/js/argon2.min.js"></script>
<script src="/.basedflare/js/challenge.min.js"></script>
</head>
@ -48,6 +49,7 @@ _M.body = string.format([[
%%s
</noscript>
<div class="powstatus"></div>
<canvas id="canvas" width="250" height="40"></canvas>
<footer>
<p>Node: <code>%%s</code></p>
<p>%%s</p>