Skip to Content

Odoo 19 Community: Web RBL – Blocklist for Attack Traffic on Your Own Websites

24 September 2026 by
Odoo 19 Community: Web RBL – Blocklist for Attack Traffic on Your Own Websites

Source code: github.com/Alex0176/odoo-web-rbl — AGPL-3, free to use.

Anyone who runs an Odoo server on the internet gets visitors. Not occasionally, but continuously. Over seventeen days, we logged and analysed 731,297 requests on our five websites. 110,511 of them were not visitors, but automated searches for vulnerabilities, credentials and configuration files.

This post describes Web RBL, an open-source module for Odoo 19 Community that detects, scores, records and rejects this traffic — and that publishes the blocklist as an RBL list, that is, in the format that firewalls and reverse proxies read directly. All figures come from live operation.

What the Module Can Do — the Functions at a Glance

FunctionWhat it doesDelivery
Pattern detectionDetects probing by path and extension: .env, .git, configuration files, cloud keys, CI/CD build instructions, WordPress, PHP, path traversal, database managers, GraphQL, command execution via /api/fs/exec and similar interfacesblock
Three levels per patternblock, count or report — switchable per pattern, without codeper pattern
ThresholdsHow many hits of the same pattern before blocking; ten for logins10 for login
Login defenceFailed logins via the form and XML-RPC/JSON-RPC, failed session checks, access to the database manager — in the database, not in memoryon
Bot detection at loginAnyone who ignores the wait message after ten failed attempts and keeps knocking is blocked immediatelyblock
Behaviour detectionAddresses that generate twenty error pages and hardly a single successful page in five minutes — in other words, guessing rather than readingreport
Score 0–100How certain is it that an attacker is behind the address? Based on the severity of the pattern, number of different patterns, conspicuous days, affected domains and hit count — with a plain-text explanationon
Login block by scoreFrom a configurable score, login is forbidden; the website remains readablefrom 40
Suspected stolen credentialsA successful login from a conspicuous address is reported and, if you wish, assigned as a task — once per address and dayon
Threat listsTor exit nodes, Spamhaus DROP, FireHOL Level 1, blocklist.de preconfigured; any further RBL list can be added by address, daily synchronisation, each list with its own levelreport
No access to loginThe fourth level for lists and countries: website readable, login form and interfaces closed — the right answer to TorOption
Country of originCountry for each hit and address via GeoLite2; the module keeps the database up to date itself with a free licence keyshow
Country rulesBlock, no access to login, report or explicitly allow — per country, deliberately with no defaultnone
MisconfigurationsQNAP, Synology, Exchange Autodiscover, ActiveSync, WebDAV/CalDAV/CardDAV are recognised, never blocked, and placed on a work list with the finding and the affected domainreport
Ticket per addressA finding becomes a ticket on request — exactly one per address; a closed one is reopened rather than duplicatedoff
Mandatory pages and harvestersThe canonical imprint path always stays reachable; automated trial-and-error of foreign spellings is detected and, if you wish, blockedreport
AllowlistAddresses and networks that are never blocked; filled daily from the firewall (VPN peers) and from the published address ranges of Googlebot and Bingboton
Customer assignmentA hit from a customer's network is assigned to that customer — from the allowlist, never guessed by handon
Domain and identifier per hitWhich website was addressed, and with which user agent — both are missing from the access logon
AnalysisChart and pivot across all hits: by pattern, day, domain, country, addresson
DecoysProbes receive fabricated responses containing a canary value; whoever retrieves it later has read themoff
RBL deliveryThe blocklist as a plain address list or JSON at its own token-protected address — for HAProxy, OPNsense and every firewall that reads URL listson

Everything that is delivered with report, off or option is that way on purpose: anyone who arms a block should do so because they have watched for a few days to see who falls for it — not because the default said so.

Web RBL analysis: hits per pattern over seven days

The analysis: hits per pattern over seven days. The tallest bar is the search for .env files.

RBL lists: a standardised way to deliver blocks

