Skip to content
Flag of Europe
Made in the European Union · Independently built · Released under EUPL 1.2

00.79.41

Latest release — available on Maven Central. The third security-hardening tick on the 00.79 line: 21 findings from a workflow-backed 2026-07-05 audit (1 Medium, 16 Low, 3 Info; 0 Critical, 0 High). The through-line is consistency drift — hardening patterns already applied in one place, now carried to every sibling, with three new shared homes so the pattern can’t drift again. No new module.

Release date: 2026-07-06 Previous release: 00.79.40 Maven coordinates (parent): com.svenruppert.jsentinel:jSentinel-parent:00.79.41 Full changelog: GitHub release v00.79.41

A multi-angle adversarial source-review (explicitly excluding the 38 findings already closed in 00.79.30 + 00.79.40) confirmed 20 distinct findings; a completeness critic flagged a further coverage gap that was verified and promoted to a 21st, Medium-severity finding.

Two Medium findings

  • JS-SEC-039 (CWE-290) — the GitHub identity mapper anchored the subject to the mutable login; it now anchors to the immutable numeric account id (github#12345), so a renamed or recycled GitHub login can no longer inherit another user’s grants.
  • JS-SEC-059 (CWE-287/294) — the OIDC nonce was computed and sent to the OP, then discarded — unreachable at callback, so the replay/login-CSRF defence couldn’t be enforced. AuthorizationCodeFlow.handleCallback now returns a CallbackResult carrying the stored nonce and resumeTarget back to the caller; both callback adapters (REST, Vaadin) were threaded through.

Sixteen Low + three Info findings

  • Identity & authorization — class-level restriction annotations (@RequiresRole and five siblings) are now @Inherited, so a subclass of a protected base is no longer silently unprotected (JS-SEC-040); the Keycloak roles mapper defaults to client-scoped, so a foreign client’s admin role no longer becomes an unqualified admin (JS-SEC-042); back-channel-logout replay detection anchors on first-seen wall-clock, not the token’s own iat (JS-SEC-041); token-introspection aud tolerates both string and array form (JS-SEC-043); only forwardable token types (BearerToken, OidcAccessToken) are ever propagated as an OAuth2 subject token (JS-SEC-044); HIBP enforces https-or-loopback and fails closed on an empty response (JS-SEC-046).
  • Log injection & secret hygiene — one shared LogFieldScrubber replaces three divergent copies and reaches a sink that previously had none (JS-SEC-045); PasswordNormalizer zeroes its pass-through buffer in a finally (JS-SEC-047).
  • Resource bounds — login-attempt, nonce, replay and dead-letter stores are capacity-bounded with eviction that never drops an in-force lockout (JS-SEC-048/050/051/052); the JWS validator caps compact-token input before decoding, matching the JWE decoder (JS-SEC-053); ConsumePipeline.verify() is now total (fails closed instead of throwing) (JS-SEC-054); the SSE handler caps concurrent streams with a 503 + Retry-After instead of starving the server (JS-SEC-049); an envelope-store cursor guards against Long.MAX_VALUE overflow (JS-SEC-058).
  • DX honesty & STRICT consistency — the recorded-not-wired rate-limit/API-key/refresh-token diagnostics stop over-claiming and now fail STRICT boots (JS-SEC-055); a public OAuth2 client without PKCE and a disabled OIDC nonce become STRICT/PRODUCTION boot gates, consistent with the existing JWKS-https gate (JS-SEC-056); the one remaining un-migrated token service (StoreBackedRememberMeService) now takes a TokenHasher (JS-SEC-057).

Three shared homes established (anti-drift)

  • CapacityBound (jSentinel-core) — the single home for the bound value and guard used by every capacity-limited store.
  • LogFieldScrubber (jSentinel-core) — control-char and space neutralisation, one implementation for every log sink.
  • JoseLimits (jSentinel-jwt, package-private) — the shared compact-token size ceiling for the JWT validator and both JWE paths.

Additivity

Three deliberate exceptions, all on @ExperimentalJSentinelApi surfaces or STRICT-only: the handleCallback return-type change (JS-SEC-059, above); IntrospectionResult.audience() changes from Optional<String> to Set<String> to preserve an array-form aud (JS-SEC-043); and three previously-silent conditions now fail the STRICT/PRODUCTION boot (recorded- not-wired rate-limit/API-keys/refresh-tokens, public-client-without-PKCE, disabled-nonce) — DEVELOPMENT / COMMUNITY_DEFAULTS keep them as WARNING/INFO, so only an app that deliberately opted out of a secure default under STRICT is affected. Everything else is additive.

Exit production-review

A workflow-backed exit review over the full 21-finding delta (47 files) returned SHIP — no high/critical finding on any delivered path across all seven checked high-risk interaction areas. Three low, non-blocking notes were folded in-cycle (a more precise HIBP failure label; a documented eviction tradeoff; confirmation that the brute-force lockout invariant holds under flood).

What it does not do

Three of four audit coverage gaps stay a scoped follow-up — none judged an as-shipped exploitable bug: REST callback CSRF default, propagation-core audience binding, security-annotation-processor coverage, RP-initiated-logout Location validation. No new feature module; 00.80.00 stays feature-reserved.

Quality

jSentinel-core (the most-touched module) re-measured at 84 % (2230/2658) mutation / 89 % line — +329 absolute kills versus the 00.71 baseline. Untouched modules retain their 00.79.40 baseline by construction. Full library-reactor clean install green; every finding carries a no-mock regression test (~1000 insertions across 47 files). No mocks.

Migration

None. Just bump the version to 00.79.41.