00.81.00
SweepingSessionStore decorator persists the policy-true
state and enforces retention), the OAuth2 login-CSRF surface is closed
(CallbackStateBinding, CWE-352), empty security annotations now fail the
build (CWE-863), token-propagation audience selection is pinned (CWE-522), and
the wire-codec delegator announced in 00.80.00 is removed. No new module.
The current release is 00.83.00.Release date: 2026-08-31 Previous release: 00.80.00 Maven coordinates (parent):
com.svenruppert.jsentinel:jSentinel-parent:00.81.00Full changelog: GitHub release v00.81.00
A focused hardening release: session records finally age, the highest-priority open audit findings (Tier 1 + Tier 2) are closed, and the wire-codec delegator announced in 00.80.00 is executed. No new module, no new feature surface.
Five themes
- Session-lifecycle integrity — the reported bug “admin views show
weeks-old sessions as ACTIVE” is closed: the new
SweepingSessionStoredecorator persists the policy-true state on every read and implements the retention promise of theSessionStorecontract; a STRICT/PRODUCTION boot now flags store-backed sessions configured without any lifetime enforcement. - OAuth2 login-CSRF (CWE-352) — the REST callback handler binds the
single-use
stateto the user-agent fail-closed via the newCallbackStateBindinghook (__Host-cookie, constant-time compare). - Processor compile guard (CWE-863) — empty security annotations
(
@RequiresPermission({}), …) now fail the build withprocessing/empty-security-annotationinstead of only failing every call at request time. - Propagation selection pinned (CWE-522) — the audience/strategy token-selection chain is verified and pinned by regression tests: no token ever reaches the wrong outbound audience, caches never cross-serve.
- Announced removal — the deprecated
events.rest.EnvelopeWireCodecdelegator is gone, one release afterforRemoval = true, since = "00.80.00".
Headline change
Records used to stay ACTIVE forever unless an admin clicked Revoke. Now you
wrap the store once, and every read shows and persists the policy-true state:
SessionStore store = new SweepingSessionStore(
new InMemorySessionStore(),
new TimeoutSessionPolicy<>(config, clock, audit));
// stale ACTIVE → persisted EXPIRED + one SessionExpired audit event
// terminal records past retention (default 30 days) → purgedAnd the callback side of the OAuth2 login flow now binds state to the
browser before the flow runs:
// start side: bind the state to this browser
response.header("Set-Cookie", CallbackStateBinding.hostCookieHeader(state));
// callback side: fail-closed binding BEFORE the flow runs
new OAuth2CallbackHandler(flow, sink, CallbackStateBinding.hostCookie());What’s new in detail
| Area | Change |
|---|---|
jSentinel-core (session) | SweepingSessionStore decorator: per-record SessionPolicy.evaluate(...) on every read path; stale ACTIVE → persisted EXPIRED + exactly one SessionExpired; terminal records past lastActivityAt + retention (default 30 d) deleted. No background thread. Audit-reason literals got a single home (SessionExpired.REASON_IDLE_TIMEOUT / REASON_ABSOLUTE_LIFETIME). |
jSentinel-dx | New bootstrap finding sessions/no-timeout-policy: ERROR in STRICT/PRODUCTION, INFO in dev modes. |
jSentinel-oauth2-rest | CallbackStateBinding hook (hostCookie(), hostCookieHeader(...) with header-injection guards on value and name, unbound()); OAuth2CallbackHandler evaluates the binding fail-closed before the flow — a rejected callback does not consume the single-use state. |
jSentinel-processor | processing/empty-security-annotation compile ERROR for empty value arrays, blank entries and blank @RequiresPolicy names. |
jSentinel-propagation-oidc | Regression tests: audience travels into the RFC 8693 exchange and scopes the mint; the cache serves the same (subject, audience) only; subjects never share entries; a blank audience omits the parameter. |
jSentinel-events-rest | Deprecated EnvelopeWireCodec delegator and its byte-identity pin test removed; zero references remain reactor-wide. |
Additivity
Additive except three deliberate changes, all on misconfigured or
opted-out setups: the announced events.rest.EnvelopeWireCodec removal
(the authoritative events.wire.EnvelopeWireCodec and the wire format are
unchanged); store-backed sessions without a lifetime policy now raise
sessions/no-timeout-policy (ERROR in STRICT, INFO in dev); and empty security
annotations that previously compiled and failed every call at runtime now fail
the build.
Migrations
EnvelopeWireCodec: replace …events.rest.EnvelopeWireCodec /
…events.rest.EventWireException imports with …events.wire.*;
encode/decode and the wire format are byte-identical. Session stores:
wrap the concrete store once in SweepingSessionStore; admin views need no
change. OAuth2 REST callback: switch to the three-argument
OAuth2CallbackHandler with CallbackStateBinding.hostCookie() and emit
hostCookieHeader(state) on the start side; integrations that bind state
themselves keep the two-argument constructor (now logged once).
Quality
No module fell more than 3 pp under its previous baseline. jSentinel-core
held flat at 84 % (2253/2680); new PIT baselines recorded for
jSentinel-oauth2-rest 86 %, jSentinel-processor 82 %, jSentinel-rest
94 % and jSentinel-vaadin 80 %. Every fix ships a no-mock regression test
against the real implementation (real flow + HttpServer stubs, real stores,
real policies, fixed clocks, compile-testing for the processor). Exit review:
SHIP. Full reactor green. No mocks.
Roadmap
Next: 00.81.10 — the full rebranding jSentinel → jCustos
(eu.jsentinel, jCustos-*), a feature-free cycle, prerequisite being the
Sonatype Central namespace verification for eu.jsentinel. Then 00.82.00 as
the first feature release under the new name.