RBL stands for Real-time Blackhole List. The term comes from the mail world, where Spamhaus and others have been publishing lists of malicious sender addresses since the nineties. The principle is extremely simple, and that is exactly why it is so widespread: one address per line, retrievable over HTTP, reloaded regularly. No format that first has to be parsed, no interface that requires a tool.

Practically every firewall and every reverse proxy can read such a list and build rules from it. Vendors call this different things, but the mechanism is the same everywhere:

  • OPNsense and pfSense — URL Table Alias: load an address list via HTTP, use it as an alias in firewall rules, refresh hourly or daily
  • HAProxy — ACL files and maps: src -f sperrliste.txt in an http-request deny rule, reloadable via the runtime API without a restart
  • nginx — geo module with an included address file, or deny directives from a generated include file
  • Fortinet FortiGate — External Block List or Threat Feed: a URL from which the firewall retrieves an IP list and uses it in policies
  • Palo Alto Networks — External Dynamic List (EDL): the same principle, as its own object type in the security policies
  • Check Point — Custom Intelligence Feeds and IP Block Lists via a URL
  • Cisco Secure Firewall (Firepower) — Security Intelligence Feeds with custom list sources
  • Sophos Firewall — Third-Party Threat Feeds: address lists from a URL as a source for block rules
  • Juniper SRX — Dynamic Address Entries from feeds
  • MikroTik RouterOS — Address Lists, populated from a URL by script and fetch
  • CrowdSec — custom lists as a source for the bouncers on every supported system

This is exactly the format in which Web RBL delivers its blocklist. The firewall drops the packets before they reach the web server — and with them every other attempt from the same address, for example on the mail server or the VPN gateway.

Both directions, but at the right place

The route also works the other way round: in Web RBL you can enter any RBL list you like — a URL, a level, done. Tor exit nodes, Spamhaus DROP, FireHOL and blocklist.de are preconfigured, and more can be added in three clicks. The module breaks every list down into address ranges and checks every request against them, in a few microseconds.

And yet: it makes more sense to use third-party lists on the firewall in front. For three reasons. The firewall sees all services, not just HTTP — an address from the Spamhaus list should also fail at the mail server and the VPN. It drops at packet level, before a connection is even established, and so costs the application server nothing. And it is the place where such lists are maintained anyway; a second copy in the application is a second place that can go stale.

What the lists in the module are still good for: what a firewall cannot do. It knows no login form. Web RBL can deny an address from the Tor list access to login and still serve the website — anyone who wants to read anonymously should be allowed to; anyone who wants to try out passwords anonymously should not. And it can report a successful login from a listed address as a suspicious case. Both need the list at the place where the login happens.

Scoring instead of gut feeling

An address that asked for /wp-login.php once is something different from one that went through five different patterns across three domains on two days. Web RBL turns this into a score from 0 to 100 — from what it has itself seen, without any external service and without any outbound query.

Web RBL entry with a score of 84 and justification

One entry: 99 hits, a score of 84, and the justification in plain language — heaviest pattern, five different serious patterns, two conspicuous days, more than twenty hits.

The score decides two things. Above an adjustable value, login is prohibited — the website stays readable, but whoever has already been searching for credentials gets no chance to try out any they have found. And a successful login from an address with a score is reported: then someone was no longer guessing but knew. Either the legitimate user was travelling, or the credentials have gone astray — the module does not decide that; it puts it before someone who can follow up. The password itself is never touched in the process.

Web RBL blocklist, filtered by score of 40 or above

The blocklist, filtered by score of 40 or above: number of hits, conspicuous days, affected domains and the last path per address.

What Web RBL protects — the protective effect in detail

1. Credentials stay undiscovered

The largest measured class of attack is not looking for vulnerabilities in Odoo, but for forgotten secrets. Over seventeen days:

  • 57,721 requests for .env files — these hold database passwords, API keys and session secrets
  • 5,535 requests for configuration files
  • 4,388 requests for cloud credentials: credentials.json, service-account.json, firebase-adminsdk.json, rclone.conf, .git-credentials
  • 2,811 requests for .git directories — anyone who finds one downloads the complete source code including history
  • 1,907 requests for build instructions such as .github/workflows/deploy.yml or .gitlab-ci.yml, which contain registries and key names

