Aller au contenu principal
Jahia Store
FR

Brute force login protection

community
Télécharger 3.1.2

Informations

Identifiant du module
brute-force-login-protection
Identifiant de groupe
org.jahia.community
Statut
community
Catégorie
Security
Auteur
Florent BOURASSE
Site web du développeur
http://www.jahia.com
Nécessite Jahia
8.2.3.0
Mis à jour
2026-07-09
Source
scm:git:git@github.com:Jahia/brute-force-login-protection.git
Étiquettes
  • security
  • cloud

Detects and blocks brute-force login attempts against a Jahia server, in the spirit of fail2ban. Supports IPv4 and IPv6.

Features

  • Sliding-window detection — count failed logins per IP within a configurable time window (findTime), not just consecutive failures.
  • Per-jail configuration — multiple jails, each with their own thresholds and ban durations.
  • Persistent bans with recidive escalation — bans survive restarts; repeat offenders get progressively longer bans, capped by a configurable maximum.
  • Pluggable failure sources, auth detectors and ban actions — FailureSourceAuthFailureDetector and BanAction SPIs let other modules contribute events, recognise new authentication schemes, or react to bans (block in-process, email, webhook, custom).
  • Broad auth coverage out of the box — form login (and every SSO valve that sets VALVE_RESULT), HTTP Basic, Personal API tokens (Authorization: APIToken …) and the legacy jahiatoken header are all tracked.
  • Built-in actions: in-process block, email notification (throttled), webhook POST signed with HMAC-SHA256 (X-BFLP-Signature header).
  • Cluster-aware — state is shared across Jahia nodes via an embedded Hazelcast instance.
  • Audit log — every ban, unban, and config change is recorded and visible from the UI.
  • React admin UI with tabs for General settings, Jails, Bans, Audit log, and Integrations.

Captures d'écran

Versions

Highlights

This release adds ignore_paths — a way to exempt specific request URLs from
brute-force detection, without weakening ban enforcement. It resolves
false-positive bans caused by non-login traffic that carries a stale or wrong
Authorization header (for example, a client polling a public content endpoint
such as the Jahia store's …/modules-repository.moduleList.json).

Why

The enforcement valve runs at position 0 of the auth pipeline and inspects every
request, not just login submissions. The built-in HTTP Basic / APIToken / jahiatoken
detectors fire whenever an unauthenticated request merely carries the matching
credential header — regardless of the target URL. A misconfigured or buggy client that
repeatedly sends a wrong Authorization header on ordinary (non-login) requests could
therefore accumulate failures and get its IP banned, even though it never attempted a
protected login.

What's new

Added

  • ignore_paths global setting. A comma-separated list of literal, case-sensitive
    URI substrings. If request.getRequestURI() contains any entry, the request is
    exempt from failure detection.
    • Matching is a literal substring test, not a regex — deliberately, so the check
      stays allocation-light and ReDoS-free on the auth hot path, and is robust to Jahia's
      vanity-vs-/cms/render URI rewriting (both forms share a distinctive tail).
    • Only detection is skipped. Ban enforcement (banned-IP short-circuit and
      blocklist) still applies on every path, including ignored ones.
    • Surfaced via the GraphQL saveGlobalSettings(ignorePaths: [String!]) argument, the
      ignorePaths field on bruteForceLoginProtectionGlobalSettings, and a new textarea
      on the General settings tab.
    • Entries are validated at save time (no control characters / CRLF, ≤ 512 characters).

Configuration

In Administration → Server settings → Configuration → Brute force login protection →
General, add one entry per line — or in
org.jahia.community.bruteforceloginprotection.global.cfg:

# Literal, case-sensitive URI substrings exempt from detection (comma-separated) ignore_paths=modules-repository.moduleList.json

 

Note: target a URL that is rendered in place (an API/content endpoint), not a
browser-facing page. A failed auth on the admin UI or a page is internally forwarded to
the login page, so the URI seen at detection time is the login path rather than the
original URL. Machine/content fetches (the intended use case) are not forwarded and
match as expected.

Upgrading

Drop-in from 3.1.x — no schema or configuration migration required. ignore_paths
defaults to empty (no exemptions), preserving existing behavior until you configure it.

Verification

  • Java unit tests: 249/249
  • Cypress E2E: 34/34 (includes dedicated positive coverage for ignore_paths)

Full Changelog: 3_1_1...3_1_2

Nécessite Jahia 8.2.3.0Mis à jour 2026-07-09

3.1.1 is a maintenance release fixing a production-impacting hang during
module updates on clustered installations. No configuration or schema
changes — drop-in replacement for 3.1.0.


Fixed

Module update no longer hangs on stuck Hazelcast partition migrations

On bundle stop the module previously performed a graceful Hazelcast shutdown,
which blocks the OSGi bundle refresh until every partition replica of the leaving
member has been migrated to the rest of the cluster. When those migrations stall
— typically because the module's port (Jahia cluster.hazelcast.bindPort + 2,
e.g. 7862) is firewalled in one direction between nodes, or because the bundle
classloader is being torn down mid-update — the update hangs, with the surviving
node logging in a loop:

