Skip to content

feat(bff): complete cookie variant docs, ADRs and unique session ids - #125

Merged
cuioss-oliver merged 13 commits into
mainfrom
feature/plan-07b-bff-cookie-docs-adr
Jul 29, 2026
Merged

feat(bff): complete cookie variant docs, ADRs and unique session ids#125
cuioss-oliver merged 13 commits into
mainfrom
feature/plan-07b-bff-cookie-docs-adr

Conversation

@cuioss-oliver

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

Copy link
Copy Markdown
Collaborator

Summary

Completes BFF Variant 3 (cookie, stateless) ahead of the 0.1.0 cut: fixes a
cookie-mode session-identity collision defect, pays the documentation and ADR
debt left by PLAN-06/PLAN-07A, and lands two CLAUDE.md git-workflow
corrections. 6 deliverables, 23 files, 6 commits on
feature/plan-07b-bff-cookie-docs-adr targeting main.

BREAKING: the sealed-cookie wire format changes
(SealedSessionPayload.FIELD_COUNT 8 -> 9,
SealedSessionCookieCodec.FORMAT_VERSION 1 -> 2). Every existing cookie-mode
session is invalidated on deploy and users must re-login. Per pre-1.0 rules
there is deliberately no legacy 8-field acceptance path in decode().

Changes

Security fix — unique cookie-mode session identity (deliverable 2)

sessionId was SHA-256(salt || loginEpochSecond || sub) truncated to 16
bytes. Two logins by the same subject inside the same wall-clock second
produced the same sessionId. TokenRefreshCoordinator's single-flight
inFlight map is keyed on that id, so the collision cross-wired the two
sessions' token refreshes — one session's refresh could complete against the
other session's tokens.

Fix: a 32-byte SecureRandom per-session nonce is minted once at login and
carried inside the sealed payload as a 9th field, included in the identity
digest, and re-encoded verbatim on every re-seal (rotate() reuses
previous.sessionId(), so the nonce must stay stable across re-seals —
TokenRefreshCoordinator's single-flight semantics depend on it).

  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/cookie/SealedSessionPayload.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/CookieSessionBinding.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/session/SessionRecord.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/refresh/TokenRefreshCoordinator.java
  • Tests: SealedSessionPayloadTest.java, SealedSessionCookieCodecTest.java,
    CookieSessionBindingTest.java, TokenRefreshCoordinatorTest.java — adds
    the missing test asserting two same-second logins for the same sub yield
    distinct sessionIds, plus a re-seal-stability test.

Javadoc correction (deliverable 1 — originating-spec D5 already satisfied)

The originating spec's D5 ("re-source UserInfoEndpoint from the sealed
cookie") had already shipped in PLAN-07A, so this deliverable resolved to
correcting the stale server-mode-only wording left in the BFF runtime
javadoc rather than any new implementation:

  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/runtime/BffRuntime.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/quarkus/BffRuntimeProducer.java

Documentation debt (deliverables 3 and 4)

  • Layer (a) design-doc fix: doc/variants/03-bff-cookie.adoc — corrects the
    false "required session.encryption_key" claim and annotates the sample
    with the condition under which the key is actually needed.
  • New layer (b)/(c) documentation:
    doc/user/bff-cookie.adoc and doc/development/bff-cookie.adoc, indexed
    from doc/user/README.adoc, doc/development/README.adoc, and
    doc/README.adoc. Layer (b) documents the forced-re-login consequence of
    the FORMAT_VERSION bump and the PendingAuthorizationStore
    cross-instance login limitation ("stateless after login").

New ADRs (deliverable 5)

  • doc/adr/0018-BFF_session_mode_is_one_SessionBinding_seam_behind_a_fixed_reserved-path_and_CSRF_model.adoc
    — the reserved-path model, the fixed CSRF model, the session-mode
    server-vs-cookie split, the cookie key-material two-mode decision, and the
    session-identity nonce/FORMAT_VERSION decision from deliverable 2.
  • doc/adr/0019-Reserved_BFF_paths_bypass_the_url-parameter_value_pipeline_and_the_cookie-header_length_relaxation_is_gateway-wide.adoc
    — the two security-sensitive relaxations previously documented only in
    javadoc (BasicChecksStage.reservedPathMatcher skipping the inbound
    url-parameter pipeline; the cookie-header length relaxation being
    gateway-wide rather than per-anchor).
  • doc/architecture.adoc updated to reference both ADRs.

CLAUDE.md corrections (deliverable 6)

  • Names the current PR reviewer (PR-Agent, per
    .github/workflows/pr-agent.yml) instead of the retired Gemini reference.
  • Adds a post-merge verification step between the existing steps 7 and 8:
    re-checks the PR's real post-merge workflow (benchmark.yml,
    pull_request: types: [closed]) and asserts zero unresolved review
    threads via ci pr comments --pr-number N --unresolved-only.