What matters here is not the number of requests, but the number of different addresses: /credentials.json came from 98 addresses, /.git-credentials from 92. This is not a single scanner, but a standard repertoire running worldwide at the same time.

2. Login attempts lead nowhere — permanently

Odoo ships with its own brake on password guessing. It has three limits, documented in the Odoo source code itself: the counter lives in the working memory of the worker process and is reset on every restart, it only delays rather than blocking, and it counts only attempts that get as far as the password check.

Web RBL hooks in at the same points, but writes to the database. This means:

  • The state survives every restart and applies across all worker processes
  • Both routes are covered: the login form /web/login and the interfaces /xmlrpc/2/common and /jsonrpc
  • Attempts that never get as far as the password check are also recorded — for example logins without a valid session token
  • A failed access to the database manager is counted too; that is the most serious login attempt of all

The threshold for logins is set to ten, and the reason is a measurement: of nine failed logins in seventeen days, the majority came from customers who were setting up access themselves on a further device and entered the wrong domain — in one case their own, just with a hyphen instead of a dot. A block at the first failed attempt would not have hit attackers but customers during setup.

Anyone who keeps knocking, on the other hand, even though the server has already replied ‘please wait a moment’, is blocked immediately. A human reads that message and waits; whoever ignores it is a program. A dedicated pattern with a threshold of zero catches exactly this case, and it cannot hit a colleague: the only one who gets there is someone who already has ten failed attempts behind them and then carries on.

3. The attack no longer costs the server anything

A probe for a path such as /@fs/../../.env used to be expensive before the module: Odoo looks for the error page, renders it, trips while building the address over the dot segments and then fails on the error page of the error page as well. Two stack traces per probe. 28,401 such errors were measured over seventeen days, plus 9,467 follow-on errors.

Web RBL hooks into ir.http._match, that is before routing. The response is produced without a template, without any database access beyond the block check and without a log line. The check against the allowlist, threat lists and country rules runs from the cache over sorted address ranges — a few microseconds, even with more than thirty thousand ranges.

4. The firewall throws the attacker out before they arrive

The blocklist is published as an RBL list that HAProxy and OPNsense read directly. The attacker thus no longer reaches the web server at all — nor any other service on the same host. An access that has once been recognised as malicious thus protects the whole machine retroactively.

Three levels: block, count, report

Not every request that leads nowhere is an attack. Web RBL therefore distinguishes three levels, and the level can be changed per pattern without touching the code.

LevelThe triggering requestConsequence for the address
blockis rejectedblocked for 24 hours, permanently after three conspicuous days
countpasses throughonly recorded, no block
reportpasses throughrecorded with finding text, never blocked

For threat lists and countries there is a fourth: no access to login. The website stays readable, login form and interfaces are closed. This is the middle way for everything that is suspicious but does not have to be malicious — Tor, a data centre range, a country with no customers.

Which attacks are detected

All patterns have been tested against real traffic. The number of different addresses is deliberately shown alongside — it shows whether a pattern hits a single scanner or a widespread approach.

Attack classRequestsAddresses
.env harvesting57.721223
PHP probes12.237256
Path traversal with a worthwhile target10.04664
Configuration files5.535114
Cloud access keys4.388152
WordPress probes3.366385
.git harvesting2.811255
Odoo reconnaissance (database manager, version query)2.500213
AI tool chains and command execution (/mcp, /api/fs/exec)2.45074
Build instructions (CI/CD)1.90788
GraphQL endpoints1.87871

The most recent class is remarkable. /api/fs/exec was requested 369 times from 54 different addresses — that is not read access, but an attempt to execute commands. Such targets did not exist two years ago; they come from the interfaces of new AI tools and task queues. Anyone who aligns their defences only with WordPress and PHP sees none of this. In the scoring, this class accordingly weighs heavily.

Threat lists and country of origin

Web RBL threat lists: Tor, Spamhaus DROP, FireHOL, blocklist.de

Four lists, 32,851 address ranges, all set to ‘report only’. Every further list is one address and one level.