Remaining migration tasks in queue => N. (… completedMigrations=0 …)

The module now terminates its dedicated Hazelcast member on deactivation
instead (ADR 0005). This is safe because none of its distributed state is
authoritative-and-irrecoverable:

  • active bans are mirrored to JCR and restored by the startup reconciliation pass
  • failure windows are transient counters that rebuild within one find-time window
  • notification markers at worst allow a single duplicate email/webhook

Documentation

  • New README section "Updating the module in a cluster": recommended
    stop-everywhere → update → start procedure, and the requirement that the
    base+2 Hazelcast port be open in both directions between all nodes.
  • New ADR 0005 recording
    the terminate-on-deactivate decision.

Upgrade notes

If a node is currently stuck mid-update on an older version, it is safe to
restart it: active bans are restored from the JCR mirror when the module starts.

Nécessite Jahia 8.2.3.0Mis à jour 2026-07-02

3.1.0 adds proactive IP blocking on top of the reactive ban system: a static
operator-managed blocklist and a dynamic list of Tor exit nodes, both enforced
at the position-0 authentication valve before any downstream auth work runs.
No breaking changes — existing 3.0.x configuration is untouched.


Static IP blocklist

  • New global setting blocklist_ips: comma-separated IPv4/IPv6 addresses or CIDR
    ranges that are always blocked while the protection service is activated.
  • Entries are validated strictly at save time — a typo is rejected instead of
    silently not blocking.

Tor exit-node blocklist

  • When tor_blocklist_enabled=true, each cluster node periodically downloads the
    TorDNSEL exit-addresses export and blocks matching client IPs.
  • tor_blocklist_url (default https://check.torproject.org/exit-addresses;
    internal mirrors supported) and tor_blocklist_refresh_seconds (default 3600,
    clamped to [300, 604800]).
  • Keep-last-known failure semantics: any fetch failure — including a 200
    response with zero valid entries — keeps the previously fetched list enforced;
    the error and list age are surfaced in the admin UI.
  • The list is per-node in-memory state, so blocklist enforcement keeps working
    even while Hazelcast is unavailable (unlike bans, which fail open per ADR 0002).
  • Strict IP-literal parsing (never DNS), v4-mapped-v6 normalization, 5 MB response
    cap, redirects disabled.

Safety & auditing

  • Whitelisted IPs always take precedence and are never blocked (self-lockout
    safety valve).
  • Blocked attempts are recorded as a new BLOCKED audit event and an INFO log
    line, throttled to one per IP per hour with a hard-capped throttle map — a
    scanner rotating source IPs cannot flood the audit log, the application log,
    or the heap.

