1.9.x Release Notes
This page summarizes notable changes in the 1.9 minor series. Patch-level releases are listed below.
1.9.12
Date: 2025-10-15
Build and packaging changes focusing on optional Hydra/OIDC integration.
Highlights:
- Hydra/OIDC is now gated behind a Go build tag: hydra. The default build (no tags) excludes Hydra, OAuth2/OIDC flows, and their dependencies.
- 2FA (TOTP) and WebAuthn frontend flows are compiled and registered only with the hydra build tag.
- Docker images are built without Hydra by default. To include Hydra, build your own image with BUILD_TAGS=hydra.
- OAuth2/Claims processing has been compile-time isolated. Non-hydra builds remain lean and keep JWT-based service authentication fully intact.
How to build:
- Without Hydra (default):
- go build -mod=vendor ./server
- go test ./...
- With Hydra enabled:
- go build -mod=vendor -tags hydra ./server
- go test -tags hydra ./...
Docker build examples:
- Default (no Hydra):
- docker build -t nauthilus:1.9.12 .
- With Hydra enabled:
- docker build --build-arg BUILD_TAGS=hydra -t nauthilus:1.9.12-hydra .
Operational impact:
- Endpoints for login/consent/device/webauthn and registration are only present in hydra builds. In non-hydra builds these endpoints are not registered (or return 404 via stubs).
- Configuration under the oauth2 section is only effective when built with hydra. It is ignored otherwise.
- Service-internal JWT authentication for REST/Admin endpoints is unaffected and always available.
Documentation updates:
- See Installation → Compiling for build-tag guidance.
- See Installation → Docker for Hydra-enabled image builds.
1.9.10
Date: 2025-09-29
Cold-start grace for brute-force (precision, multi-instance safe)
A new mechanism prevents immediate lockouts right after cache flushes or password changes when multiple devices may still send the old password in parallel. The system grants a one-time, per-IP grace for known accounts without negative password history while atomically seeding evidence so that Repeating‑Wrong‑Password can short-circuit concurrent first attempts without inflating generic buckets.
Highlights:
- One-time grace per scoped IP, then normal enforcement resumes within the configured TTL.
- Atomic Redis Lua script seeds a per-password key alongside the grace key to eliminate races in parallel scenarios.
- Safe in multi-instance deployments via shared Redis and EVALSHA script management.
New configuration (disabled by default):
brute_force:
cold_start_grace_enabled: true # Enable one-time grace for known accounts without negative PW history
cold_start_grace_ttl: 120s # Grace window (recommended 60–180s)
Behavior:
- First failed attempt from an IP (scoped consistently with RWP and IPv6 ip_scoping): learn metadata, do not enforce yet.
- Concurrent first attempts are recognized via the atomic seed and treated as repeating wrong password.
- From the next attempt within the TTL: enforcement resumes so complex buckets fill/trigger as usual.
Documentation:
- Configuration reference updated: /docs/configuration/brute-force#brute_forcecold_start_grace_enabled-v1910
1.9.9
Date: 2025-09-24
Highlights:
- Brotli response compression middleware, aligned with existing zstd middleware semantics (skipper, exclusions, min_length).
- Brotli request decompression: Requests with
Content-Encoding: brare automatically decompressed when compression is enabled. - Algorithm selection by preference order now supports
brin addition tozstdandgzip. - Configuration options extended with
level_brotlito control Brotli compression strength. - Deprecated
server.compression.level(gzip) in favor ofserver.compression.level_gzipfor consistency.
Configuration changes:
server.compression.algorithmsmay now include"br"(Brotli). Example:
server:
compression:
enabled: true
algorithms: ["br", "zstd", "gzip"]
- New:
server.compression.level_brotli(0..3) maps to middleware levels similar to zstd:- 0 = DefaultCompression
- 1 = BestSpeed
- 2 = BetterCompression
- 3 = BestCompression
server:
compression:
enabled: true
algorithms: ["br", "zstd", "gzip"]
level_brotli: 2
- Deprecated:
server.compression.level→ Useserver.compression.level_gzipinstead (still accepted for backward compatibility).
server:
compression:
# Deprecated, prefer level_gzip
level: 6
# New
level_gzip: 6
- Existing:
level_zstd(0..3) andmin_lengthremain available and unchanged.
For the full description of options, see the Server Configuration: /docs/configuration/server-configuration#compression-configuration
Behavior:
- Only one response compression algorithm is active at a time, chosen by the configured
algorithmsorder and the client’sAccept-Encoding. - Request decompression now covers gzip, zstd (zstd/zst/zstandard), and Brotli (br) when compression is enabled.
- The
/metricsendpoint continues to disable response compression to avoid double compression by Prometheus.
Implementation notes:
- Brotli compression implemented using
github.com/andybalholm/brotli. - Zstandard compression implemented using
github.com/klauspost/compress/zstd.
Migration notes:
- If you previously set
server.compression.level, migrate toserver.compression.level_gzip. - To enable Brotli for compatible clients, add
"br"toserver.compression.algorithmsand optionally configurelevel_brotli.
Thanks: Thanks to all contributors and users for feedback leading to this improvement in performance and bandwidth efficiency.
1.9.8
Date: 2025-09-23
Highlights:
- Zstandard (zstd) response compression via a new middleware (analog to gin-contrib/gzip).
- Zstandard request decompression: incoming requests with Content-Encoding: zstd/zst/zstandard are automatically decompressed when compression is enabled.
- Configurable algorithm preference and zstd levels via new configuration keys.
Details:
- New config keys under server.compression:
- algorithms: ordered list of enabled algorithms (e.g., ["zstd", "gzip"]). Only one algorithm is applied per response; the first supported by the client wins.
- level_zstd: integer mapping for zstd encoder levels (0=DefaultCompression, 1=BestSpeed, 2=BetterCompression, 3=BestCompression).
- min_length: remains supported to avoid compressing very small responses.
- Gzip keeps using the existing level (1..9). The deprecated content_types remains ignored (since 1.9.2) and should be removed from configs.
- Implementation uses klauspost/compress v1.18.0 for zstd.
- The /metrics endpoint keeps compression disabled to avoid double compression with Prometheus.
Docs:
- Configuration reference updated with algorithms, level_zstd, and examples.
- Full configuration example updated accordingly.
1.9.5
Date: 2025-09-11
Breaking change (JSON REST): protocol replaces service
- The JSON authentication endpoint (
POST /api/v1/auth/json) now uses the fieldprotocol(e.g.,"imap","smtp","pop3","http") to determine the application protocol. - The JSON field
serviceno longer controls protocol selection and is ignored by this endpoint. - Header-based (
/api/v1/auth/header) and form-based (application/x-www-form-urlencoded) endpoints are unchanged.
Migration for JSON clients:
- Replace
"service": "imap"with"protocol": "imap"in request payloads. - If you previously relied on
serviceto switch behavior, make sure to provideprotocolexplicitly. If omitted, Nauthilus will apply internal defaults, which may not match your intended workflow.
Documentation updates:
- REST API documentation updated to reflect the
protocolfield and to highlight this change.
1.9.4
Date: 2025-09-11
IPv6 ip_scoping for Repeating‑Wrong‑Password and Tolerations
Two new configuration options allow aggregating IPv6 addresses by network rather than strict /128 in contexts where privacy extensions commonly rotate interface identifiers. This improves stability of detection for IPv6 households and multi-device environments.
Added under brute_force.ip_scoping:
rwp_ipv6_cidr— Applies to Repeating‑Wrong‑Password (PW_HIST) lookups and counters.tolerations_ipv6_cidr— Applies to Tolerations keys and housekeeping.
Behavior:
- When set to a value > 0 (range 1..128), IPv6 addresses are normalized to the configured network CIDR (e.g., 2001:db8::/64) for the respective subsystem. IPv4 behavior is unchanged.
- Defaults to
0(disabled) to keep legacy behavior (/128) unless explicitly enabled.
Example:
brute_force:
ip_scoping:
rwp_ipv6_cidr: 64
tolerations_ipv6_cidr: 64
Related changes:
- Cache flush is aware of ip_scoping:
/api/v1/cache/flushnow removes both raw (/128) and CIDR-scoped variants of keys so that no scoped keys linger in Redis after a user flush.
Stability fix for Repeating‑Wrong‑Password after cache flush
A logic fix prevents immediate brute-force bucket increments when the negative password history is empty for a known account (e.g., right after a cache flush or first failed attempt). In this situation, the system now gracefully learns client IP metadata (protocol/OIDC) and avoids enforcement that could lead to self-lockout.
Notes:
- This change only affects the path where the account exists but no negative password history is present.
- Normal brute-force detection and enforcement remain unchanged in other scenarios.
New option: brute_force.pw_history_for_known_accounts
Reduce PW_HIST write amplification for already‑blocked (cached) requests. When enabled, Nauthilus only writes account‑scoped PW_HIST for verified accounts (no fallback to raw usernames). IP‑wide PW_HIST remains unchanged.
Why this can be useful:
- Significantly reduces Redis key footprint and write load during credential‑stuffing with rotating/non‑existent usernames.
Minimal risk:
- After unlock or cache flush, the missing per‑username PW_HIST for unknown usernames removes a small quick‑skip signal for "repeating wrong password". Buckets may increment a bit sooner until context is re‑learned; metrics/forensics for such usernames are less granular.
Config:
brute_force:
pw_history_for_known_accounts: true
Documentation
- Configuration reference updated to include
brute_force.ip_scopingoptions and examples. - Full configuration example extended with
ip_scoping.
1.9.3
Maintenance and minor improvements.
1.9.2
Maintenance and minor improvements.
1.9.1
Maintenance and minor improvements.
1.9.0
Initial 1.9 release.