A third-party list is a claim that somebody else makes, for reasons we do not know, about traffic we have never seen. It can be wrong, outdated or intended for a different purpose: blocklist.de mainly collects addresses that have tried out SSH logins elsewhere, and that tells you little about a web server. That is why Web RBL keeps third-party lists separate from its own blocklist, and why the default is report. And your own allowlist always wins: a customer we know weighs more heavily than the assessment of a third party.

The country of origin is shown on every hit and every address. For assessment it is indispensable — a login attempt from your own country at ten in the morning is something different from one from a country without a single customer. For blanket blocking it is of little use, and our own figures say so: the attackers in our list came predominantly from cloud ranges in Belgium, Finland and the Netherlands. A block on everything outside the German-speaking world would not have hit them — but it would have hit our own staff on holiday and customers on business trips.

Web RBL country rules: delivered empty, with an explanation

Country rules are delivered empty, and the view says why.

Report misconfigurations instead of blocking them

The most common case in real traffic is neither an attack nor a visitor, but faulty software that mistakes the web server for something else:

  • 1,367 calls to the web interface of a QNAP NAS — a sync client whose target is no longer correct. A single connection knocked 962 times.
  • 65 Autodiscover queries — an Outlook that mistakes the web domain for its Exchange

Such addresses are never blocked. A block does not fix the fault, it hides it — and hits the person whose device is already set up wrongly. Instead, an entry is created with a finding text and the affected domains, from which a ticket is automatically created on request: exactly one per address, and if it is closed and the address stands out again, the same ticket reopens rather than a second one being created.

QNAP, Synology, Exchange Autodiscover, ActiveSync and WebDAV, CalDAV and CardDAV clients are recognised. The patterns name exact file names and directories, never mere extensions: In the same /cgi-bin/ directory the harmless sync calls sit right next to classic probes such as printenv.pl.

The allowlist: addresses that are never blocked

The price of a mistake is not the same for every address. With a scanner from a data centre, a false alarm costs nothing. With the peer of a site tunnel, it costs the site.

That is why there is a second list that comes before the first: addresses and networks that are never blocked. It does not silence anything — whoever is on it is still checked and recorded, only the consequence is dropped. That is the difference from an exception in the firewall: that makes you blind, this one only makes you patient.

It is filled daily from two sources: from the firewall itself with all VPN peers, so that a newly set-up tunnel does not depend on someone remembering the blocklist — and from the published address ranges of Google and Microsoft for their crawlers. Only in this way can a genuine Googlebot be told apart from a scanner that calls itself one. Search engines must never be blocked; whoever locks them out removes their site from indexing with no way back.

A rule applies here that is more important than the synchronisation: An empty response never deletes. If the source does not respond, responds with nothing or with something unintelligible, the list remains unchanged. An allowlist that empties itself in the event of a fault takes all tunnels with it at the worst conceivable moment.

If an allowlist entry carries a customer, every hit from their network is assigned to them. A misconfiguration thus immediately has a contact person, without anyone having to look up addresses.

Mandatory information and automated data harvesters

The imprint is not an ordinary page. It must remain reachable for people, because a block on it creates exactly the violation that a harvester is looking for. At the same time it is the page that carries name and address — and thus the target of automated harvesting.

During the Austrian wave of warning letters over embedded Google fonts, the logs showed that the letters did not originate from individual visits, but from an automated sweep: various unrelated hosts at intervals of milliseconds.

Web RBL solves this by downgrading instead of making exceptions. The canonical path /impressum matches no pattern at all and always stays reachable. Foreign spellings such as /impressum.php — which cannot exist on an Odoo site — are reported, and anyone who does not want to tolerate automated harvesting changes one parameter. sitemap and robots are exempt and fundamentally cannot be blocked: they contain no personal data, and fetching them is the job of every search engine crawler.

Domain and identifier: what is missing from the access log

The access log of werkzeug records neither the host addressed nor the identifier of the caller. Odoo of course receives both — without the host it could not select the right website when there are several. But you cannot look it up afterwards.

Web RBL records both per hit. This answers two questions that cannot otherwise be answered: which website a misconfiguration is addressing — and thus which customer the domain belongs to — and whether an address visits one site or all of them. Several unrelated domains from the same address are the hallmark of the sweep, and they raise the score.