Admin UI & GraphQL

  • New Blocklist tab: static list editor, Tor toggle/URL/interval, live
    per-node status (entry counts, last fetch time, list age, last error) and a
    "Fetch now" button.
  • saveGlobalSettings gains blocklistIpstorBlocklistEnabled,
    torBlocklistUrltorBlocklistRefreshSeconds (null = leave unchanged).
  • New blocklistStatus query and refreshTorBlocklist mutation.
  • Everything remains gated by the bruteForceLoginProtectionAdmin permission.

Also in this release

  • Startup reconciliation of JCR-mirrored bans with the Hazelcast map (ADR 0004).
  • testEmail / testWebhook mutations to verify integrations without a live ban.
  • saveGlobalSettings accepts trustedProxyCidrs for multi-CIDR reverse-proxy
    setups.

Upgrade notes

  • Blocklisting requires the global Protection enabled flag, exactly like bans.
  • The Tor fetcher needs outbound access to check.torproject.org (or configure an
    internal mirror). Each node fetches independently on its own schedule.
  • Requires Jahia cluster mode, as before (a cluster of one is fine).
Nécessite Jahia 8.2.3.0Mis à jour 2026-07-02

3.0.0 is a full rewrite of the module to bring it in line with fail2ban
semantics. The 2.x model — one global threshold, "block after N consecutive
failures", JCR-only state, single-node only, form-login only — has been
replaced end-to-end. There is no automatic migration: re-enter your
settings from the admin UI after upgrading.


What changed vs 2.0.2

Detection model

Aspect 2.0.2 3.0.0
Trigger N consecutive failures (default 6) Sliding window: maxRetry failures within findTime
Configuration scope Single global threshold Multiple jails, each with own maxRetry / findTime / banTime / enabled flag
Repeat offenders None — same threshold every time Recidive escalation: progressively longer bans, capped by maxBanTime
Ban duration Until manual unban / cache flush Time-based, automatic unban; bans survive restart
State backend JCR cache Embedded Hazelcast instance, cluster-shared
Counter reset Manual via "Unblock" / "Flush cache" Sliding window expires naturally; manual unban still available

Authentication coverage

2.0.2 only inspected the legacy form-login valve. 3.0.0 tracks every
authenticated request through an ordered chain of detectors:

Order Detector Covers
100 FormLoginFailureDetector LoginEngineAuthValveImpl + every SsoValve subclass (anything that sets VALVE_RESULT)
200 BasicAuthFailureDetector Authorization: Basic …
300 ApiTokenAuthFailureDetector Authorization: APIToken … (personal-api-tokens)
400 JahiaTokenAuthFailureDetector legacy jahiatoken header (TokenAuthValveImpl)

The valve breaks on the first non-null FailureSignal, so each request
records at most one failure. Detector exceptions are isolated — one buggy
custom detector cannot break the chain.

In 2.0.2, Basic auth, Personal API tokens and jahiatoken requests
bypassed the listener entirely. Brute-force against those schemes was
effectively unprotected.

New extension points (SPI)

Three OSGi DS service interfaces let other modules plug in without
patching the core:

public interface AuthFailureDetector {       // recognise a new auth scheme
    FailureSignal detect(AuthFailureContext context);
    default int order() { return 500; }
}

public interface FailureSource { /* contribute failure events */ }
public interface BanAction     { /* react when an IP is banned */ }

Built-in BanActions shipped: in-process block, throttled email
notification, signed webhook POST. No script/exec action is provided
or supported
 — by design.

See README → Extending — adding a new auth-failure detector for a
worked example.

Storage & cluster

  • OSGi ConfigurationAdmin is now the source of truth for global
    settings and jail definitions. They live as .cfg files under
    <jahia-var>/karaf/etc/. Bans and the audit log remain JCR-backed
    (runtime state, not configuration).
  • Embedded Hazelcast replaces the JCR cache for tracker state and
    ban distribution across nodes.
  • Audit entries are auto-bucketed by yyyy/MM/dd under the audit-log
    container using jmix:autoSplitFolders, so the JCR fan-out stays
    bounded on busy installs.

