Repository files navigation

@imqueue/http-protect

Implements simple HTTP traffic protection middleware for node-based express-like web-servers to detect and block abnormal activity on a server from a detected IP sources.

Simple configuration allows to set desired limit on number of requests per given time period and define the blacklist threshold for the users which are by exceeding the limit continue to send requests to the server.

The service protected by this module may be configured on a code level or by setting environment variables.

Requirements

  • redis server

Installation

npm i @imqueue/http-protect

Usage

importHttpProtectfrom'@imqueue/http-protect';app.use(newHttpProtect().jsonMiddleware());

Or it is possible to do manual injection:

importHttpProtect,{VerificationStatus}from'@imqueue/http-protect';// inside some async function in the codeconstprotect=newHttpProtect();const{ status, httpCode }=awaitprotect.verify(req);switch(status){caseVerificationStatus.LIMITED: {// user us reached request limit, but not blacklisted yet.// warn about abnormal usagebreak;}caseVerificationStatus.BANNED: {// bad traffic source, requests must be bannedbreak;}default: {// good request, safe to gobreak;}}

This module aldo provides simple API to check if given IP is blacklisted or not, or get the list of banned network addresses:

All three are async, so they must be awaited — without await, bannedNetworks() throws (.toJSON is not a function on a Promise) and the other two log a pending Promise rather than a boolean:

importHttpProtectfrom'@imqueue/http-protect';constprotect=newHttpProtect();// get the list of banned networksconsole.log((awaitprotect.bannedNetworks()).toJSON());// check if given IP is currently banned or notconsole.log(awaitprotect.isBanned('127.0.0.1'));// check if given IP is currently limited or notconsole.log(awaitprotect.isLimited('127.0.0.1'));

This module uses redis server to deal with requests counters and banned networks. It also based on ioredis module to connect to redis server, so you might want to configure it via constructor options or bypass existing ioredis instance in the options. Please, refer HttpProtectOptions interface for more details.

Before you deploy it

Three properties of the defaults are worth deciding about deliberately.

A ban does not expire. Banned addresses go into a redis set that is never given a TTL, and nothing in this module removes a member from it. Once an address passes banLimit it is answered 418 until something outside this module deletes it from <redisPrefix>:block-list. Plan for how you will lift one — an admin endpoint, a cron, or a manual SREM — before you rely on the ban threshold.

maxRequests counts a continuous stream, not a fixed window. The per-IP counter's TTL is pushed back to ttl on every request from that address, so the count is discarded only after a full ttl of silence. With the defaults, 200 requests in 10 seconds trips the limit — and so does one request per second for 200 seconds, which is ordinary behaviour for a real user. If your sessions are long-lived and chatty, raise maxRequests and banLimit accordingly, or you will ban real users.

Anything unidentifiable is refused. If the resolver cannot produce an address, that request is answered 429 without being counted, rather than served unchecked. It is deliberately not pooled under a shared key, because one such client could then exhaust that counter and get every other unidentifiable client permanently banned.

bannedNetworks() and isBanned() widen every banned address to a /32, which is right for IPv4 and wrong for IPv6 — a /32 IPv6 prefix spans 2^96 addresses, so those two methods over-report for IPv6 clients. Actual blocking is unaffected: verify() matches exact addresses in redis and does not use them.

Client IP resolution

By default the client IP is resolved with request-ip, which reads the usual proxy headers (x-forwarded-for, x-real-ip, etc.). Because bans and rate limits are keyed by this address, a client behind an untrusted proxy could spoof those headers to evade limits or poison the ban list. When the service is exposed behind proxies you do not fully control, override the resolver with a trust-aware one (for example built on top of proxy-addr configured with your known proxies):