How much the identifier matters was shown by an analysis of one of our websites: over seventy per cent of all traffic there came from a single caller, spread across more than a hundred addresses. Without the identifier it would have looked like a distributed scraper. It was GPTBot, OpenAI's crawler — it kept asking for robots.txt and behaved perfectly correctly.

The security aspects in detail

1. Proxy detection — the most dangerous point of all

If Odoo runs behind a reverse proxy and proxy_mode is set incorrectly, the module sees the same address for every request: that of the proxy. A block would then lock out all visitors at once.

That is why Web RBL checks before every block whether the address determination is reliable. It requires proxy_mode together with a forwarding header, refuses to work if a forward arrives without proxy_mode, and likewise refuses if there is more than one intermediate proxy. When in doubt, nothing is blocked.

2. Never your own networks

Private address ranges, loopback, link-local and CGNAT are permanently excluded. They cannot end up on the list under any circumstances, not even by hand. The same applies to third-party lists: private and reserved ranges as well as absurdly large networks are discarded on import.

3. A fault of its own must not take the website down with it

The entire check runs inside a safeguard that catches every error. Whatever goes wrong here is logged, and the request carries on unchanged. A protection module that takes the website down with it when it has a fault of its own is worse than none at all. In addition, all queries in the request path run without the visitor's browser language and without sorting by translated fields — a visitor with a language that is not installed must not be able to make the check fail.

4. Observe before blocking

After installation, blocking of listed addresses on ordinary requests is switched off. Probes are rejected and recorded, everything else passes through. That way you can watch for a few days who actually ends up on the list before anyone is locked out. The same applies to behaviour detection, threat lists and country rules: report first, block after review.

5. Narrow patterns instead of broad networks

A wrongly identified visitor is more costly than a missed probe. Every pattern therefore carries its level, and it can be changed per pattern. A file extension alone is rarely enough: for .php there are 14,012 genuine probes against a handful of old links, for .asp the ratio is the other way round. Such decisions should be measured, not estimated.

6. A manual release stays in place

Whoever releases an address had a reason. A further hit does not silently override that decision.

7. The list is protected, but not a secret

The endpoint requires a token and answers without a valid token with 404, not with 403 — the existence of the address is thus not even confirmed. The comparison runs in constant time. The list itself contains nothing confidential: it consists of addresses that came knocking on our door of their own accord.

8. No secrets in the records

For failed logins, the username attempted is noted, never the password. For the database manager, nothing at all is read from the submitted values: the master password stands right at the front there, and an entry in a blocklist travels into every database backup. The licence key for GeoLite2 is held only in the system parameter and in no log line.

9. The decoy gives away nothing usable

Optionally, Web RBL answers probes with fabricated content that contains a unique canary value. Whoever retrieves this value later has read the fake response and acted on it — a false alarm is ruled out. The decoys contain no usable details, no real paths and no valid credentials. This function is delivered switched off, because it changes outwardly visible behaviour.

What the module cannot do

It replaces neither a firewall nor a web application firewall. It detects known probing patterns in paths, failed logins and conspicuous behaviour — not attacks on forms, not injection attempts in parameters.

It also sees only what comes back over HTTP. What an attacker does with stolen database or mailbox access remains invisible — and that is precisely why the canary values are paths and not credentials.

And it does not replace diligence: a module that rejects .env probes does not make an accidentally served .env file harmless.

Availability

Web RBL is publicly available under the AGPL-3 and runs in production for us on five websites:

github.com/Alex0176/odoo-web-rbl

The module requires Odoo 19 Community and has no dependencies other than base and web. The ticket integration is optional and requires no particular ticket system; without one, simply nothing happens. The country of origin needs the free GeoLite2 database from MaxMind; if it is missing, the field stays empty and everything else works unchanged.

Further reading: How Web RBL fits into a complete security architecture -- together with firewall, two-factor authentication and permissions, including a step-by-step guide: Combining the Odoo 19 security architecture properly.

We welcome questions, comments and bug reports — preferably as an issue on GitHub.