Cluster hardening

  • Per-install group password. On first start the module generates a
    random 32-byte secret in
    <jahia-var>/karaf/etc/bflp-cluster-secret.properties (mode rw-------
    where supported). Override with -Dbflp.cluster.password=….

  • Mutual-TLS between members is opt-in:

    Property Purpose
    bflp.cluster.keystore Path to keystore (.jks / .p12)
    bflp.cluster.keystorePassword Keystore password
    bflp.cluster.truststore Path to truststore
    bflp.cluster.truststorePassword Truststore password

    Without these, cluster traffic between Hazelcast members is plaintext.

Network input hardening

  • Trusted-proxy gating. In 2.0.2 a single trustProxyHeader flag was
    enough to honour X-Forwarded-For — meaning any direct client that
    set the header bypassed CIDR filtering. In 3.0.0 the flag is necessary
    but no longer sufficient: the remote socket address must also match
    one of the CIDR entries in trustedProxyCidrs. A one-time warning is
    logged if the flag is on but the list is empty.
  • Signed webhooks. The webhook integration POSTs an X-BFLP-Signature
    HMAC-SHA256 header derived from the configured secret.
    URL validation (server-side) rejects private / loopback / link-local /
    multicast targets and the AWS metadata IP (169.254.169.254). Plain
    http:// receivers are opt-in only via -Dbflp.webhook.allowHttp=true.
  • Webhook secret at rest. Operator-pasted plaintext is re-encrypted
    on save ({enc} prefix). The GraphQL mutation honours a tri-state
    contract: null keeps the stored secret, "" clears it, anything else
    replaces it.

Hazelcast deserialization safety

The embedded Hazelcast registers a GlobalSerializerConfig with an
explicit allowlist of classes that may be deserialized from cluster
traffic. Unknown types are rejected. This addresses the classic
"deserialize anything off the wire" class of vulnerabilities that comes
with naive Hazelcast embedding.

Bearer tokens never leak into the audit log

Detectors that match on bearer-style headers (APITokenjahiatoken)
deliberately do not populate FailureSignal.username — the token
value is a secret, and username is rendered in the audit UI.

Admin UI

The legacy "Service status + threshold + CIDR whitelist + Tracked IPs"
screen is gone. The new React admin UI has five tabs:

  • General — protection on/off, IP whitelist (CIDR), username
    ignore patterns, trustProxyHeader + trustedProxyCidrs, recidive
    factor, max ban time, audit log size.
  • Jails — create / edit / delete jails (enabledmaxRetry,
    findTimeSecondsbanTimeSeconds).
  • Bans — currently-banned IPs; manual ban / unban.
  • Audit — paginated event browser; clear log.
  • Integrations — email recipient, webhook URL + secret, Test
    buttons
     for both. Webhook payload is Slack-compatible.

GraphQL API

The 2.x endpoint bruteForceLoginProtectionSettings with fields
nb_failed_login_max / time_to_idle has been removed. Use:

  • bruteForceLoginProtectionGlobalSettings for the global config.
  • Jail queries / mutations for per-jail thresholds.
  • Audit, bans and integration mutations as exposed in the new schema.

There is no compatibility shim — clients of the 2.x GraphQL API must be
updated.


Breaking changes — at a glance

  1. No automatic migration from 2.0.2 — re-enter settings after upgrade.
  2. JCR schema rewritten: old nb_failed_login_max / time_to_idle
    nodes are no longer read.
  3. GraphQL schema rewrittenbruteForceLoginProtectionSettings is
    gone; use bruteForceLoginProtectionGlobalSettings + jails.
  4. Settings moved out of JCR into OSGi ConfigurationAdmin
    (<jahia-var>/karaf/etc/). Existing v3 snapshot installs must also
    re-enter their settings.
  5. trustProxyHeader alone no longer trusts X-Forwarded-For —
    trustedProxyCidrs must list the legitimate front proxies.
  6. Bearer tokens are never recorded as usernames in audit events.
  7. Hazelcast Import-Package is now required (see Prerequisites).