Test Plan

  • python3 .plan/execute-script.py plan-marshall:build-maven:maven run --command-args "verify -Ppre-commit"
  • python3 .plan/execute-script.py plan-marshall:build-maven:maven run --command-args "verify"
  • New SealedSessionPayloadTest / SealedSessionCookieCodecTest cases
    (same-second collision, re-seal stability) pass
  • Manual testing completed (if applicable)

Related Issues

No linked issue for this plan.


Generated by plan-finalize skill

Summary by CodeRabbit

  • New Features

    • Cookie-based BFF session cookies now include a per-login nonce to anchor session identity.
    • Sealed session cookies use format version 2 and are stricter about compatibility.
    • Reserved BFF endpoints now bypass the URL-parameter value validation pipeline, and cookie-mode gateways allow longer Cookie/Set-Cookie header values.
  • Bug Fixes

    • Session rotation/refresh preserves the session nonce to prevent identity drift.
    • Older sealed-cookie formats are rejected fail-closed, requiring users to sign in again after upgrades.
  • Documentation

    • Updated operator, contributor, and architecture docs for cookie-based BFF (variant 3) and the revised validation/session behavior.

cuioss-oliver and others added 6 commits July 28, 2026 23:32
The BffRuntime and BffRuntimeProducer javadoc still described the runtime as
server-mode-only, contradicting BffRuntimeProducer.isBffMode(), which activates
the runtime for either recognised session.mode (server or cookie) provided a
redirect_uri is configured.

Rewrite the eight server-mode-only sites in BffRuntime.java (class javadoc,
public constructor, inert(), isActive() and the three inert @throws clauses) to
mode-neutral wording naming both modes, and drop the stale server-mode qualifier
from BffRuntimeProducer's class javadoc, lazy-discovery paragraph and
tokenValidator @param so they agree with its own activation paragraph. The
literal token server-mode no longer occurs in either file.

Javadoc only: no signature, import or behaviour change. dispatch()'s already
mode-neutral @throws is left untouched.

D5 hypothesis (re-source the user-info endpoint from the sealed cookie) is
recorded as ALREADY-SATISFIED, shipped by PLAN-07A: BffRuntimeProducer.java
:215-218/:274-275 builds UserInfoEndpoint over the mode-selected SessionBinding,
and BffCookieStatelessnessIT.bothInstancesResolveTheSameIdentity() (:106-123)
covers the cookie-mode user-info path end-to-end across two instances. No
re-implementation and no new cookie-mode user-info IT were added.

Co-Authored-By: Claude <noreply@anthropic.com>
… nonce

CookieSessionBinding derived the cookie-mode sessionId from a salted digest over
the login instant and sub only, so two logins by the same subject inside one
clock second collided. A collision cross-wires TokenRefreshCoordinator's
single-flight inFlight keying between two genuinely distinct sessions.

Mint a per-session random nonce at login and fold it into the digest:

- SealedSessionPayload gains a ninth mandatory component sessionNonce, appended
  in declaration order and read back in decode(). FIELD_COUNT rises to 9 with a
  strict guard — no legacy eight-field acceptance path, since admitting one
  would mean synthesizing a nonce and silently changing the derived identity.
- SealedSessionCookieCodec.FORMAT_VERSION rises to 2. The version byte is bound
  into the GCM associated data, so a v1 cookie fails authentication outright
  rather than being mis-parsed against the nine-field shape.
- SessionRecord gains a tenth component Optional<String> sessionNonce,
  normalized to empty in the canonical constructor and redacted from
  toString(). Server mode always leaves it empty; only the cookie binding
  populates it.
