Skip to content

feat(bff): add stateless cookie session variant with AES-GCM sealing - #118

Merged
cuioss-oliver merged 24 commits into
mainfrom
feature/plan-07a-bff-cookie-crypto-core
Jul 28, 2026
Merged

feat(bff): add stateless cookie session variant with AES-GCM sealing#118
cuioss-oliver merged 24 commits into
mainfrom
feature/plan-07a-bff-cookie-crypto-core

Conversation

@cuioss-oliver

@cuioss-oliver cuioss-oliver commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

Implements the cryptographic half of the stateless session.mode: cookie BFF
variant (doc/plan/08-bff-cookie.adoc) as a delta on the landed PLAN-06
server-session runtime, not as a greenfield build. AES-256-GCM sealed session
cookies replace server-side InMemorySessionStore state for this mode, with
previous_key rotation, dual key-material sourcing (passed key or
generate-on-startup), a mode-neutral session-binding seam so
SessionAuthenticationStage / TokenRefreshCoordinator no longer hard-bind
SessionStore, and a 404 mode gate on the existing back-channel-logout
endpoint in cookie mode.

Changes

  • D1 — AES-256-GCM sealed session cookie. New bff/cookie/ package:
    CookieKeyMaterial, SealedSessionCookieCodec, SealedSessionPayload,
    CookieSessionBinding seal/unseal the token material PLAN-06's server
    variant kept in InMemorySessionStore.
  • D2 — previous_key rotation. Decrypt-with-previous / encrypt-with-current
    rollover, composing only with the passed-key mode
    (config/model/OidcConfig.java, CookieKeyMaterial).
  • D2b — Two first-class key-material modes. Passed key via
    encryption_key / previous_key ${ENV_VAR} refs, or generate-on-startup
    (auto-generated AES-256-GCM key). config/validation/ConfigValidator.java
    relaxed so cookie mode with no key configured defaults to
    generate-on-startup instead of failing boot, with a companion rule that
    keeps previous_key-without-encryption_key invalid. Reference docs for
    the new/changed keys, including the generate-on-startup caveats (session
    loss on restart, not shareable across replicas), ship in
    doc/configuration.adoc.
  • D3 — Mode-neutral single-flight refresh. bff/refresh/TokenRefreshCoordinator.java
    re-expressed against the mode-neutral SessionBinding seam (D7) so its
    existing single-flight coalescing and reuse-as-failure/session-destruction
    rules apply uniformly to cookie-sealed state.
  • D4 — Back-channel logout gated 404 in cookie mode. bff/reserved/BackchannelLogoutEndpoint.java
    and bff/logout/BackchannelLogoutReceiver.java mode-gated; unreachable
    pre-login handshake state stays server-side (see Known residual below).
  • D6 — Cookie-mode integration test suites. BffCookieActivationWiringTest,
    BffCookieSessionIT, BffCookieStatelessnessIT,
    BffCookieBackchannelDisabledIT plus BffKeycloakLoginFlow support code.
    Per-variant benchmark additions are explicitly DEFERRED — see obligation 2
    below.
  • D7 — Mode-neutral SessionBinding seam. New bff/session/SessionBinding.java
    interface with ServerSessionBinding (PLAN-06 store-backed) and
    CookieSessionBinding (new, stateless) implementations, replacing the
    direct SessionStore binding in SessionAuthenticationStage and
    TokenRefreshCoordinator. Re-scoped into this plan at outline time per the
    spec's pre-authorization once the CENTRAL RISK resolved to the store-shaped
    branch — see obligation 7 below.