Prerequisites (new in 3.0.0)

Jahia cluster mode must be enabled — the bundle imports
com.hazelcast.* packages that Jahia only exports when clustering is
active.

  • Tarball/installer: cluster.activated=true in
    digital-factory-config/jahia/jahia.node.properties, then restart.
  • Docker image: CLUSTER_ENABLED=true.

A single-node "cluster of one" is fine for dev/test. Without cluster
mode the bundle will fail to resolve with:

Unable to resolve brute-force-login-protection:
  missing requirement osgi.wiring.package=com.hazelcast.core

In a real cluster, OSGi ConfigurationAdmin storage is per-node — rely
on Karaf Cellar (shipped with Jahia clustering) to replicate
karaf/etc/, or mount a shared digital-factory-data/karaf/etc/. The
module itself does not broadcast settings over Hazelcast.


Operational notes for upgraders

  • Plan a maintenance window. Bans, settings and the GraphQL schema
    all change shape; you'll need to re-enter configuration once the new
    bundle is deployed.
  • Verify cluster mode is on before deploying — see the Prerequisites
    block above. A bundle that fails to resolve is the most common 3.0.0
    upgrade failure.
  • Audit-log receivers: if you scraped the old audit nodes directly
    from JCR, expect a different node structure (date-bucketed under
    yyyy/MM/dd).
  • Webhook receivers: verify the X-BFLP-Signature HMAC-SHA256
    header with a constant-time comparator
    (MessageDigest.isEqual in Java, hmac.compare_digest in Python,
    crypto.timingSafeEqual in Node.js).

Full Changelog2_0_2...3_0_0

Nécessite Jahia 8.2.1.0Mis à jour 2026-05-23

Highlights

  • Security hardening of the brute-force login protection valve to close bypass paths.
  • Accessibility overhaul of the admin UI to meet WCAG 2.1 AA (with AAA contrast on the refresh button).
  • Cache management fix for EhCache heap-pool configurations.
  • Dependency hygiene: Dependabot enabled, CVE/alert upgrades applied.
  • Repo metadata refresh (AGENTS.md, SECURITY.md, CLAUDE.md reference).

Features

  • Add Dependabot configuration file (cd6fccb).

Fixes

Security

  • Harden brute-force login protection against bypass and related issues (d5f0680).
  • Upgrade systeminformation 5.31.5 -> 5.31.6 via resolution to address CVE (5c11079).
  • Upgrade @babel/plugin-transform-modules-systemjs to fix Dependabot alerts (3705522).

Caching

  • Skip per-cache entry cap when the EhCache manager uses a heap pool (7ef4449).

Accessibility

  • Resolve all WCAG 2.1 AA accessibility issues in admin UI (44087df).
  • Resolve WCAG 1.3.1 / 3.3.2 empty-label checks on toggle checkbox (da53627), later reverted (ce797b0).
  • Resolve remaining WCAG 2.1 AA audit issues in brute-force-login admin UI (e54b328).
  • Darken refresh button color for AAA contrast (3c3e051).

Refactoring

  • No standalone refactors; UI rework occurred alongside accessibility fixes in BruteForceLoginProtection.jsx / .scss and valve/cache hardening in Java sources.

Tests

  • tests/package.json and tests/yarn.lock updated alongside dependency upgrades; no new test commits.

Docs / Meta

  • Rename CLAUDE.md to AGENTS.md and add CLAUDE.md reference (a0981f8).
  • Streamline SECURITY.md content (f1f73be).

Full Changelog2_0_1...2_0_2

Nécessite Jahia 8.2.1.0Mis à jour 2026-05-19
Nécessite Jahia 8.2.1.0Mis à jour 2026-04-18