- CookieSessionBinding mints the nonce once in bind() (32 secure-random bytes,
  base64url, matching newSessionId()'s width), re-seals it verbatim in
  persist(), and folds it into derivedIdentity(). persist() fails loud rather
  than re-minting when a record carries no nonce.
- TokenRefreshCoordinator.rotate() copies the previous record's nonce onto the
  rotated one; rotate() rebuilds field-by-field, so an uncopied component is
  silently dropped and a dropped nonce would change the identity mid-session.

SessionBinding.persist()'s signature is unchanged and the identity remains a
digest — the raw nonce is never used as, or substituted for, the sessionId.

Consumer sweep for the SessionRecord canonical-constructor break: a module-wide
word-boundary identifier search across all four modules returned 178 hits
(api-sheriff main and test; integration-tests and benchmarks empty as expected).
Classified: 3 direct new SessionRecord(...) sites, all in InMemorySessionStoreTest
and updated to the 10-arg form; 0 destructuring or pattern-match sites; the
remainder builder() calls or plain type references, all source-compatible.

Test contract updated for the new shape, including regressions that an
eight-field payload and a v1-stamped cookie are both rejected, that two logins
by one subject in a single second yield distinct identities, that the identity
is byte-identical across a re-seal, and that rotate() propagates the nonce.
CookieKeyMaterialTest also constructed the payload directly and was updated
although the plan did not enumerate it. Both cookie ITs pass unmodified.

Co-Authored-By: Claude <noreply@anthropic.com>
…-0018/0019

Variant 3 (cookie-based BFF) had only a design section, while Variant 2 carried
all three documentation layers. This lands the missing layers, fixes the design
section's factual defects, and records the architectural decisions.

Design layer (doc/variants/03-bff-cookie.adoc):

- session.encryption_key was described as required. It is not: omitting it
  selects the generate-on-startup key mode. Rewritten to describe the key as
  optional, naming what declaring it buys (sharing sessions across replicas,
  surviving restart) and the cost of omitting it. The YAML sample now carries
  the optionality inline, matching previous_key's existing annotation.
- Documented that the variant is stateless after login but not during it: the
  pending authorization is per-instance, so a login must complete on the
  instance that began it. Session affinity is required for the login leg only.
- Documented the sealed-cookie format bump and its forced-re-login consequence.

Operator layer (doc/user/bff-cookie.adoc, new): client registration, mode
selection, the two key-material modes and their trade-off, decrypt-only
rotation, the 4 KB cookie budget with remediation guidance, what IdP-driven
destruction cannot do here, login-leg affinity, and the upgrade consequence.

Contributor layer (doc/development/bff-cookie.adoc, new): where the cookie code
lives, the SessionBinding seam and why the reserved endpoints stay mode-neutral,
the sealed-cookie wire format with the rule that a format change requires a
FORMAT_VERSION bump, the derived identity and its stability-and-uniqueness
constraints, and the unit/IT entry points including the two-instance topology.

ADRs:

- ADR-0018 records the BFF session-mode architecture: reserved-path precedence,
  the fixed CSRF model, the one SessionBinding seam, the cookie key-material
  two-mode decision, and the per-session identity nonce. The first four
  originate in earlier BFF work and are marked retroactive in the context.
- ADR-0019 records the two inbound-validation relaxations with their bounding
  constraints: reserved paths bypass the url-parameter value pipeline (they
  terminate at the gateway and their handlers validate their own parameters),
  and the cookie-header length raise is necessarily gateway-wide because
  inbound validation runs before route selection.

Indexes: added the new guides to doc/user/README.adoc, doc/development/
README.adoc and doc/README.adoc's Deployment Variants table. The Architecture
Decisions table gained ADR-0018/0019 and, as a Boy-Scout fix, the four rows
that were missing entirely (ADR-0010, 0011, 0012, 0017) — it now enumerates all
19 ADRs in the corpus.

doc/architecture.adoc references both new ADRs and drops the same stale
server-mode-only framing corrected earlier in BffRuntime: the producer activates
for either recognised session.mode, and both modes drive the same wiring.

Co-Authored-By: Claude <noreply@anthropic.com>
…e-docs-adr

Collapse two near-identical SessionRecord-rebuild helpers in
CookieSessionBindingTest into one shared withRotatedAccessToken helper.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EfShpgB9HbUSf9n6zhP9r

@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, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@cuioss-oliver, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 59 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: c218fb62-f7b4-496e-b8a5-e30af03410a5

📥 Commits

Reviewing files that changed from the base of the PR and between 0d386ea and 365b30e.

📒 Files selected for processing (10)
  • CLAUDE.md
  • 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/session/SessionRecord.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/SealedSessionPayloadTest.java
  • doc/architecture.adoc
  • doc/development/bff-cookie.adoc
  • doc/user/bff-cookie.adoc
  • doc/variants/03-bff-cookie.adoc
📝 Walkthrough

Walkthrough

Changes

Cookie session nonce and BFF mode

Layer / File(s) Summary
Sealed payload contract and format version
api-sheriff/src/main/java/.../cookie/SealedSessionPayload.java, api-sheriff/src/main/java/.../cookie/SealedSessionCookieCodec.java, api-sheriff/src/test/java/.../cookie/*Test.java
Adds a mandatory nonce field, changes the wire format to nine fields, bumps the format version to 2, rejects legacy cookies, and updates payload tests.
Nonce minting, persistence, and identity derivation
api-sheriff/src/main/java/.../cookie/CookieSessionBinding.java, api-sheriff/src/test/java/.../cookie/CookieSessionBindingTest.java
Mints a nonce at login, preserves it during re-sealing, restores it from cookies, and incorporates it into derived session identities.
Session record and refresh propagation
api-sheriff/src/main/java/.../session/SessionRecord.java, api-sheriff/src/main/java/.../refresh/TokenRefreshCoordinator.java, api-sheriff/src/test/java/.../refresh/*Test.java, api-sheriff/src/test/java/.../session/*Test.java
Adds the cookie-only nonce to SessionRecord, normalizes and redacts it, and copies it during token refresh.
Mode-neutral runtime architecture
api-sheriff/src/main/java/.../runtime/BffRuntime.java, api-sheriff/src/main/java/.../quarkus/BffRuntimeProducer.java, doc/architecture.adoc, doc/adr/*, doc/README.adoc
Documents runtime activation for both session modes, shared binding and CSRF wiring, reserved-path validation, and related architecture decisions.
Cookie-mode guides and workflow references
doc/development/*, doc/user/*, doc/variants/03-bff-cookie.adoc, CLAUDE.md
Adds contributor and operator guidance for cookie configuration, key rotation, format changes, deployment behavior, and automated review workflow updates.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • cuioss/API-Sheriff#103: Introduces the shared SessionRecord and refresh foundation extended here for nonce propagation.
  • cuioss/API-Sheriff#118: Introduces the sealed-cookie pipeline modified here for nonce storage and format version 2.
🚥 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 is concise and accurately captures the main themes: cookie variant documentation and unique session identity changes.

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 29, 2026

Copy link
Copy Markdown

PR Reviewer Guide 🔍

(Review updated until commit 365b30e)

🧪 PR contains tests
🔒 No security concerns identified
⚡ No major issues detected

@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: 9

Caution

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

⚠️ Outside diff range comments (2)
api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/cookie/SealedSessionPayload.java (1)

54-78: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Enforce a nonblank nonce across both contracts. Null checks alone allow "" to become an identity input, defeating the nonce’s uniqueness guarantee for malformed reconstructed records.

  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/cookie/SealedSessionPayload.java#L54-L78: reject blank sessionNonce values in the canonical constructor.
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/session/SessionRecord.java#L74-L102: retain Optional.empty() for server mode, but reject present blank nonce values.

As per coding guidelines, “validate all inputs and outputs and use secure defaults.”

🤖 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/SealedSessionPayload.java`
around lines 54 - 78, Enforce nonblank session nonces in both record contracts:
in SealedSessionPayload’s canonical constructor, reject null or blank
sessionNonce values; in SessionRecord, preserve Optional.empty() for server mode
but reject any present blank nonce. Apply validation consistently to prevent
blank nonce values from entering identity inputs.

Source: Coding guidelines

api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/runtime/BffRuntime.java (1)

106-119: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Complete the required public API Javadocs.

  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/runtime/BffRuntime.java#L106-L119: add @since, thread-safety notes, and usage examples to the constructor documentation.
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/runtime/BffRuntime.java#L137-L147: add the required documentation elements to inert() and stepUpCoordinator().
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/runtime/BffRuntime.java#L157-L160: document isActive() with the required API metadata.
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/runtime/BffRuntime.java#L167-L181: complete sessionStage() and csrfDefence() documentation.
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/quarkus/BffRuntimeProducer.java#L127-L131: complete the public constructor documentation.

As per coding guidelines, every public or protected class and method must include these elements.

🤖 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/BffRuntime.java`
around lines 106 - 119, Complete the public API Javadocs: in
api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/runtime/BffRuntime.java
lines 106-119, document the constructor with `@since`, thread-safety guidance, and
usage examples; lines 137-147, add the required metadata to inert() and
stepUpCoordinator(); lines 157-160, document isActive(); and lines 167-181,
complete sessionStage() and csrfDefence(). Also complete the public constructor
Javadoc in
api-sheriff/src/main/java/de/cuioss/sheriff/gateway/quarkus/BffRuntimeProducer.java
lines 127-131, ensuring every public or protected API includes the required
metadata and examples where applicable.

Source: Coding guidelines

🤖 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/SealedSessionCookieCodec.java`:
- Around line 100-104: Update the Javadoc for the version-2 payload in
SealedSessionCookieCodec to state that unseal() rejects version-1 cookies at the
explicit version != FORMAT_VERSION guard before Cipher#doFinal, rather than
attributing rejection to GCM authentication; preserve the fail-closed and
re-login behavior description.

In `@CLAUDE.md`:
- Around line 136-141: Update the automated review comment workflow in the
“Handle the automated review comments from all three reviewers” section to
require running both “verify -Ppre-commit” and “verify” successfully with zero
errors and warnings before every bot-fix commit. Preserve the existing fix,
commit, push, reply, and resolve sequence, and reference the repository’s
established pre-commit guidance if appropriate.

In
`@doc/adr/0019-Reserved_BFF_paths_bypass_the_url-parameter_value_pipeline_and_the_cookie-header_length_relaxation_is_gateway-wide.adoc`:
- Around line 79-82: Update the stage policy description to scope the raised
maximum header-value length exclusively to inbound Cookie header values. Remove
Set-Cookie from the stated relaxation unless a separate response-header
validator is explicitly covered by the codec contract, and preserve the
unaffected bearer-only and server-mode behavior.

In `@doc/architecture.adoc`:
- Around line 186-195: The session-resolution documentation incorrectly says
cookie mode has “no store at all.” Update the cookie-mode description to say it
has no session store while acknowledging that login or pending-authorization
state may still use the existing PendingAuthorizationStore created by
BffRuntimeProducer; leave the session-state resolution behavior unchanged.

In `@doc/development/bff-cookie.adoc`:
- Line 77: Update the format layout documentation to explicitly identify
version(1) as a one-byte field and indicate that its current value is 2,
avoiding ambiguity with the format version guidance below. Keep the remaining
field sizes and layout unchanged.

In `@doc/user/bff-cookie.adoc`:
- Around line 57-58: Update the BFF cookie documentation to remove the claim
that the gateway ignores the oidc block unless a route’s effective auth is
require: session. Describe activation using the actual
BffRuntimeProducer.isBffMode() inputs: the global OIDC redirect_uri and
recognized session.mode, without asserting a route-auth prerequisite.

In `@doc/variants/03-bff-cookie.adoc`:
- Around line 195-211: Update the earlier portability claim in
doc/variants/03-bff-cookie.adoc to apply only to established-session requests,
replacing the broader “any request” wording while preserving the existing
explanation that login redirects and callbacks require the same instance.
- Around line 176-184: Update the cookie-session documentation to remove claims
about splitting oversized refresh or ID tokens into separate cookies, since the
SealedSessionCookieCodec/CookieSessionBinding path rejects over-budget seals.
Document reducing token size or switching to server mode as the supported
alternatives.
- Around line 154-155: Update the earlier Cookie Design key-management table to
match the optional encryption_key contract documented in the configuration
example: allow startup key generation when SHERIFF_SESSION_KEY is omitted, while
preserving the previous_key decrypt-only rotation behavior. Ensure the table no
longer states that a single environment-supplied shared key is required.

---

Outside diff comments:
In
`@api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/cookie/SealedSessionPayload.java`:
- Around line 54-78: Enforce nonblank session nonces in both record contracts:
in SealedSessionPayload’s canonical constructor, reject null or blank
sessionNonce values; in SessionRecord, preserve Optional.empty() for server mode
but reject any present blank nonce. Apply validation consistently to prevent
blank nonce values from entering identity inputs.

In
`@api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/runtime/BffRuntime.java`:
- Around line 106-119: Complete the public API Javadocs: in
api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/runtime/BffRuntime.java
lines 106-119, document the constructor with `@since`, thread-safety guidance, and
usage examples; lines 137-147, add the required metadata to inert() and
stepUpCoordinator(); lines 157-160, document isActive(); and lines 167-181,
complete sessionStage() and csrfDefence(). Also complete the public constructor
Javadoc in
api-sheriff/src/main/java/de/cuioss/sheriff/gateway/quarkus/BffRuntimeProducer.java
lines 127-131, ensuring every public or protected API includes the required
metadata and examples where applicable.
🪄 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: 955e2418-60c7-4599-899e-64ce1a12b8f6

📥 Commits

Reviewing files that changed from the base of the PR and between d8a18d9 and f25cd34.

📒 Files selected for processing (23)
  • CLAUDE.md
  • 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/refresh/TokenRefreshCoordinator.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/runtime/BffRuntime.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/bff/session/SessionRecord.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/quarkus/BffRuntimeProducer.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/session/InMemorySessionStoreTest.java
  • doc/README.adoc
  • doc/adr/0018-BFF_session_mode_is_one_SessionBinding_seam_behind_a_fixed_reserved-path_and_CSRF_model.adoc
  • doc/adr/0019-Reserved_BFF_paths_bypass_the_url-parameter_value_pipeline_and_the_cookie-header_length_relaxation_is_gateway-wide.adoc
  • doc/architecture.adoc
  • doc/development/README.adoc
  • doc/development/bff-cookie.adoc
  • doc/user/README.adoc
  • doc/user/bff-cookie.adoc
  • doc/variants/03-bff-cookie.adoc

Comment thread CLAUDE.md
Comment thread doc/architecture.adoc
Comment thread doc/development/bff-cookie.adoc Outdated
Comment thread doc/user/bff-cookie.adoc Outdated
Comment thread doc/variants/03-bff-cookie.adoc
Comment thread doc/variants/03-bff-cookie.adoc
Comment thread doc/variants/03-bff-cookie.adoc
cuioss-oliver and others added 2 commits July 29, 2026 08:28
The cookie-mode per-session nonce is minted from a static final SecureRandom
on the login path. GraalVM initializes static state at build time by default,
so the class initializer baked a seeded SecureRandom into the image heap and
native-image failed with 'Detected an instance of Random/SplittableRandom
class in the image heap'.

Register the class with --initialize-at-run-time, matching the three sibling
BFF classes that already hold a static SecureRandom for the same reason.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EfShpgB9HbUSf9n6zhP9r
…fix its tamper helper

Two fixes in the cookie-session IT:

1. sealedCookieValueCarriesNoReadableToken asserted the sealed cookie's
   leading version byte is 1. FORMAT_VERSION is now 2, so assert 2.

2. tamperedCookieIsUnauthenticatedNeverServerError used the base64-padding-bit
   tamper helper that PR #121 replaced in BffCookieStatelessnessIT but left in
   place here. Flipping the final base64url character is not reliably a
   mutation: when the sealed length is not a multiple of three that character
   carries unused padding bits which the decoder discards, so the value
   re-decodes identically and the peer correctly answers 200. Adding the
   9th sealed-payload field shifted the length so the degenerate case became
   deterministic rather than the ~4% flake #121 described. Apply the same
   decoded-GCM-tag mutation the sibling IT already uses.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EfShpgB9HbUSf9n6zhP9r

@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.

Caution

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

⚠️ Outside diff range comments (1)
api-sheriff/src/main/resources/application.properties (1)

49-55: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Make native-image runtime-initialization coverage authoritative.

The current CookieSessionBinding entry is correct, but this remains a hand-maintained list that must mirror source declarations of static final SecureRandom. A future BFF class can be omitted silently, causing native-image failures or build-time-captured randomness. Use a verified package-level rule where safe, or generate the option from the authoritative source set.

As per path instructions, a hardcoded list that must mirror a set defined elsewhere must be derived at build or run time.

Also applies to: 63-66

🤖 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/resources/application.properties` around lines 49 - 55,
Replace the hand-maintained SecureRandom class list in the native-image
runtime-initialization configuration with an authoritative generated option or a
safe package-level rule covering every BFF class that declares static final
SecureRandom. Ensure future matching classes are included automatically while
preserving runtime initialization for CookieSessionBinding and the existing
native-image option format.

Source: Path instructions

🤖 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.

Outside diff comments:
In `@api-sheriff/src/main/resources/application.properties`:
- Around line 49-55: Replace the hand-maintained SecureRandom class list in the
native-image runtime-initialization configuration with an authoritative
generated option or a safe package-level rule covering every BFF class that
declares static final SecureRandom. Ensure future matching classes are included
automatically while preserving runtime initialization for CookieSessionBinding
and the existing native-image option format.

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: 3b70901a-39e7-46a6-b7cf-62db3528bff6

📥 Commits

Reviewing files that changed from the base of the PR and between f25cd34 and 0d386ea.

📒 Files selected for processing (2)
  • api-sheriff/src/main/resources/application.properties
  • integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/BffCookieSessionIT.java

…xist

The earlier version of step 8 claimed benchmark.yml is the only genuinely
post-merge workflow. That was derived from grepping .github/workflows/ and
finding no deploy-snapshot, but deploy-snapshot is a job of the reusable
cuioss-organization/reusable-maven-build.yml that maven.yml delegates to, so
it never appears in this repo's workflow files.

maven.yml triggers on push to main, and deploy-snapshot is skipped on pull
requests but runs on that push. It is therefore a real post-merge job — and
unlike benchmark.yml it is NOT attached to the PR, so it has to be looked up
by merge commit. Step 8 now covers both, and names the distinction that
makes the second one easy to miss.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EfShpgB9HbUSf9n6zhP9r
@cuioss-oliver

Copy link
Copy Markdown
Collaborator Author

Triage dispositions

In reply to comment_id: PRR_kwDOPatrT88AAAABHl-jFw

Both outside-diff items reviewed; split verdict. (1) Nonblank nonce - ACCEPTED, will be addressed by TASK-11 on this branch. Verified: SealedSessionPayload:73-83 and SessionRecord:92-103 guard sessionNonce with a null check only, so an empty string is accepted; since the nonce keys CookieSessionBinding.derivedIdentity(), a blank value would silently degrade the identity back to the colliding pre-fix shape rather than failing. We will reject null-or-blank in SealedSessionPayload and reject only a PRESENT-but-blank value in SessionRecord, keeping Optional.empty() valid for server mode, with tests covering both rejections plus the absent-is-still-valid server-mode case. (2) Public API Javadocs - DECLINED. Per-method @SInCE / thread-safety / usage-example blocks are not this codebase's convention: @SInCE is applied at TYPE level only, uniformly, including in files this PR does not touch (GatewayEdgeRoute carries exactly one @SInCE, SealedSessionCookieCodec's three are all type-level). Applying it per-method to BffRuntime alone would make that one class inconsistent with the whole codebase, and applying it everywhere is a project-wide sweep well outside this PR. Usage examples on single-expression accessors such as isActive() and csrfDefence() would be noise. BffRuntime's public members already carry full @param/@return/@throws documentation.

In reply to comment_id: PRR_kwDOPatrT88AAAABHmbe6A

Valid long-term maintainability point, declined for this PR. Two reasons. (a) There is no live gap: we swept the module and found exactly four 'static final SecureRandom' declarations - SessionRecord:85, CookieSessionBinding:91, PendingAuthorizationRecord:68, RpInitiatedLogout:61 - and all four are registered, so the current list is complete and correct. The one class that was genuinely missing (CookieSessionBinding) was caught by this PR's own native-image CI failure and fixed in b6762c5. (b) Neither proposed derivation is a small change. The four classes live in four different packages (bff.pending, bff.session, bff.logout, bff.cookie), so the only covering package rule is the prefix de.cuioss.sheriff.gateway.bff, which would force runtime initialization for the entire BFF package - a materially larger native-image blast radius than the four targeted entries, not a safe like-for-like swap. Generating the flag from the source set means new build-time machinery, which is a design change requiring maintainer sign-off. We are tracking this as a follow-up rather than reshaping the native-image configuration inside a docs/ADR PR.

In reply to comment_id: IC_kwDOPatrT88AAAABMM6InQ

Acknowledged - PR-Agent's reviewer guide reports tests present, no security concerns and no major issues, so there is no actionable request here. Noted; no change required.

cuioss-oliver and others added 4 commits July 29, 2026 10:19
…iant set

Fixes six CodeRabbit findings on PR #125 where the Variant 3 documentation contradicted the shipped implementation:

- architecture.adoc: cookie mode has no *session* store, but PendingAuthorizationStore.InMemory is assembled in both modes
- development/bff-cookie.adoc: wire-format sizes now use the codec's own nB notation, so version(1B) no longer reads as format version 1
- user/bff-cookie.adoc: BFF activation is (recognised session.mode AND redirect_uri), never route effective auth
- variants/03-bff-cookie.adoc: key-management row reconciled with the optional encryption_key contract
- variants/03-bff-cookie.adoc: removed the non-existent cookie-splitting claim; over-budget seals fail
- variants/03-bff-cookie.adoc: statelessness claim narrowed to established sessions, cross-referencing the login-affinity subsection
…it gates

CodeRabbit finding e23e61 on PR #125: Git Workflow step 6 told the agent to fix-and-commit for a valid bot comment without naming the repository's own commit precondition, so the bot-fix path could read as exempt from the CRITICAL Pre-Commit Process gates. Adds a single cross-referencing clause; the canonical commands stay single-sourced in that section.
Two CodeRabbit findings on PR #125:

- 997d43: the FORMAT_VERSION javadoc named GCM authentication as the v1-rejection mechanism. unseal() rejects a foreign version at an explicit guard before the Cipher is constructed, so decryption is never attempted; the associated-data binding is an additional property, not the gate. Javadoc-only correction — unseal()'s control flow and guard order are unchanged.
- aae09b: both canonical constructors guarded sessionNonce with a null check only, so the empty string was accepted and would silently degrade the derived session identity back to the colliding pre-nonce shape. SealedSessionPayload now rejects null and blank; SessionRecord keeps the nonce Optional (absent stays the valid server-mode shape) and rejects only a present-but-blank value. Neither rejection message carries the nonce.
Sonar java:S6213 (MAJOR) — 'record' is a Java restricted identifier, so a
method named record() trips the restricted-identifier naming rule. The helper
was introduced by the SessionRecordNonceContract tests in 8483687.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EfShpgB9HbUSf9n6zhP9r
@cuioss-oliver
cuioss-oliver added this pull request to the merge queue Jul 29, 2026
@cuioss-oliver
cuioss-oliver deleted the feature/plan-07b-bff-cookie-docs-adr branch July 29, 2026 10:14
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a manual request Jul 29, 2026
@cuioss-oliver
cuioss-oliver restored the feature/plan-07b-bff-cookie-docs-adr branch July 29, 2026 10:16
@cuioss-oliver cuioss-oliver reopened this Jul 29, 2026
@cuioss-oliver
cuioss-oliver added this pull request to the merge queue Jul 29, 2026
Merged via the queue into main with commit 984d6d2 Jul 29, 2026
53 of 56 checks passed
@cuioss-oliver
cuioss-oliver deleted the feature/plan-07b-bff-cookie-docs-adr branch July 29, 2026 11:42
cuioss-oliver added a commit that referenced this pull request Jul 29, 2026
PR #125 landed ADR-0018 and ADR-0019 while this branch was queued, so the
number this ADR was authored under is taken. The rebase was conflict-free
because the filenames differ, which is exactly why the collision had to be
caught by inspection rather than by git.

Co-Authored-By: Claude <noreply@anthropic.com>
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