Operator-approved decisions and deliberate omissions (auditable record)

  1. Compose crossing (operator-approved, additive-only). Adds two
    cookie-mode services to integration-tests/docker-compose.yml
    (api-sheriff-cookie on 10445/19002 and api-sheriff-cookie-2 on
    10446/19003) plus a readiness wait in
    integration-tests/scripts/start-integration-container.sh.
    docker-compose.yml is PLAN-25's declared surface under
    parallelization_scope: 2. The crossing is additive only — no new
    network, no new volume, no edit to any existing service, no change to
    compose networking. Taken because mandatory lesson 2026-07-25-15-001
    requires at least one IT that actually activates session.mode: cookie
    end to end, and the second instance is what makes the stateless
    cross-process proof real rather than asserted.
  2. Benchmark deferral. The spec's deliverable 6 called for per-variant
    benchmark additions. They are DEFERRED out of this plan because
    benchmarks/** is PLAN-25's surface. Stating the deferral explicitly so
    the omission is auditable rather than silent.
  3. Doc-layer split. Only layer (a) reference documentation ships here
    (doc/configuration.adoc and doc/LogMessages.adoc). The operator guide
    and developer guide are PLAN-07B's deliverable 7 — a deliberate split,
    not an omission. Also deferring the update of
    doc/variants/03-bff-cookie.adoc:154,177 (which still states the
    now-removed "required session.encryption_key") to PLAN-07B.
  4. Known residual — PendingAuthorizationStore. Pre-login handshake
    state is still server-side and in-memory, so in cookie mode a login
    started on one instance cannot complete on another. This is an accepted,
    documented residual of the variant, not a defect fixed here. The sealed
    pre-login-state delta was deliberately not taken.
  5. Accepted formatter output. The mangled @Nullable record-component
    layout in BffRuntime.java (ReservedHttpRequest, ReservedHttpResponse)
    is deliberately accepted and recorded in-code rather than fixed, because
    no formatter plugin is declared in either POM (inherited from
    de.cuioss:cui-java-parent) and adding one would be an unapproved
    build-config change.
  6. In-scope security fix beyond the original spec (operator-approved).
    Finding 0ced30: GatewayEdgeRoute.readReservedBodyThenDispatch buffered
    the full request body of the two unauthenticated reserved POST paths with
    no byte cap, before the pipeline stages that would bound it — an
    unauthenticated memory-exhaustion vector. Pre-existing (PLAN-06's
    reserved-path surface), not introduced by this plan, fixed here under the
    Boy Scout rule because this plan already edits that file. The fix caps
    reserved-path bodies at 16 KiB derived from EdgeHardeningOptions,
    enforced twice (Content-Length pre-check + streaming byte counter),
    rejecting with 413 and WARN ApiSheriff-109.
  7. Scope note — the seam refactor. Deliverable 7 (the mode-neutral
    SessionBinding seam) was added to this plan at outline time after the
    spec's CENTRAL RISK resolved to the store-shaped branch: the landed
    PLAN-06 session seam bound SessionStore directly in seven consumers plus
    the producer, so a stateless variant was not expressible without it. The
    spec pre-authorised exactly this re-scope.

Test Plan

  • Three cookie IT suites (BffCookieActivationWiringTest,
    BffCookieSessionIT, BffCookieStatelessnessIT,
    BffCookieBackchannelDisabledIT) plus both cookie-mode compose
    instances verified GREEN locally against a freshly built native image
    (75/75 integration tests pass, no pre-existing suite regressed) before
    opening this PR.
  • CI quality gate / full verify passed
  • Manual testing completed (if applicable)

Related Issues

N/A


Generated by plan-finalize skill

Summary by CodeRabbit

  • New Features
    • Added stateless cookie-mode sessions using tamper-resistant sealed cookies (AES-GCM), supporting optional key rotation and cross-instance portability.
    • End-to-end cookie-mode handling now unifies login/refresh/logout/user-info via a single session-binding flow and properly emits multiple Set-Cookie headers when needed.
  • Bug Fixes
    • Oversized reserved-path POST bodies now fail with HTTP 413 (including connection retirement).
    • Capability-gated back-channel logout returns HTTP 404; invalid/tampered/expired cookie sessions fail safely.
  • Documentation
    • Updated configuration and structured log documentation for cookie-mode encryption_key/previous_key behavior and max_cookie_size limits.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @cuioss-oliver, your pull request is larger than the review limit of 150000 diff characters

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The pull request adds stateless AES-GCM cookie sessions, key rotation, and runtime wiring through SessionBinding. It also adds cookie-size and reserved-body limits, capability-gated back-channel logout, configuration and logging updates, boot fixtures, container wiring, and unit and integration coverage.

Changes

Cookie-mode BFF session flow

Layer / File(s) Summary
Cookie payload, sealing, and key rotation
api-sheriff/src/main/java/.../bff/cookie/*, api-sheriff/src/test/java/.../bff/cookie/*
Adds sealed payload encoding, AES-GCM sealing/unsealing, generated or configured AES-256 keys, decrypt-only previous-key rotation, size budgets, TTL handling, redaction, and tests.
Session-binding seam and runtime integration
api-sheriff/src/main/java/.../bff/session/*, .../bff/cookie/CookieSessionBinding.java, .../bff/runtime/*, .../bff/reserved/*, .../bff/refresh/*
Introduces SessionBinding, adds server and cookie implementations, and rewires authentication, refresh, callback, login, logout, user-info, and back-channel logout flows through the binding.
Cookie limits and reserved-body enforcement
api-sheriff/src/main/java/.../config/*, .../edge/*, .../pipeline/*, related tests
Adds max_cookie_size, cookie-header validation exceptions, reserved POST body ceilings with HTTP 413 handling, and validation tests.
Boot, documentation, and integration coverage
api-sheriff/src/test/resources/config/cookieboot/*, doc/*, integration-tests/*, api-sheriff/pom.xml
Adds cookie-mode boot fixtures, structured log and configuration documentation, two cookie gateway services, readiness/logging support, and end-to-end statelessness and logout tests.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • cuioss/API-Sheriff#103: Builds on the server-session BFF foundation by adding the SessionBinding abstraction and adapting the BFF runtime around it.
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a stateless cookie-based BFF session variant with AES-GCM sealing.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cuioss-review-bot

cuioss-review-bot Bot commented Jul 27, 2026

Copy link
Copy Markdown

PR Reviewer Guide 🔍

(Review updated until commit ba3edbc)

🧪 PR contains tests
🔒 Security concerns

Session Hijacking / Cross-Contamination:
In CookieSessionBinding.derivedIdentity, sessionId is derived non-uniquely from sub and the loginInstant epoch second. Concurrent token refreshes for two separate sessions belonging to the same user created in the same second will result in one session's cookie being overwritten with the other session's token material.

⚡ Recommended focus areas for review

Session Collision

derivedIdentity generates a session's sessionId using only identitySalt, payload.loginInstant().getEpochSecond(), and payload.sub(). If a single user (sub) creates two distinct sessions within the same epoch second (e.g., logging in on two devices/browsers or opening parallel tabs simultaneously), both session cookies will produce the exact same sessionId.

When concurrent requests from these two distinct sessions near token expiry and trigger a refresh, TokenRefreshCoordinator coalesces them via inFlight.putIfAbsent(sessionId, leader) because they share the same sessionId. The second session joins the leader's refresh and receives the leader's refreshed session record and Set-Cookie header, overwriting the second session's cookie with the first session's tokens.

To ensure each session receives a unique sessionId while remaining stable across re-seals, SealedSessionPayload should carry a randomly generated session identifier (minted at login) that is preserved across re-seals.

private String derivedIdentity(SealedSessionPayload payload) {
    MessageDigest digest;
    try {
        digest = MessageDigest.getInstance(DIGEST_ALGORITHM);
    } catch (NoSuchAlgorithmException unavailable) {
        throw new IllegalStateException(DIGEST_ALGORITHM + " is required for the cookie-mode session identity",
                unavailable);
    }
    digest.update(identitySalt);
    digest.update(Long.toString(payload.loginInstant().getEpochSecond()).getBytes(StandardCharsets.UTF_8));
    digest.update(payload.sub().getBytes(StandardCharsets.UTF_8));
    byte[] identity = new byte[IDENTITY_BYTES];
    System.arraycopy(digest.digest(), 0, identity, 0, IDENTITY_BYTES);
    return Base64.getUrlEncoder().withoutPadding().encodeToString(identity);
}

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/runtime/SessionAuthenticationStage.java (1)

130-172: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Render Map<String, String> Set-Cookie rows only once

PipelineRequest.responseHeaders() is documented as single-valued (Map<String, String>), and the reserved-route render path reads that map before appending special cookie handling. Since emitSetCookies currently calls put(SET_COOKIE_HEADER, ...) for at most one cookie, on a failed-refresh + HTML navigation, both the clearing cookie and the login-challenge cookie land in the same Map slot; whichever write is last is emitted, and the other is lost. Either support multi-value response-cookie storage/retention or render Set-Cookie in the single pass where multiple values are already handled explicitly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/runtime/SessionAuthenticationStage.java`
around lines 130 - 172, Update emitSetCookies and the unauthenticated
refresh-failure flow so multiple Set-Cookie values are retained instead of
overwriting the single responseHeaders entry. Ensure the clearing cookie and the
login-challenge cookie are both emitted for HTML navigation, using the existing
response rendering path that supports multiple cookie values.
api-sheriff/src/main/java/de/cuioss/sheriff/gateway/quarkus/BffRuntimeProducer.java (1)

300-304: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Assembly diagnostic still says "Server-mode" on the cookie path. build(...) is now reached for both modes, so a cookie-mode boot logs a message naming the wrong binding — the one line an operator greps to confirm which mode came up.

🪵 Report the resolved mode
-        LOGGER.debug("Server-mode BFF runtime assembled for origin %s (issuer %s)", gatewayOrigin, issuer);
+        LOGGER.debug("BFF runtime assembled in %s session mode for origin %s (issuer %s)",
+                isCookieMode(session) ? SESSION_MODE_COOKIE : SESSION_MODE_SERVER, gatewayOrigin, issuer);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@api-sheriff/src/main/java/de/cuioss/sheriff/gateway/quarkus/BffRuntimeProducer.java`
around lines 300 - 304, Update the assembly diagnostic in build(...) to report
the resolved runtime mode instead of hard-coding “Server-mode.” Use the existing
mode value or symbol available in the method, while preserving the gateway
origin and issuer details in the LOGGER.debug message.
api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/reserved/CallbackEndpoint.java (1)

173-208: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

sessionBinding.bind()'s documented IllegalStateException is not caught during login completion.

SessionBinding.bind(...) is documented to throw IllegalStateException when the binding cannot bind the session — including, for a stateless (cookie-mode) binding, when the sealed representation exceeds the cookie-size budget (a realistic outcome for large ID tokens / claim sets, enforced via SealedSessionCookieCodec.seal()'s CookieSizeBudgetExceededException). completeLogin(...) is invoked inside the handle() try block (line 175), but the catch only handles TokenSheriffException (line 176). A budget-exceeded (or, in server mode, store-at-capacity) session therefore propagates as an unhandled IllegalStateException out of handle() instead of a clean CallbackOutcome error.

Catch it alongside the exchange failure and map it to an explicit error status.

🛡️ Suggested fix
         try {
             AuthorizationCodeFlow.AuthenticationResult result = codeExchange.exchange(pending.flowContext(), params);
             return completeLogin(result, pending, now);
         } catch (TokenSheriffException engineFailure) {
             LOGGER.debug(engineFailure, "OIDC callback code exchange / token validation failed");
             return CallbackOutcome.error(BAD_REQUEST);
+        } catch (IllegalStateException bindingFailure) {
+            // A stateless binding whose sealed session exceeds the cookie-size budget, or a
+            // stateful binding at capacity — either way, the login cannot be completed.
+            LOGGER.warn(bindingFailure, "OIDC callback could not bind the new session");
+            return CallbackOutcome.error(SOME_APPROPRIATE_STATUS);
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/reserved/CallbackEndpoint.java`
around lines 173 - 208, Update the handle() exception handling around
codeExchange.exchange() and completeLogin() to also catch IllegalStateException
from sessionBinding.bind(). Map it to an explicit CallbackOutcome.error status,
preserving the existing TokenSheriffException handling and clean callback
response behavior.
api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/validation/ConfigValidator.java (1)

737-762: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

oidc.session.mode is matched case-sensitively in ConfigValidator but case-insensitively in GatewayEdgeRoute. Both read the same config field to decide "is this cookie mode?", but disagree on case handling — the shared root cause is that neither side normalizes the value before comparing.

  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/validation/ConfigValidator.java#L737-L762: validateSessionMode uses "cookie".equals(mode) / "server".equals(mode); a value like Cookie matches neither branch, so it silently skips both the cookie-key companion rule and the server-store rule with no boot error.
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/edge/GatewayEdgeRoute.java#L1102-L1118: cookieHeaderConfigurationFor uses SESSION_MODE_COOKIE::equalsIgnoreCase, so the same Cookie value IS treated as active cookie-mode here, relaxing the pre-route Cookie header cap even though the runtime-selected SessionBinding (built via case-sensitive matching elsewhere, per BffRuntimeProducer) is unlikely to actually be cookie-mode.

Normalize the comparison consistently — either lower-case the value once at config-load time, or make both sites use the same (ideally equalsIgnoreCase) comparison — so a case variation can't silently produce a validated-but-wrong runtime mode.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/validation/ConfigValidator.java`
around lines 737 - 762, Normalize session mode comparisons consistently across
ConfigValidator.validateSessionMode and
GatewayEdgeRoute.cookieHeaderConfigurationFor, preferably by using the same
case-insensitive comparison or lowercasing once during configuration loading.
Update both affected
sites—api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/validation/ConfigValidator.java:737-762
and
api-sheriff/src/main/java/de/cuioss/sheriff/gateway/edge/GatewayEdgeRoute.java:1102-1118—so
case variations trigger the same validation and runtime behavior.
api-sheriff/src/main/java/de/cuioss/sheriff/gateway/edge/GatewayEdgeRoute.java (1)

417-491: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Close or drain the keep-alive connection after oversized reserved-body 413s.

On both the pre-read Content-Length reject and the mid-stream ceiling reject, rejectOversizedReservedBody() calls renderProblem(...)response.end(body) while the request body remains unconsumed. Vert.x does not automatically close the connection here, so reused HTTP/1.1 keep-alive connections can be left with unread bytes pending, which can desync subsequent requests or pin the connection until idle timeout. Add connection.close() or drain the remaining request stream before returning the 413.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@api-sheriff/src/main/java/de/cuioss/sheriff/gateway/edge/GatewayEdgeRoute.java`
around lines 417 - 491, Update rejectOversizedReservedBody() to close the
request connection or drain the remaining request stream before completing the
413 response, covering both declared-content-length and streamed-body rejection
paths. Ensure the response still renders normally while preventing unread
keep-alive bytes from being reused or left pending.
🧹 Nitpick comments (3)
api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/cookie/CookieKeyMaterial.java (1)

55-57: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Stale @link target — codec() takes 3 args, not 2.

{@link #codec(String, Duration)} doesn't match the actual codec(String, Duration, int) signature (Line 146), so this reference won't resolve.

📝 Proposed fix
- * through {`@link` `#codec`(String, Duration)} and {`@link` `#identitySalt`()}, both of which consume it
+ * through {`@link` `#codec`(String, Duration, int)} and {`@link` `#identitySalt`()}, both of which consume it
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/cookie/CookieKeyMaterial.java`
around lines 55 - 57, Correct the Javadoc link in CookieKeyMaterial to reference
the three-argument codec(String, Duration, int) method signature, while leaving
the surrounding documentation unchanged.
api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/cookie/CookieKeyMaterialTest.java (1)

213-221: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test doesn't actually assert a 256-bit key.

identitySalt() is always a 32-byte SHA-256 digest regardless of the underlying key length, so assertEquals(32, salt.length) would pass even if generateKey() produced, say, a 128-bit key. The @DisplayName claims this verifies "a 256-bit key, so the codec it builds is AES-256-GCM," but nothing here actually checks the key size.

Consider asserting on the generated key material directly (e.g., expose a package-private accessor, or assert via a round trip that specifically depends on 256-bit key length) rather than inferring it from the SHA-256 salt length.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/cookie/CookieKeyMaterialTest.java`
around lines 213 - 221, Update shouldGenerateA256BitKey in CookieKeyMaterialTest
so it verifies the generated encryption key’s actual 256-bit length rather than
identitySalt().length, which is always the SHA-256 digest size. Expose or reuse
a package-private accessor for the underlying key material, then assert its
length is 32 bytes while preserving the separate per-boot salt uniqueness
assertion.
api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/cookie/CookieSessionBinding.java (1)

191-196: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Rollover log fires on every resolve, not once per rollover.

markForReseal logs COOKIE_RESEALED_WITH_CURRENT_KEY at INFO every time resolve() unseals a still-previous-key cookie — i.e., on every request for that session until the next bind/persist write actually re-seals it. That contradicts the class doc's "recorded once" framing and can flood logs at INFO level for the whole rotation window on a busy, long-TTL deployment; it also announces a completed "re-sealed" event before any write has actually re-sealed anything.

Consider gating the log to the point where the re-seal write actually happens (inside seal(), informed by whether the read that produced this payload came from the previous key), or downgrading repeat detections to DEBUG and reserving INFO for the completed write.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/cookie/CookieSessionBinding.java`
around lines 191 - 196, Move the COOKIE_RESEALED_WITH_CURRENT_KEY INFO log out
of markForReseal and gate it at the actual re-seal write in seal(), using the
state that indicates the payload was read with the previous key. Ensure INFO is
emitted only after a successful re-sealing write, while repeated resolve()
detections before persistence do not produce the rollover log.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/cookie/SealedSessionPayload.java`:
- Around line 104-125: Update SealedSessionPayload.decode to catch
DateTimeException alongside IllegalArgumentException around payload
reconstruction, returning Optional.empty() for epoch seconds outside Instant’s
supported bounds while preserving the existing handling for other malformed
authenticated payloads.

In
`@api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/reserved/BackchannelLogoutEndpoint.java`:
- Around line 102-115: Change the capability-gate logging in receive so
unsupported IdP destruction no longer emits an attacker-triggerable WARN for
every request. Downgrade LOGGER.warn using
BffLogMessages.WARN.COOKIE_BACKCHANNEL_DISABLED to an appropriate lower-level
log, or replace the per-request warning with a suitable counter metric, while
preserving the existing NOT_FOUND response and fail-closed behavior.

In
`@api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/session/SessionRecord.java`:
- Around line 36-40: Update the SessionRecord session identity documentation
around sessionId() and the corresponding cookie-mode description near the later
referenced section: state that server mode uses the opaque store key as the
clear session-cookie value, while cookie mode uses the generated opaque ID from
newSessionId(), sealed inside the cookie rather than derived. Preserve the
existing identity and redaction semantics.

---

Outside diff comments:
In
`@api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/reserved/CallbackEndpoint.java`:
- Around line 173-208: Update the handle() exception handling around
codeExchange.exchange() and completeLogin() to also catch IllegalStateException
from sessionBinding.bind(). Map it to an explicit CallbackOutcome.error status,
preserving the existing TokenSheriffException handling and clean callback
response behavior.

In
`@api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/runtime/SessionAuthenticationStage.java`:
- Around line 130-172: Update emitSetCookies and the unauthenticated
refresh-failure flow so multiple Set-Cookie values are retained instead of
overwriting the single responseHeaders entry. Ensure the clearing cookie and the
login-challenge cookie are both emitted for HTML navigation, using the existing
response rendering path that supports multiple cookie values.

In
`@api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/validation/ConfigValidator.java`:
- Around line 737-762: Normalize session mode comparisons consistently across
ConfigValidator.validateSessionMode and
GatewayEdgeRoute.cookieHeaderConfigurationFor, preferably by using the same
case-insensitive comparison or lowercasing once during configuration loading.
Update both affected
sites—api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/validation/ConfigValidator.java:737-762
and
api-sheriff/src/main/java/de/cuioss/sheriff/gateway/edge/GatewayEdgeRoute.java:1102-1118—so
case variations trigger the same validation and runtime behavior.

In
`@api-sheriff/src/main/java/de/cuioss/sheriff/gateway/edge/GatewayEdgeRoute.java`:
- Around line 417-491: Update rejectOversizedReservedBody() to close the request
connection or drain the remaining request stream before completing the 413
response, covering both declared-content-length and streamed-body rejection
paths. Ensure the response still renders normally while preventing unread
keep-alive bytes from being reused or left pending.

In
`@api-sheriff/src/main/java/de/cuioss/sheriff/gateway/quarkus/BffRuntimeProducer.java`:
- Around line 300-304: Update the assembly diagnostic in build(...) to report
the resolved runtime mode instead of hard-coding “Server-mode.” Use the existing
mode value or symbol available in the method, while preserving the gateway
origin and issuer details in the LOGGER.debug message.

---

Nitpick comments:
In
`@api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/cookie/CookieKeyMaterial.java`:
- Around line 55-57: Correct the Javadoc link in CookieKeyMaterial to reference
the three-argument codec(String, Duration, int) method signature, while leaving
the surrounding documentation unchanged.

In
`@api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/cookie/CookieSessionBinding.java`:
- Around line 191-196: Move the COOKIE_RESEALED_WITH_CURRENT_KEY INFO log out of
markForReseal and gate it at the actual re-seal write in seal(), using the state
that indicates the payload was read with the previous key. Ensure INFO is
emitted only after a successful re-sealing write, while repeated resolve()
detections before persistence do not produce the rollover log.

In
`@api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/cookie/CookieKeyMaterialTest.java`:
- Around line 213-221: Update shouldGenerateA256BitKey in CookieKeyMaterialTest
so it verifies the generated encryption key’s actual 256-bit length rather than
identitySalt().length, which is always the SHA-256 digest size. Expose or reuse
a package-private accessor for the underlying key material, then assert its
length is 32 bytes while preserving the separate per-boot salt uniqueness
assertion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: cuioss/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 185f012d-219e-4993-a8ab-bce6c5389c6e

📥 Commits

Reviewing files that changed from the base of the PR and between c065876 and 13a8141.

📒 Files selected for processing (65)
  • api-sheriff/pom.xml
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/ApiSheriffLogMessages.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/BffLogMessages.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/cookie/CookieKeyMaterial.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/cookie/CookieSessionBinding.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/cookie/SealedSessionCookieCodec.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/cookie/SealedSessionPayload.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/cookie/package-info.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/logout/BackchannelLogoutReceiver.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/refresh/TokenRefreshCoordinator.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/reserved/BackchannelLogoutEndpoint.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/reserved/CallbackEndpoint.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/reserved/LoginInitiationEndpoint.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/reserved/LogoutEndpoint.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/reserved/UserInfoEndpoint.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/runtime/BffRuntime.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/runtime/SessionAuthenticationStage.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/runtime/package-info.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/session/ServerSessionBinding.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/session/SessionBinding.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/session/SessionRecord.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/session/SessionStore.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/session/package-info.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/model/OidcConfig.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/validation/ConfigValidator.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/edge/EdgeHardeningOptions.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/edge/GatewayEdgeRoute.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/events/EventType.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/pipeline/BasicChecksStage.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/quarkus/BffRuntimeProducer.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/auth/AuthenticationStageTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/cookie/CookieKeyMaterialTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/cookie/CookieSessionBindingTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/cookie/SealedSessionCookieCodecTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/cookie/SealedSessionPayloadTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/refresh/TokenRefreshCoordinatorTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/reserved/BackchannelLogoutEndpointTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/reserved/CallbackEndpointTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/reserved/LoginInitiationEndpointTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/reserved/LogoutEndpointTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/reserved/UserInfoEndpointTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/runtime/SessionAuthenticationStageTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/session/ServerSessionBindingTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/config/validation/ConfigValidatorTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/edge/EdgeHardeningTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/edge/GatewayEdgeRouteBffWiringTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/edge/ReservedBodyCeilingTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/events/EventTypeTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/pipeline/BasicChecksStageTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/quarkus/BffRuntimeProducerTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/quarkus/CookieModeBootTest.java
  • api-sheriff/src/test/resources/config/cookieboot/endpoints/bff-cookie.yaml
  • api-sheriff/src/test/resources/config/cookieboot/gateway.yaml
  • api-sheriff/src/test/resources/config/cookieboot/topology.properties
  • doc/LogMessages.adoc
  • doc/configuration.adoc
  • integration-tests/docker-compose.yml
  • integration-tests/scripts/dump-keycloak-logs.sh
  • integration-tests/scripts/start-integration-container.sh
  • integration-tests/src/main/docker/sheriff-config-cookie/gateway.yaml
  • integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/BffCookieActivationWiringTest.java
  • integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/BffCookieBackchannelDisabledIT.java
  • integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/BffCookieSessionIT.java
  • integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/BffCookieStatelessnessIT.java
  • integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/BffKeycloakLoginFlow.java

@cuioss-oliver

Copy link
Copy Markdown
Collaborator Author

Triage dispositions

In reply to comment_id: PRR_kwDOPatrT88AAAABHY7Grg

Acknowledged — this is a Sourcery service-limit notice (the diff exceeds the 150000-character review budget), not a code finding, so there is nothing to fix or reply to substantively. The PR is deliberately batched per the project's minimize-the-number-of-PRs rule and stays under the 150-changed-file split threshold. Review coverage for this change is provided by CodeRabbit and PR-Agent, both of which reviewed the full diff, plus the SonarCloud new-code gate. No action taken.

In reply to comment_id: PRR_kwDOPatrT88AAAABHZEqZg

Every item in this review was verified against the current worktree and all eight are being fixed; none was dismissed. Outside-diff comments: SessionAuthenticationStage single-slot Set-Cookie overwrite -> TASK-019 (confirmed: the clearing cookie emitted at line 137 is overwritten by the login-challenge cookie at line 164, leaving a revoked session cookie in the browser); CallbackEndpoint uncaught bind() IllegalStateException -> TASK-020; ConfigValidator vs GatewayEdgeRoute session-mode case mismatch -> TASK-021 (confirmed: equals vs equalsIgnoreCase, so a mixed-case value skips boot validation yet still relaxes the pre-route Cookie header cap); GatewayEdgeRoute 413 keep-alive not drained -> TASK-022; BffRuntimeProducer hard-coded Server-mode boot diagnostic -> TASK-024. Nitpicks: CookieKeyMaterial stale codec @link -> TASK-024; CookieSessionBinding per-resolve rollover INFO -> TASK-024; CookieKeyMaterialTest 256-bit assertion that only checks the SHA-256 salt length -> TASK-025. See follow-up commits on this branch.

In reply to comment_id: IC_kwDOPatrT88AAAABL76TMw

Thanks — checked against the current code and the guard is already in place. TokenRefreshCoordinator.refresh() wraps performRefresh in a try/finally whose finally block calls leader.complete(RefreshOutcome.failed()) and then inFlight.remove(sessionId, leader) (TokenRefreshCoordinator.java:142-148). CompletableFuture.complete is a no-op once the future is already completed, so on the happy path the earlier complete(outcome) wins, and when performRefresh throws an unchecked exception the finally deterministically completes the leader with FAILED before the exception propagates to the leader's own caller. Coalesced waiters blocked in existing.join() therefore always unblock, and the in-flight entry is always cleared. The snippet quoted in the review already contains this finally block, so no change is needed. No action taken.

cuioss-oliver and others added 24 commits July 28, 2026 07:53
Spec D7. Replace every direct SessionStore binding in the BFF foundation with
one mode-neutral seam a stateless variant can also implement, so login, CSRF,
step-up, scope enforcement and logout orchestration stay single-sourced across
both session.mode values.

- SessionBinding: bind / resolve / persist / destroy plus the two IdP-driven
  destruction forms and their SUPPORTED/UNSUPPORTED capability flag. The
  contract names no store and no opaque id; BoundSession carries the session
  plus the Set-Cookie headers the caller emits.
- ServerSessionBinding: the server-mode adapter over the unchanged SessionStore
  and SessionCookieCodec, behaviour-preserving by construction and reporting
  IdpDestruction.SUPPORTED.
- Migrate all seven consumers (SessionAuthenticationStage,
  TokenRefreshCoordinator, CallbackEndpoint, LogoutEndpoint, UserInfoEndpoint,
  LoginInitiationEndpoint, BackchannelLogoutReceiver) plus the
  BffRuntimeProducer assembly site in one clean break — no shim, no
  compatibility layer, per the pre-1.0 rules and this plan's breaking
  compatibility setting.
- Single identity model: every binding populates SessionRecord.sessionId(), so
  TokenRefreshCoordinator keeps single-flight keyed on that component and the
  seam gains no identity accessor. The coordinator now threads the request
  Cookie header so its re-resolve-and-re-check under single-flight exclusion
  stays mode-neutral, and RefreshOutcome carries the re-bind's Set-Cookie
  headers, which the stage emits on the response.
- Javadoc corrected where it had become factually wrong: runtime/package-info
  no longer claims the stage resolves an opaque session id, SessionRecord
  states the seam's identity model, and SessionStore is narrowed to the
  server-mode implementation detail behind the seam.

Tests: new ServerSessionBindingTest pins the adapter's preservation of every
landed store semantic (upsert-on-persist with no destroy window, lazy-TTL
eviction on resolve, O(1) sid/sub destruction). The nine compile-breaking
consumer test classes are migrated to SessionBinding doubles with every landed
assertion intact.

Co-Authored-By: Claude <noreply@anthropic.com>
Spec D1. Seal the mediated token set into an authenticated encrypted cookie and
read it back per request, so session.mode=cookie holds no server-side state.

- SealedSessionPayload: the sealed plaintext — token material, identity/session
  claims, and the absolute login instant that anchors the server-enforced TTL.
  Encoding is compact, explicit and dependency-free; every credential is
  redacted from toString().
- SealedSessionCookieCodec: version(1B) || key-id(1B) || nonce(12B) ||
  ciphertext || tag(16B), base64url without padding. A fresh 96-bit SecureRandom
  nonce per seal — never derived, never counter-based, because GCM nonce reuse
  under one key is catastrophic. Version, key id and cookie name are bound into
  the GCM associated data, so a value cannot be replayed under a different
  format version, key generation, or cookie name. unseal selects the key
  deterministically by key id (never a try-every-key decrypt) and returns
  "no session" for any tag failure, malformed length, unknown version, or
  unknown key id — never a 500. A value over the ~4 KB budget fails the seal
  with a checked outcome and a logged warning rather than truncating silently.
  Max-Age carries the remaining absolute lifetime, so a re-seal never extends
  the session.
- CookieSessionBinding: the seam implementation over the codec. It enforces the
  absolute TTL server-side from the sealed login instant, so an expired cookie a
  browser still holds is refused; reports IdpDestruction.UNSUPPORTED because a
  stateless gateway has no index to destroy another browser's session through;
  and populates SessionRecord.sessionId() with a salted digest over the login
  instant and sub — stable for the session's life, used only as the in-instance
  single-flight key, and never emitted to the browser.
- BffRuntimeProducer: isServerModeBff becomes the mode-aware isBffMode, so
  session.mode=cookie activates the runtime. Only the binding differs between
  modes; every other collaborator is identical. The stale comment claiming a
  cookie-mode gateway never touches the confidential-client engine is corrected.
  Cookie mode fails closed at boot on a missing or non-AES-256 sealing key.
- Six LogRecord constants (INFO 15-17, WARN 113-115) carrying only bounded,
  non-sensitive dispositions, documented in doc/LogMessages.adoc as the project
  logging standard requires.

No new Maven dependency: the sealing uses the JDK's own javax.crypto AES-GCM.

Tests: 47 new cases across the payload, codec and binding — round-trip fidelity,
a distinct value per seal, tamper rejection with a flipped byte in each of
ciphertext / nonce / tag, cookie-name and version and key-id mismatch rejection,
oversize refusal, server-side TTL expiry, remaining-lifetime Max-Age on re-seal,
and the absence of key or token material from every emitted header. The
BffRuntimeProducer inert-for-cookie-mode test is migrated to the new
specification, where cookie mode is an active mode.

Co-Authored-By: Claude <noreply@anthropic.com>
Drive the back-channel logout gate off the SessionBinding sid/sub destruction
capability rather than a mode string: when idpDestruction() reports UNSUPPORTED
the endpoint answers 404 before parsing the form body, so the logout_token is
never read and the receiver is never reached. Each gated request records the
catalogued WARN COOKIE_BACKCHANNEL_DISABLED with a bounded, non-sensitive
reason and no token material.

Server-mode behaviour (200 with the destroyed count, 400 on a missing or
rejected token) is unchanged, and the reserved path stays registered in both
modes so it returns a deliberate 404 instead of falling through to the proxy
route table. The BffRuntime dispatch arm already renders every back-channel
outcome uncacheable, so the 404 is served Cache-Control: no-store like the
200/400 outcomes.
…rtup guard

Add the config/cookieboot fixture (gateway.yaml, topology.properties, and one
require: session endpoint) mirroring the landed config/testboot layout, plus
CookieModeBootTest: a @QuarkusTest whose profile points sheriff.config.dir at
the fixture and asserts the gateway boots active in cookie mode with NO
encryption_key configured — the generate-on-startup key mode — and that the
resulting binding reports the UNSUPPORTED sid/sub capability, observed at the
runtime surface as the back-channel path answering an uncacheable 404.

The fixture carries a token_validation block because an active BFF mode
resolves the shared @GatewayValidator TokenValidator at boot, and an
oidc.client_secret as a bare ${VAR} because the D4 secrets rule refuses a
literal and the confidential-client engine refuses a blank secret; the surefire
environment supplies that throwaway value. The test starts no container and
reaches no network — OIDC discovery is lazy — so it runs in the ordinary
surefire phase, before the Docker suite.
…wiring guard

Add sheriff-config-cookie/gateway.yaml mirroring the sheriff-config-mtls overlay
pattern (session.mode: cookie, encryption_key as a bare ${SESSION_ENCRYPTION_KEY}
reference, no store) and exactly one additive api-sheriff-cookie compose service
following the api-sheriff-mtls precedent: same image, shared sheriff-config mount
plus the cookie gateway.yaml overlay, its own port 10445, and the session-key
environment variable. No existing service, network, or volume definition is
touched.

Add BffCookieActivationWiringTest, the fast no-Docker surefire guard that parses
the committed descriptors and asserts the variant is actually switched on: cookie
session mode with no store, the sealing key supplied by env reference rather than
generated per boot, the back-channel path still registered so its capability gate
answers 404 instead of falling through, and the compose service publishing 10445
with the overlay mounted.

Deliverable 8 is incomplete: the three Docker-backed cookie IT suites are blocked
on two descriptor-level conflicts raised for a planning decision.
…o end

Add the Variant 3 cookie-mode integration suites and the deployment wiring
they need.

BffCookieSessionIT drives the login, steady-state mediation, logout and
tamper round trips against the dedicated cookie-mode gateway, asserting the
__Host- prefixed Secure/HttpOnly/SameSite=Lax cookie and that its value
carries no readable segment of the live mediated token.

BffCookieStatelessnessIT lands the statelessness proof literally rather than
by inference: a session sealed by api-sheriff-cookie (10445) is replayed
against a second, separate gateway process, api-sheriff-cookie-2 (10446),
that shares nothing with the first but the AES sealing key -- no session
store, no volume, no sticky routing. It also pins that the peer refuses a
cookie it cannot unseal, so being served is evidence about the key rather
than about laxness.

BffCookieBackchannelDisabledIT pins the capability gate on both bindings side
by side: 404 no-store in cookie mode, the unchanged 400 contract in server
mode.

Two operator-approved widenings of the plan's write boundary, both additive:

- docker-compose.yml gains a SECOND cookie-mode service, api-sheriff-cookie-2,
  a literal clone of api-sheriff-cookie on 10446/19003 using the same image,
  the same sheriff-config-cookie overlay and a byte-identical
  SESSION_ENCRYPTION_KEY. No new network, no new volume, no edit to any
  existing service, no change to the compose networking.
- scripts/start-integration-container.sh gains the missing readiness waits for
  both cookie instances' published management ports (19002 and 19003),
  mirroring the existing 19001 mTLS block. This makes the compose comment
  true rather than deleting its claim, and stops the ITs racing either
  instance. Additive only; the script is not restructured.

BffCookieActivationWiringTest is the fast no-Docker guard that the committed
descriptors actually activate the variant, extended here to assert the peer
instance exists, publishes 10446, mounts the same overlay, and carries the
identical sealing key -- the descriptor precondition the statelessness proof
rests on.
The ReservedHttpRequest and ReservedHttpResponse record headers carry an
unusual component layout -- a line break between each @nullable annotation
and the component it annotates. That layout is what the formatter inherited
from de.cuioss:cui-java-parent produces, and it reads as an accident that
invites a well-meant hand-reflow, which the next format-check silently undoes.

Name it instead: a short comment above each of the two record declarations
records that the layout is formatter-produced, that it is accepted rather
than fought because this project declares no formatter plugin of its own and
adding one is out of scope, and that the header must therefore not be
hand-reflowed. The two records are peers of the same structure, so both are
annotated in the same change.

Comments only -- no code, annotation, or component-ordering change, and no
pom.xml or .editorconfig modification.
The pre-commit quality gate rewrites sources in place. Land what it produced
across this plan's files so the tree matches the gate's own output and the
next run is a no-op:

- import-block whitespace normalized to the inherited convention
- test methods that declared a single checked exception now declare
  throws Exception, per the inherited JUnit recipe
- one constant-first equals() comparison

No behavioural change; the gate passes clean on the result.
… config key

Cookie mode could not serve a single authenticated request: every request
carrying a live sealed session cookie was rejected 400 at the edge before any
BFF logic ran. Two independent constants encoded the same limit and had drifted
apart -- SecurityConfiguration.defaults().maxHeaderValueLength (2048), hard-coded
into the pre-route BasicChecksStage by GatewayEdgeRoute, versus
SealedSessionCookieCodec.MAX_COOKIE_VALUE_BYTES (4096), enforced at seal time.

CHOSEN DIRECTION (operator-selected, overriding the ranked options recorded on
TASK-016): remove the root cause rather than the symptom. The task recommended a
blanket Cookie-header exemption from the length check; that treats the symptom
and leaves the two contradicting constants in place. Instead ONE declared number
now drives BOTH ends of the round trip, so the contradiction is unrepresentable:

  * New config key oidc.session.max_cookie_size (default 4096), validated in
    ConfigValidator with the same rigour as its sibling session keys -- floor 40
    (the encoded length of a structurally minimal sealed value, below which no
    cookie could ever be emitted) and ceiling 8192 (kept well below the 16 KiB
    inbound header-block limit, so a configured value can never exceed what the
    transport carries).
  * SealedSessionCookieCodec's seal-time budget is that configured value.
    MAX_COOKIE_VALUE_BYTES is REMOVED, not deprecated or kept as a fallback --
    leaving a second independent constant behind is exactly the defect.
  * The pre-route cap derives from the same value (budget + 512 bytes headroom
    for the cookie name and co-resident cookies).

THE LIFT IS SCOPED TWICE -- the narrowest relaxation that works:

  * By MODE: GatewayEdgeRoute no longer hard-codes SecurityConfiguration
    .defaults() for the pre-route stage; it derives the Cookie policy from
    gatewayConfig + bffRuntime, following the pattern already used a few lines
    above for forwarded()/oidc()/tls()/securityHeaders(). A bearer-only or
    server-mode gateway keeps 2048 on every header, unchanged.
  * By HEADER: within a cookie-mode gateway the raised cap applies to Cookie and
    Set-Cookie values ONLY. BasicChecksStage.validateHeaders already had the
    header name in hand at the loop head -- that is the seam. Every other header
    keeps 2048, and every non-length validator (null-byte, control-character,
    injection-pattern) still applies to the cookie value.

RECORDED CONSTRAINT: per-anchor scoping is unreachable for this stage. It runs
BEFORE route selection and GatewayEdgeRoute is one bean holding the whole route
table, so no anchor exists at that point. The key lives on the session block but
enforcement is necessarily gateway-wide. Stated in the code and in the reference
docs so the key's placement is not mistaken for per-anchor enforcement.

This remains a deliberate relaxation of an inbound hardening control on a
security gateway. It is justified in code, held as narrow as the two scopings
allow, and nothing beyond them is widened.

Also in this change:

  * doc/configuration.adoc gains the reference-layer row for the new key
    (default, bounds, the cookie-mode-only + Cookie-header-only scoping, and the
    gateway-wide-enforcement caveat). A config key never merges undocumented.
  * dump-keycloak-logs.sh's failure-path app-log dump now names both cookie
    instances, so a CI-only recurrence is diagnosable from the uploaded artifact
    instead of requiring a local repro.
  * BffCookieSessionIT.sealedCookieValueCarriesNoReadableToken: a latent test
    defect the 400 had masked. Passing response.path(...) straight into
    String.valueOf() let the generic return type infer to char[], so the
    compiler inserted a cast the JSON-path proxy cannot satisfy. Bound to Object
    first, exactly as the sibling assertions in the same class already do.

COVERAGE pinning the new boundary at module-test level: a Cookie header at the
configured budget passes; one above the raised cap is rejected; an oversized
NON-Cookie header still yields 400 on a cookie-mode gateway; and a server /
bearer-only gateway still rejects an oversized Cookie header at 2048 (proving
the lift did not leak across modes). Plus the ConfigValidator floor/ceiling
bounds, on and outside the range.

Verified: quality gate green, full verify green, and the full Docker integration
profile green against a freshly rebuilt native image -- 75/75 ITs pass, with
BffCookieSessionIT (5), BffCookieStatelessnessIT (3) and
BffCookieBackchannelDisabledIT (2) all green and no pre-existing suite regressed.
…e-crypto-core

Inline the SessionCookieCodec construction into the server-mode branch of
BffRuntimeProducer. It was built unconditionally ahead of the mode-selection
ternary but consumed only in the server-mode branch, so every cookie-mode boot
constructed and discarded it.

Co-Authored-By: Claude <noreply@anthropic.com>
Deliberate, operator-approved widening of this plan's scope onto a
PRE-EXISTING PLAN-06 defect surfaced by the finalize security audit.

GatewayEdgeRoute#readReservedBodyThenDispatch buffered the FULL request
body of the two reserved, PRE-AUTHENTICATION POST paths (the OIDC
response_mode=form_post callback and the back-channel logout receiver)
under a 20-second wall-clock deadline and NO byte-size cap. That read
happens before basicChecksStage / thoroughChecksStage run, so the
SecurityConfiguration.maxBodySize cap that bounds ordinary proxied routes
never applied to it, and EdgeHardeningOptions bounded only a single 16 KiB
chunk rather than the cumulative body. On a 512M container that was an
unauthenticated memory-exhaustion DoS vector.

The read is now bounded by EdgeHardeningOptions.reservedBodyMaxBytes(),
enforced twice (defence in depth):

  a) a Content-Length pre-check that refuses before a single body byte is
     buffered, and
  b) a streaming cumulative byte counter that aborts mid-read, closing the
     bypass a chunked / Content-Length-absent / lying-Content-Length body
     would otherwise have.

The ceiling is DEFINED AS EdgeHardeningOptions.MAX_HEADER_SIZE_BYTES
(16 KiB) rather than restated as a second literal, so the gateway's single
inbound-unit bound cannot drift into two contradicting constants — the
exact defect class this plan already fixed once for the cookie budget. Both
payloads (a urlencoded code/state pair, a compact logout_token JWT) sit an
order of magnitude below it, and the worst case at the 2048 admission cap
is ~32 MiB.

Either breach is rejected 413 (EventType.RESERVED_BODY_TOO_LARGE, the
honest status for an over-large payload) through the surrounding RFC 9457
problem+json contract — never a 500, and never by silently truncating the
body into the handler. The new WARN ApiSheriff-109 records the ceiling and
a fixed disposition only, never the offending body, and is documented in
doc/LogMessages.adoc.

The 20-second deadline, the reserved-path dispatch shape, and the pipeline
stages are untouched.

Coverage: ReservedBodyCeilingTest drives a live Vert.x server and pins a
body at the ceiling (accepted), one byte over (413), an over-ceiling
declared Content-Length refused inside a bounded wait far below the body
deadline (proving no buffering), and an over-ceiling chunked body with no
declared length at all (proving the streaming counter). EdgeHardeningTest
pins the derivation itself; EventTypeTest pins the new error-contract row.

Also carries a whitespace-only reflow of BffRuntimeProducer emitted by the
pre-commit formatter.
Long.parseLong could succeed while Instant.ofEpochSecond threw
DateTimeException, which is not an IllegalArgumentException and so escaped
SealedSessionPayload.decode's catch and unseal() as an unhandled request
error. Widen the catch and cover both the loginInstant and authTime fields
with a regression test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAAt7guDZEDA4S6nUTHcLW
The gate fired a catalogued WARN before any signature or body verification on
a reserved, unauthenticated path, so any caller who could reach the gateway
could drive an unbounded WARN flood with no logout_token, session, or
credential. Every sibling malformed-input path already logs DEBUG; match them
and drop the now-unreferenced ApiSheriff-115 LogRecord and its catalogue entry.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAAt7guDZEDA4S6nUTHcLW
emitSetCookies wrote into the single-valued response-header map and took only
findFirst(), so on the failed-refresh HTML navigation path the clearing cookie
was overwritten by the login-challenge cookie and a revoked session cookie was
left in the browser. Set-Cookie is legitimately multi-valued (RFC 6265 3), so
PipelineRequest now carries a dedicated Set-Cookie accumulator that the edge
renders as distinct header lines on every terminal path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAAt7guDZEDA4S6nUTHcLW
SessionBinding.bind is documented to throw IllegalStateException when it cannot
hold the session -- for cookie mode whenever the sealed value exceeds the
cookie-size budget, a realistic outcome for a large ID token. handle() caught
only TokenSheriffException, so that escaped as an unhandled exception. Also
widen the auth_time claim parse, which had the same DateTimeException gap.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAAt7guDZEDA4S6nUTHcLW
ConfigValidator compared the mode case-sensitively while the edge cap, the
runtime binding selection and the readiness check each compared it
case-insensitively against their own private constant. A value like 'Cookie'
therefore skipped both boot companion rules while still relaxing the pre-route
Cookie header-value cap. OidcConfig.Session now canonicalizes the value at
construction and owns the single isCookieMode/isServerMode predicate all four
sites read; the four duplicated constants are gone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAAt7guDZEDA4S6nUTHcLW
Both 413 paths ended the response with the request body unconsumed, leaving
pending bytes on a reused HTTP/1.1 keep-alive connection -- which desyncs the
next request framed on it, or lets an attacker pin connections by repeatedly
tripping the ceiling, undercutting the reserved-body DoS guard. The response
now advertises Connection: close and the connection is closed once written.
Also reattach the dangling Javadoc block to securityConfigurationFor
(java:S8491).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAAt7guDZEDA4S6nUTHcLW
Both catch parameters in SealedSessionCookieCodec.unseal were declared but
never referenced (java:S7467). The rejection class each branch covers now lives
in an adjacent comment instead of an unused binding.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAAt7guDZEDA4S6nUTHcLW
Four statements were wrong about cookie mode: the session-identity docs implied
one model for both bindings (cookie mode actually discards the minted id and
derives its own), the assembly diagnostic hard-coded 'Server-mode' on a path now
reached by both modes, a Javadoc link named a signature that no longer exists,
and the rotation INFO fired on every resolve of an unrotated cookie -- flooding
for the whole rotation window and announcing a re-seal no write had performed.
The rotation record is now latched once per process, the repeats are DEBUG, and
the LogRecord is retemplated to say what it observes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAAt7guDZEDA4S6nUTHcLW
shouldGenerateA256BitKey asserted the derived identity salt's length, which is
a fixed-width SHA-256 digest and so would pass for a 128-bit key too -- the
crypto-strength claim in its name was never checked. It now asserts the key
length through a package-private accessor that exposes no material, with the
salt-uniqueness claim split into its own test. Also hoist assertThrows lambda
arguments into locals so exactly one call can throw (java:S5778), and rename
the locals named 'record' (java:S6213).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAAt7guDZEDA4S6nUTHcLW
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAAt7guDZEDA4S6nUTHcLW
@cuioss-oliver
cuioss-oliver force-pushed the feature/plan-07a-bff-cookie-crypto-core branch from 66f3817 to ba3edbc Compare July 28, 2026 05:54
@cuioss-oliver

Copy link
Copy Markdown
Collaborator Author

/review

@cuioss-oliver
cuioss-oliver enabled auto-merge July 28, 2026 05:59

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/cookie/SealedSessionCookieCodec.java (1)

225-231: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

INFO-level seal-success logging fires on every refresh, not just login.

seal() logs INFO unconditionally on every successful seal, but CookieSessionBinding calls it from both bind() (login) and persist() (every token refresh). On a busy gateway with many active sessions and short-lived tokens, this becomes one INFO line per refresh per session — the same "fires on every request" pattern the rollover detection in CookieSessionBinding was explicitly throttled to avoid.

♻️ Suggested downgrade
-        LOGGER.info(BffLogMessages.INFO.COOKIE_SESSION_SEALED, encoded.length());
+        LOGGER.debug("Sealed cookie-mode session (%s bytes)", encoded.length());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/cookie/SealedSessionCookieCodec.java`
around lines 225 - 231, Update the successful-seal logging in
SealedSessionCookieCodec.seal so routine refreshes do not emit an INFO-level
COOKIE_SESSION_SEALED entry on every call. Preserve the size-budget validation
and return behavior, and use an appropriate lower log level for this per-seal
event while retaining the existing context.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/cookie/CookieSessionBinding.java`:
- Around line 118-124: Update the cookie-mode session identity derivation used
by CookieSessionBinding and SessionRecord so it incorporates a session-specific
value from the sealed refresh-token family or nonce, rather than only sub and
loginInstant epoch seconds. Ensure each sealed payload for the same user and
timestamp receives a distinct, stable sessionId that remains consistent across
token refreshes and prevents TokenRefreshCoordinator.refresh() from coalescing
unrelated sessions.

---

Nitpick comments:
In
`@api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/cookie/SealedSessionCookieCodec.java`:
- Around line 225-231: Update the successful-seal logging in
SealedSessionCookieCodec.seal so routine refreshes do not emit an INFO-level
COOKIE_SESSION_SEALED entry on every call. Preserve the size-budget validation
and return behavior, and use an appropriate lower log level for this per-seal
event while retaining the existing context.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: cuioss/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 953c9f8f-0052-4ba8-ab06-2ba699058d7d

📥 Commits

Reviewing files that changed from the base of the PR and between 66f3817 and ba3edbc.

📒 Files selected for processing (68)
  • api-sheriff/pom.xml
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/ApiSheriffLogMessages.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/BffLogMessages.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/cookie/CookieKeyMaterial.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/cookie/CookieSessionBinding.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/cookie/SealedSessionCookieCodec.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/cookie/SealedSessionPayload.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/cookie/package-info.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/logout/BackchannelLogoutReceiver.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/refresh/TokenRefreshCoordinator.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/reserved/BackchannelLogoutEndpoint.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/reserved/CallbackEndpoint.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/reserved/LoginInitiationEndpoint.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/reserved/LogoutEndpoint.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/reserved/UserInfoEndpoint.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/runtime/BffRuntime.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/runtime/SessionAuthenticationStage.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/runtime/package-info.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/session/ServerSessionBinding.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/session/SessionBinding.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/session/SessionRecord.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/session/SessionStore.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/session/package-info.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/model/OidcConfig.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/validation/ConfigValidator.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/edge/EdgeHardeningOptions.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/edge/GatewayEdgeRoute.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/events/EventType.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/pipeline/BasicChecksStage.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/pipeline/PipelineRequest.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/quarkus/BffRuntimeProducer.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/quarkus/GatewayReadinessCheck.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/auth/AuthenticationStageTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/cookie/CookieKeyMaterialTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/cookie/CookieSessionBindingTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/cookie/SealedSessionCookieCodecTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/cookie/SealedSessionPayloadTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/refresh/TokenRefreshCoordinatorTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/reserved/BackchannelLogoutEndpointTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/reserved/CallbackEndpointTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/reserved/LoginInitiationEndpointTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/reserved/LogoutEndpointTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/reserved/UserInfoEndpointTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/runtime/SessionAuthenticationStageTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/session/ServerSessionBindingTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/config/model/ConfigModelContractTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/config/validation/ConfigValidatorTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/edge/EdgeHardeningTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/edge/GatewayEdgeRouteBffWiringTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/edge/ReservedBodyCeilingTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/events/EventTypeTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/pipeline/BasicChecksStageTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/quarkus/BffRuntimeProducerTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/quarkus/CookieModeBootTest.java
  • api-sheriff/src/test/resources/config/cookieboot/endpoints/bff-cookie.yaml
  • api-sheriff/src/test/resources/config/cookieboot/gateway.yaml
  • api-sheriff/src/test/resources/config/cookieboot/topology.properties
  • doc/LogMessages.adoc
  • doc/configuration.adoc
  • integration-tests/docker-compose.yml
  • integration-tests/scripts/dump-keycloak-logs.sh
  • integration-tests/scripts/start-integration-container.sh
  • integration-tests/src/main/docker/sheriff-config-cookie/gateway.yaml
  • integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/BffCookieActivationWiringTest.java
  • integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/BffCookieBackchannelDisabledIT.java
  • integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/BffCookieSessionIT.java
  • integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/BffCookieStatelessnessIT.java
  • integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/BffKeycloakLoginFlow.java
🚧 Files skipped from review as they are similar to previous changes (56)
  • api-sheriff/src/test/resources/config/cookieboot/topology.properties
  • integration-tests/scripts/dump-keycloak-logs.sh
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/ApiSheriffLogMessages.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/quarkus/GatewayReadinessCheck.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/session/package-info.java
  • integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/BffCookieSessionIT.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/edge/EdgeHardeningTest.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/pipeline/BasicChecksStage.java
  • doc/configuration.adoc
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/reserved/CallbackEndpointTest.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/reserved/LoginInitiationEndpoint.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/session/SessionBinding.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/reserved/LogoutEndpoint.java
  • api-sheriff/src/test/resources/config/cookieboot/gateway.yaml
  • integration-tests/src/main/docker/sheriff-config-cookie/gateway.yaml
  • api-sheriff/pom.xml
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/events/EventType.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/pipeline/BasicChecksStageTest.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/session/ServerSessionBinding.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/session/SessionStore.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/runtime/BffRuntime.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/reserved/LoginInitiationEndpointTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/cookie/CookieSessionBindingTest.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/pipeline/PipelineRequest.java
  • integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/BffCookieBackchannelDisabledIT.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/events/EventTypeTest.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/logout/BackchannelLogoutReceiver.java
  • api-sheriff/src/test/resources/config/cookieboot/endpoints/bff-cookie.yaml
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/reserved/UserInfoEndpointTest.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/session/SessionRecord.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/auth/AuthenticationStageTest.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/runtime/package-info.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/reserved/LogoutEndpointTest.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/reserved/UserInfoEndpoint.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/edge/EdgeHardeningOptions.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/reserved/BackchannelLogoutEndpoint.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/config/validation/ConfigValidatorTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/quarkus/CookieModeBootTest.java
  • integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/BffKeycloakLoginFlow.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/reserved/CallbackEndpoint.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/cookie/SealedSessionPayload.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/cookie/CookieKeyMaterialTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/quarkus/BffRuntimeProducerTest.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/model/OidcConfig.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/BffLogMessages.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/cookie/SealedSessionCookieCodecTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/edge/GatewayEdgeRouteBffWiringTest.java
  • integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/BffCookieActivationWiringTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/config/model/ConfigModelContractTest.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/refresh/TokenRefreshCoordinator.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/refresh/TokenRefreshCoordinatorTest.java
  • integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/BffCookieStatelessnessIT.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/edge/GatewayEdgeRoute.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/validation/ConfigValidator.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/bff/session/ServerSessionBindingTest.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/quarkus/BffRuntimeProducer.java

@cuioss-oliver
cuioss-oliver added this pull request to the merge queue Jul 28, 2026
Merged via the queue into main with commit 341ab9e Jul 28, 2026
26 checks passed
@cuioss-oliver
cuioss-oliver deleted the feature/plan-07a-bff-cookie-crypto-core branch July 28, 2026 06:21
@cuioss-oliver

Copy link
Copy Markdown
Collaborator Author

/review

1 similar comment
@cuioss-oliver

Copy link
Copy Markdown
Collaborator Author

/review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant