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

00.82.00

Hardening the residuals. The security findings earlier releases documented as accepted residuals rather than fixed — four of them closed here, each with a test that fails when the fix is removed: unprotected REST handlers reported at startup (CWE-862), relying-party vetting of the logout redirect (CWE-601), time-bounded HTTP body reads (CWE-400), constant-cost password verification during lazy migration (CWE-208). Additive throughout. The current release is 00.83.00.

Release date: 2026-09-01 Previous release: 00.81.20 Maven coordinates (parent): eu.jsentinel:jCustos-community-parent:00.82.00 Full changelog: GitHub release v00.82.00

Additive throughout — no API was broken, and nothing changes behaviour until you opt in.

What was closed

Unprotected REST handlers reported at startup (CWE-862)

Deny-by-default already refused a handler carrying no security annotation, but a REST application only found out when a request arrived — Vaadin reports it at startup because its router can enumerate routes; REST has no such registry. RestHandlerDiscovery closes it; the application names its handler classes, since it is the only party that knows them:

RestSecurity.bootstrap()
    .discoverHandlers(new ClassScanningRestHandlerDiscovery(DocumentHandlers.class))
    .mode(JCustosBootstrapMode.STRICT)
    .install();

Three findings keep “could not check” distinct from “checked and clean”: deny-by-default/unannotated-handler (unreachable), …/discovery-unavailable (the check could not run) and …/discovery-disabled (no enumeration configured).

The relying party can vet the logout redirect (CWE-601)

post_logout_redirect_uri was forwarded to the provider unchecked. When the URI comes from the request, an attacker picks where the user lands:

new RpInitiatedLogoutInitiator(
    PostLogoutRedirectValidator.allowOnly(URI.create("https://app.example.com/bye")));

allowOnly compares scheme, host, port and path exactly — a prefix check on https://app.example.com also accepts https://app.example.com.attacker.test. A rejected URI throws rather than being dropped. The no-arg constructor is unchanged, so upgrading changes nothing until you pass a validator.

HTTP body reads are bounded in time, not just in size (CWE-400)

Every HTTP client used ofInputStream() then readNBytes(MAX + 1). The size cap worked; the time bound did not — HttpRequest.timeout covers the exchange up to the response headers, and the body was read afterwards, outside any deadline. BoundedHttpBody hands send() a materialising, limiting subscriber, so the request timeout covers the whole exchange. Seven clients carried the pattern: JWKS, token endpoint, OAuth2 form post, OIDC discovery, userinfo, the propagation token client and the HIBP checker.

Every password verification costs the same (CWE-208)

The dummy KDF ran the preferred algorithm, so a stored hash on a different one verified at a different cost — measurable from outside during a lazy migration, separating “existing but unmigrated” from “no such user”. A verification against a non-preferred envelope now runs an additional preferred-cost dummy KDF; the cost is one extra KDF per unmigrated account and disappears as the migration completes.

Also in this release

  • Full-backup export — EclipseStoreJCustosStorage.issueFullBackup(Path) writes a bootable copy of the live storage while it keeps serving.
  • Javadoc references repaired: two @links pointed at packages not on their module’s classpath and failed javadoc generation during the 00.81.20 build.

Deferred

CSRF / web-adapter hardening stays open. Login-CSRF is covered (CallbackStateBinding, 00.81.00), but general CSRF protection needs a concept rather than a patch: it only affects cookie-based authentication, while the documented jCustos REST path uses bearer tokens, where a CSRF token would be ceremony without effect. Half a CSRF defence is worse than none.

Verification

Community 6600+ tests, enterprise 474 — no failures. Every security fix has a test that was confirmed to fail when the fix is disabled; a test that never goes red proves nothing. No mocks.

Upgrade

Drop-in. No API was broken and no default changed.