importproxyaddrfrom'proxy-addr';constprotect=newHttpProtect({// trust only your known load balancer subnetgetClientIp: req=>proxyaddr(req,ip=>ip==='10.0.0.1'),});

License

This project is licensed under the GNU General Public License v3.0. See the LICENSE

Happy Coding!

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

@imqueue/http-protect

Implements simple HTTP traffic protection middleware for node-based express-like web-servers to detect and block abnormal activity on a server from a detected IP sources.

Simple configuration allows to set desired limit on number of requests per given time period and define the blacklist threshold for the users which are by exceeding the limit continue to send requests to the server.

The service protected by this module may be configured on a code level or by setting environment variables.

Requirements

  • redis server

Installation

npm i @imqueue/http-protect

Usage

importHttpProtectfrom'@imqueue/http-protect';app.use(newHttpProtect().jsonMiddleware());

Or it is possible to do manual injection:

importHttpProtect,{VerificationStatus}from'@imqueue/http-protect';// inside some async function in the codeconstprotect=newHttpProtect();const{ status, httpCode }=awaitprotect.verify(req);switch(status){caseVerificationStatus.LIMITED: {// user us reached request limit, but not blacklisted yet.// warn about abnormal usagebreak;}caseVerificationStatus.BANNED: {// bad traffic source, requests must be bannedbreak;}default: {// good request, safe to gobreak;}}

This module aldo provides simple API to check if given IP is blacklisted or not, or get the list of banned network addresses:

All three are async, so they must be awaited — without await, bannedNetworks() throws (.toJSON is not a function on a Promise) and the other two log a pending Promise rather than a boolean:

importHttpProtectfrom'@imqueue/http-protect';constprotect=newHttpProtect();// get the list of banned networksconsole.log((awaitprotect.bannedNetworks()).toJSON());// check if given IP is currently banned or notconsole.log(awaitprotect.isBanned('127.0.0.1'));// check if given IP is currently limited or notconsole.log(awaitprotect.isLimited('127.0.0.1'));

This module uses redis server to deal with requests counters and banned networks. It also based on ioredis module to connect to redis server, so you might want to configure it via constructor options or bypass existing ioredis instance in the options. Please, refer HttpProtectOptions interface for more details.

Before you deploy it

Three properties of the defaults are worth deciding about deliberately.

A ban does not expire. Banned addresses go into a redis set that is never given a TTL, and nothing in this module removes a member from it. Once an address passes banLimit it is answered 418 until something outside this module deletes it from <redisPrefix>:block-list. Plan for how you will lift one — an admin endpoint, a cron, or a manual SREM — before you rely on the ban threshold.

maxRequests counts a continuous stream, not a fixed window. The per-IP counter's TTL is pushed back to ttl on every request from that address, so the count is discarded only after a full ttl of silence. With the defaults, 200 requests in 10 seconds trips the limit — and so does one request per second for 200 seconds, which is ordinary behaviour for a real user. If your sessions are long-lived and chatty, raise maxRequests and banLimit accordingly, or you will ban real users.

Anything unidentifiable is refused. If the resolver cannot produce an address, that request is answered 429 without being counted, rather than served unchecked. It is deliberately not pooled under a shared key, because one such client could then exhaust that counter and get every other unidentifiable client permanently banned.

bannedNetworks() and isBanned() widen every banned address to a /32, which is right for IPv4 and wrong for IPv6 — a /32 IPv6 prefix spans 2^96 addresses, so those two methods over-report for IPv6 clients. Actual blocking is unaffected: verify() matches exact addresses in redis and does not use them.

Client IP resolution

By default the client IP is resolved with request-ip, which reads the usual proxy headers (x-forwarded-for, x-real-ip, etc.). Because bans and rate limits are keyed by this address, a client behind an untrusted proxy could spoof those headers to evade limits or poison the ban list. When the service is exposed behind proxies you do not fully control, override the resolver with a trust-aware one (for example built on top of proxy-addr configured with your known proxies):

importproxyaddrfrom'proxy-addr';constprotect=newHttpProtect({// trust only your known load balancer subnetgetClientIp: req=>proxyaddr(req,ip=>ip==='10.0.0.1'),});

License

This project is licensed under the GNU General Public License v3.0. See the LICENSE

Happy Coding!

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

@imqueue/http-protect

Implements simple HTTP traffic protection middleware for node-based express-like web-servers to detect and block abnormal activity on a server from a detected IP sources.

Simple configuration allows to set desired limit on number of requests per given time period and define the blacklist threshold for the users which are by exceeding the limit continue to send requests to the server.

The service protected by this module may be configured on a code level or by setting environment variables.

Requirements

  • redis server

Installation

npm i @imqueue/http-protect

Usage

importHttpProtectfrom'@imqueue/http-protect';app.use(newHttpProtect().jsonMiddleware());

Or it is possible to do manual injection:

importHttpProtect,{VerificationStatus}from'@imqueue/http-protect';// inside some async function in the codeconstprotect=newHttpProtect();const{ status, httpCode }=awaitprotect.verify(req);switch(status){caseVerificationStatus.LIMITED: {// user us reached request limit, but not blacklisted yet.// warn about abnormal usagebreak;}caseVerificationStatus.BANNED: {// bad traffic source, requests must be bannedbreak;}default: {// good request, safe to gobreak;}}

This module aldo provides simple API to check if given IP is blacklisted or not, or get the list of banned network addresses:

All three are async, so they must be awaited — without await, bannedNetworks() throws (.toJSON is not a function on a Promise) and the other two log a pending Promise rather than a boolean:

importHttpProtectfrom'@imqueue/http-protect';constprotect=newHttpProtect();// get the list of banned networksconsole.log((awaitprotect.bannedNetworks()).toJSON());// check if given IP is currently banned or notconsole.log(awaitprotect.isBanned('127.0.0.1'));// check if given IP is currently limited or notconsole.log(awaitprotect.isLimited('127.0.0.1'));

This module uses redis server to deal with requests counters and banned networks. It also based on ioredis module to connect to redis server, so you might want to configure it via constructor options or bypass existing ioredis instance in the options. Please, refer HttpProtectOptions interface for more details.

Before you deploy it

Three properties of the defaults are worth deciding about deliberately.

A ban does not expire. Banned addresses go into a redis set that is never given a TTL, and nothing in this module removes a member from it. Once an address passes banLimit it is answered 418 until something outside this module deletes it from <redisPrefix>:block-list. Plan for how you will lift one — an admin endpoint, a cron, or a manual SREM — before you rely on the ban threshold.

maxRequests counts a continuous stream, not a fixed window. The per-IP counter's TTL is pushed back to ttl on every request from that address, so the count is discarded only after a full ttl of silence. With the defaults, 200 requests in 10 seconds trips the limit — and so does one request per second for 200 seconds, which is ordinary behaviour for a real user. If your sessions are long-lived and chatty, raise maxRequests and banLimit accordingly, or you will ban real users.

Anything unidentifiable is refused. If the resolver cannot produce an address, that request is answered 429 without being counted, rather than served unchecked. It is deliberately not pooled under a shared key, because one such client could then exhaust that counter and get every other unidentifiable client permanently banned.

bannedNetworks() and isBanned() widen every banned address to a /32, which is right for IPv4 and wrong for IPv6 — a /32 IPv6 prefix spans 2^96 addresses, so those two methods over-report for IPv6 clients. Actual blocking is unaffected: verify() matches exact addresses in redis and does not use them.

Client IP resolution

By default the client IP is resolved with request-ip, which reads the usual proxy headers (x-forwarded-for, x-real-ip, etc.). Because bans and rate limits are keyed by this address, a client behind an untrusted proxy could spoof those headers to evade limits or poison the ban list. When the service is exposed behind proxies you do not fully control, override the resolver with a trust-aware one (for example built on top of proxy-addr configured with your known proxies):

importproxyaddrfrom'proxy-addr';constprotect=newHttpProtect({// trust only your known load balancer subnetgetClientIp: req=>proxyaddr(req,ip=>ip==='10.0.0.1'),});

License

This project is licensed under the GNU General Public License v3.0. See the LICENSE

Happy Coding!

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

@imqueue/http-protect

Implements simple HTTP traffic protection middleware for node-based express-like web-servers to detect and block abnormal activity on a server from a detected IP sources.

Simple configuration allows to set desired limit on number of requests per given time period and define the blacklist threshold for the users which are by exceeding the limit continue to send requests to the server.

The service protected by this module may be configured on a code level or by setting environment variables.

Requirements

  • redis server

Installation

npm i @imqueue/http-protect

Usage

importHttpProtectfrom'@imqueue/http-protect';app.use(newHttpProtect().jsonMiddleware());

Or it is possible to do manual injection:

importHttpProtect,{VerificationStatus}from'@imqueue/http-protect';// inside some async function in the codeconstprotect=newHttpProtect();const{ status, httpCode }=awaitprotect.verify(req);switch(status){caseVerificationStatus.LIMITED: {// user us reached request limit, but not blacklisted yet.// warn about abnormal usagebreak;}caseVerificationStatus.BANNED: {// bad traffic source, requests must be bannedbreak;}default: {// good request, safe to gobreak;}}

This module aldo provides simple API to check if given IP is blacklisted or not, or get the list of banned network addresses:

All three are async, so they must be awaited — without await, bannedNetworks() throws (.toJSON is not a function on a Promise) and the other two log a pending Promise rather than a boolean:

importHttpProtectfrom'@imqueue/http-protect';constprotect=newHttpProtect();// get the list of banned networksconsole.log((awaitprotect.bannedNetworks()).toJSON());// check if given IP is currently banned or notconsole.log(awaitprotect.isBanned('127.0.0.1'));// check if given IP is currently limited or notconsole.log(awaitprotect.isLimited('127.0.0.1'));

This module uses redis server to deal with requests counters and banned networks. It also based on ioredis module to connect to redis server, so you might want to configure it via constructor options or bypass existing ioredis instance in the options. Please, refer HttpProtectOptions interface for more details.

Before you deploy it

Three properties of the defaults are worth deciding about deliberately.

A ban does not expire. Banned addresses go into a redis set that is never given a TTL, and nothing in this module removes a member from it. Once an address passes banLimit it is answered 418 until something outside this module deletes it from <redisPrefix>:block-list. Plan for how you will lift one — an admin endpoint, a cron, or a manual SREM — before you rely on the ban threshold.

maxRequests counts a continuous stream, not a fixed window. The per-IP counter's TTL is pushed back to ttl on every request from that address, so the count is discarded only after a full ttl of silence. With the defaults, 200 requests in 10 seconds trips the limit — and so does one request per second for 200 seconds, which is ordinary behaviour for a real user. If your sessions are long-lived and chatty, raise maxRequests and banLimit accordingly, or you will ban real users.

Anything unidentifiable is refused. If the resolver cannot produce an address, that request is answered 429 without being counted, rather than served unchecked. It is deliberately not pooled under a shared key, because one such client could then exhaust that counter and get every other unidentifiable client permanently banned.

bannedNetworks() and isBanned() widen every banned address to a /32, which is right for IPv4 and wrong for IPv6 — a /32 IPv6 prefix spans 2^96 addresses, so those two methods over-report for IPv6 clients. Actual blocking is unaffected: verify() matches exact addresses in redis and does not use them.

Client IP resolution

By default the client IP is resolved with request-ip, which reads the usual proxy headers (x-forwarded-for, x-real-ip, etc.). Because bans and rate limits are keyed by this address, a client behind an untrusted proxy could spoof those headers to evade limits or poison the ban list. When the service is exposed behind proxies you do not fully control, override the resolver with a trust-aware one (for example built on top of proxy-addr configured with your known proxies):

importproxyaddrfrom'proxy-addr';constprotect=newHttpProtect({// trust only your known load balancer subnetgetClientIp: req=>proxyaddr(req,ip=>ip==='10.0.0.1'),});

License

This project is licensed under the GNU General Public License v3.0. See the LICENSE

Happy Coding!

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

@imqueue/http-protect

Implements simple HTTP traffic protection middleware for node-based express-like web-servers to detect and block abnormal activity on a server from a detected IP sources.

Simple configuration allows to set desired limit on number of requests per given time period and define the blacklist threshold for the users which are by exceeding the limit continue to send requests to the server.

The service protected by this module may be configured on a code level or by setting environment variables.

Requirements

  • redis server

Installation

npm i @imqueue/http-protect

Usage

importHttpProtectfrom'@imqueue/http-protect';app.use(newHttpProtect().jsonMiddleware());

Or it is possible to do manual injection:

importHttpProtect,{VerificationStatus}from'@imqueue/http-protect';// inside some async function in the codeconstprotect=newHttpProtect();const{ status, httpCode }=awaitprotect.verify(req);switch(status){caseVerificationStatus.LIMITED: {// user us reached request limit, but not blacklisted yet.// warn about abnormal usagebreak;}caseVerificationStatus.BANNED: {// bad traffic source, requests must be bannedbreak;}default: {// good request, safe to gobreak;}}

This module aldo provides simple API to check if given IP is blacklisted or not, or get the list of banned network addresses:

All three are async, so they must be awaited — without await, bannedNetworks() throws (.toJSON is not a function on a Promise) and the other two log a pending Promise rather than a boolean:

importHttpProtectfrom'@imqueue/http-protect';constprotect=newHttpProtect();// get the list of banned networksconsole.log((awaitprotect.bannedNetworks()).toJSON());// check if given IP is currently banned or notconsole.log(awaitprotect.isBanned('127.0.0.1'));// check if given IP is currently limited or notconsole.log(awaitprotect.isLimited('127.0.0.1'));

This module uses redis server to deal with requests counters and banned networks. It also based on ioredis module to connect to redis server, so you might want to configure it via constructor options or bypass existing ioredis instance in the options. Please, refer HttpProtectOptions interface for more details.

Before you deploy it

Three properties of the defaults are worth deciding about deliberately.

A ban does not expire. Banned addresses go into a redis set that is never given a TTL, and nothing in this module removes a member from it. Once an address passes banLimit it is answered 418 until something outside this module deletes it from <redisPrefix>:block-list. Plan for how you will lift one — an admin endpoint, a cron, or a manual SREM — before you rely on the ban threshold.

maxRequests counts a continuous stream, not a fixed window. The per-IP counter's TTL is pushed back to ttl on every request from that address, so the count is discarded only after a full ttl of silence. With the defaults, 200 requests in 10 seconds trips the limit — and so does one request per second for 200 seconds, which is ordinary behaviour for a real user. If your sessions are long-lived and chatty, raise maxRequests and banLimit accordingly, or you will ban real users.

Anything unidentifiable is refused. If the resolver cannot produce an address, that request is answered 429 without being counted, rather than served unchecked. It is deliberately not pooled under a shared key, because one such client could then exhaust that counter and get every other unidentifiable client permanently banned.

bannedNetworks() and isBanned() widen every banned address to a /32, which is right for IPv4 and wrong for IPv6 — a /32 IPv6 prefix spans 2^96 addresses, so those two methods over-report for IPv6 clients. Actual blocking is unaffected: verify() matches exact addresses in redis and does not use them.

Client IP resolution

By default the client IP is resolved with request-ip, which reads the usual proxy headers (x-forwarded-for, x-real-ip, etc.). Because bans and rate limits are keyed by this address, a client behind an untrusted proxy could spoof those headers to evade limits or poison the ban list. When the service is exposed behind proxies you do not fully control, override the resolver with a trust-aware one (for example built on top of proxy-addr configured with your known proxies):

importproxyaddrfrom'proxy-addr';constprotect=newHttpProtect({// trust only your known load balancer subnetgetClientIp: req=>proxyaddr(req,ip=>ip==='10.0.0.1'),});

License

This project is licensed under the GNU General Public License v3.0. See the LICENSE

Happy Coding!

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

@imqueue/http-protect

Implements simple HTTP traffic protection middleware for node-based express-like web-servers to detect and block abnormal activity on a server from a detected IP sources.

Simple configuration allows to set desired limit on number of requests per given time period and define the blacklist threshold for the users which are by exceeding the limit continue to send requests to the server.

The service protected by this module may be configured on a code level or by setting environment variables.

Requirements

  • redis server

Installation

npm i @imqueue/http-protect

Usage

importHttpProtectfrom'@imqueue/http-protect';app.use(newHttpProtect().jsonMiddleware());

Or it is possible to do manual injection:

importHttpProtect,{VerificationStatus}from'@imqueue/http-protect';// inside some async function in the codeconstprotect=newHttpProtect();const{ status, httpCode }=awaitprotect.verify(req);switch(status){caseVerificationStatus.LIMITED: {// user us reached request limit, but not blacklisted yet.// warn about abnormal usagebreak;}caseVerificationStatus.BANNED: {// bad traffic source, requests must be bannedbreak;}default: {// good request, safe to gobreak;}}

This module aldo provides simple API to check if given IP is blacklisted or not, or get the list of banned network addresses:

All three are async, so they must be awaited — without await, bannedNetworks() throws (.toJSON is not a function on a Promise) and the other two log a pending Promise rather than a boolean:

importHttpProtectfrom'@imqueue/http-protect';constprotect=newHttpProtect();// get the list of banned networksconsole.log((awaitprotect.bannedNetworks()).toJSON());// check if given IP is currently banned or notconsole.log(awaitprotect.isBanned('127.0.0.1'));// check if given IP is currently limited or notconsole.log(awaitprotect.isLimited('127.0.0.1'));

This module uses redis server to deal with requests counters and banned networks. It also based on ioredis module to connect to redis server, so you might want to configure it via constructor options or bypass existing ioredis instance in the options. Please, refer HttpProtectOptions interface for more details.

Before you deploy it

Three properties of the defaults are worth deciding about deliberately.

A ban does not expire. Banned addresses go into a redis set that is never given a TTL, and nothing in this module removes a member from it. Once an address passes banLimit it is answered 418 until something outside this module deletes it from <redisPrefix>:block-list. Plan for how you will lift one — an admin endpoint, a cron, or a manual SREM — before you rely on the ban threshold.

maxRequests counts a continuous stream, not a fixed window. The per-IP counter's TTL is pushed back to ttl on every request from that address, so the count is discarded only after a full ttl of silence. With the defaults, 200 requests in 10 seconds trips the limit — and so does one request per second for 200 seconds, which is ordinary behaviour for a real user. If your sessions are long-lived and chatty, raise maxRequests and banLimit accordingly, or you will ban real users.

Anything unidentifiable is refused. If the resolver cannot produce an address, that request is answered 429 without being counted, rather than served unchecked. It is deliberately not pooled under a shared key, because one such client could then exhaust that counter and get every other unidentifiable client permanently banned.

bannedNetworks() and isBanned() widen every banned address to a /32, which is right for IPv4 and wrong for IPv6 — a /32 IPv6 prefix spans 2^96 addresses, so those two methods over-report for IPv6 clients. Actual blocking is unaffected: verify() matches exact addresses in redis and does not use them.

Client IP resolution

By default the client IP is resolved with request-ip, which reads the usual proxy headers (x-forwarded-for, x-real-ip, etc.). Because bans and rate limits are keyed by this address, a client behind an untrusted proxy could spoof those headers to evade limits or poison the ban list. When the service is exposed behind proxies you do not fully control, override the resolver with a trust-aware one (for example built on top of proxy-addr configured with your known proxies):

importproxyaddrfrom'proxy-addr';constprotect=newHttpProtect({// trust only your known load balancer subnetgetClientIp: req=>proxyaddr(req,ip=>ip==='10.0.0.1'),});

License

This project is licensed under the GNU General Public License v3.0. See the LICENSE

Happy Coding!

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

@imqueue/http-protect

Implements simple HTTP traffic protection middleware for node-based express-like web-servers to detect and block abnormal activity on a server from a detected IP sources.

Simple configuration allows to set desired limit on number of requests per given time period and define the blacklist threshold for the users which are by exceeding the limit continue to send requests to the server.

The service protected by this module may be configured on a code level or by setting environment variables.

Requirements

  • redis server

Installation

npm i @imqueue/http-protect

Usage

importHttpProtectfrom'@imqueue/http-protect';app.use(newHttpProtect().jsonMiddleware());

Or it is possible to do manual injection:

importHttpProtect,{VerificationStatus}from'@imqueue/http-protect';// inside some async function in the codeconstprotect=newHttpProtect();const{ status, httpCode }=awaitprotect.verify(req);switch(status){caseVerificationStatus.LIMITED: {// user us reached request limit, but not blacklisted yet.// warn about abnormal usagebreak;}caseVerificationStatus.BANNED: {// bad traffic source, requests must be bannedbreak;}default: {// good request, safe to gobreak;}}

This module aldo provides simple API to check if given IP is blacklisted or not, or get the list of banned network addresses:

All three are async, so they must be awaited — without await, bannedNetworks() throws (.toJSON is not a function on a Promise) and the other two log a pending Promise rather than a boolean:

importHttpProtectfrom'@imqueue/http-protect';constprotect=newHttpProtect();// get the list of banned networksconsole.log((awaitprotect.bannedNetworks()).toJSON());// check if given IP is currently banned or notconsole.log(awaitprotect.isBanned('127.0.0.1'));// check if given IP is currently limited or notconsole.log(awaitprotect.isLimited('127.0.0.1'));

This module uses redis server to deal with requests counters and banned networks. It also based on ioredis module to connect to redis server, so you might want to configure it via constructor options or bypass existing ioredis instance in the options. Please, refer HttpProtectOptions interface for more details.

Before you deploy it

Three properties of the defaults are worth deciding about deliberately.

A ban does not expire. Banned addresses go into a redis set that is never given a TTL, and nothing in this module removes a member from it. Once an address passes banLimit it is answered 418 until something outside this module deletes it from <redisPrefix>:block-list. Plan for how you will lift one — an admin endpoint, a cron, or a manual SREM — before you rely on the ban threshold.

maxRequests counts a continuous stream, not a fixed window. The per-IP counter's TTL is pushed back to ttl on every request from that address, so the count is discarded only after a full ttl of silence. With the defaults, 200 requests in 10 seconds trips the limit — and so does one request per second for 200 seconds, which is ordinary behaviour for a real user. If your sessions are long-lived and chatty, raise maxRequests and banLimit accordingly, or you will ban real users.

Anything unidentifiable is refused. If the resolver cannot produce an address, that request is answered 429 without being counted, rather than served unchecked. It is deliberately not pooled under a shared key, because one such client could then exhaust that counter and get every other unidentifiable client permanently banned.

bannedNetworks() and isBanned() widen every banned address to a /32, which is right for IPv4 and wrong for IPv6 — a /32 IPv6 prefix spans 2^96 addresses, so those two methods over-report for IPv6 clients. Actual blocking is unaffected: verify() matches exact addresses in redis and does not use them.

Client IP resolution

By default the client IP is resolved with request-ip, which reads the usual proxy headers (x-forwarded-for, x-real-ip, etc.). Because bans and rate limits are keyed by this address, a client behind an untrusted proxy could spoof those headers to evade limits or poison the ban list. When the service is exposed behind proxies you do not fully control, override the resolver with a trust-aware one (for example built on top of proxy-addr configured with your known proxies):

importproxyaddrfrom'proxy-addr';constprotect=newHttpProtect({// trust only your known load balancer subnetgetClientIp: req=>proxyaddr(req,ip=>ip==='10.0.0.1'),});

License

This project is licensed under the GNU General Public License v3.0. See the LICENSE

Happy Coding!

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

@imqueue/http-protect

Implements simple HTTP traffic protection middleware for node-based express-like web-servers to detect and block abnormal activity on a server from a detected IP sources.

Simple configuration allows to set desired limit on number of requests per given time period and define the blacklist threshold for the users which are by exceeding the limit continue to send requests to the server.

The service protected by this module may be configured on a code level or by setting environment variables.

Requirements

  • redis server

Installation

npm i @imqueue/http-protect

Usage

importHttpProtectfrom'@imqueue/http-protect';app.use(newHttpProtect().jsonMiddleware());

Or it is possible to do manual injection:

importHttpProtect,{VerificationStatus}from'@imqueue/http-protect';// inside some async function in the codeconstprotect=newHttpProtect();const{ status, httpCode }=awaitprotect.verify(req);switch(status){caseVerificationStatus.LIMITED: {// user us reached request limit, but not blacklisted yet.// warn about abnormal usagebreak;}caseVerificationStatus.BANNED: {// bad traffic source, requests must be bannedbreak;}default: {// good request, safe to gobreak;}}

This module aldo provides simple API to check if given IP is blacklisted or not, or get the list of banned network addresses:

All three are async, so they must be awaited — without await, bannedNetworks() throws (.toJSON is not a function on a Promise) and the other two log a pending Promise rather than a boolean:

importHttpProtectfrom'@imqueue/http-protect';constprotect=newHttpProtect();// get the list of banned networksconsole.log((awaitprotect.bannedNetworks()).toJSON());// check if given IP is currently banned or notconsole.log(awaitprotect.isBanned('127.0.0.1'));// check if given IP is currently limited or notconsole.log(awaitprotect.isLimited('127.0.0.1'));

This module uses redis server to deal with requests counters and banned networks. It also based on ioredis module to connect to redis server, so you might want to configure it via constructor options or bypass existing ioredis instance in the options. Please, refer HttpProtectOptions interface for more details.

Before you deploy it

Three properties of the defaults are worth deciding about deliberately.

A ban does not expire. Banned addresses go into a redis set that is never given a TTL, and nothing in this module removes a member from it. Once an address passes banLimit it is answered 418 until something outside this module deletes it from <redisPrefix>:block-list. Plan for how you will lift one — an admin endpoint, a cron, or a manual SREM — before you rely on the ban threshold.

maxRequests counts a continuous stream, not a fixed window. The per-IP counter's TTL is pushed back to ttl on every request from that address, so the count is discarded only after a full ttl of silence. With the defaults, 200 requests in 10 seconds trips the limit — and so does one request per second for 200 seconds, which is ordinary behaviour for a real user. If your sessions are long-lived and chatty, raise maxRequests and banLimit accordingly, or you will ban real users.

Anything unidentifiable is refused. If the resolver cannot produce an address, that request is answered 429 without being counted, rather than served unchecked. It is deliberately not pooled under a shared key, because one such client could then exhaust that counter and get every other unidentifiable client permanently banned.

bannedNetworks() and isBanned() widen every banned address to a /32, which is right for IPv4 and wrong for IPv6 — a /32 IPv6 prefix spans 2^96 addresses, so those two methods over-report for IPv6 clients. Actual blocking is unaffected: verify() matches exact addresses in redis and does not use them.

Client IP resolution

By default the client IP is resolved with request-ip, which reads the usual proxy headers (x-forwarded-for, x-real-ip, etc.). Because bans and rate limits are keyed by this address, a client behind an untrusted proxy could spoof those headers to evade limits or poison the ban list. When the service is exposed behind proxies you do not fully control, override the resolver with a trust-aware one (for example built on top of proxy-addr configured with your known proxies):

importproxyaddrfrom'proxy-addr';constprotect=newHttpProtect({// trust only your known load balancer subnetgetClientIp: req=>proxyaddr(req,ip=>ip==='10.0.0.1'),});

License

This project is licensed under the GNU General Public License v3.0. See the LICENSE

Happy Coding!

Releases

Packages

Used by

Contributors

Languages