Skip to content

fix(users): re-check the session user's status on every request - #38469

Open
jcastro-dotcms wants to merge 20 commits into
mainfrom
revalidate-session-user-status
Open

jcastro-dotcms wants to merge 20 commits into
mainfrom
revalidate-session-user-status

Conversation

@jcastro-dotcms

Copy link
Copy Markdown
Member

Summary

The user a session belongs to is now resolved through a new SessionUserResolver that reads the current user record (cache-backed) on every request, instead of the User copy stored in the session at login. A user that is no longer active, or no longer exists, resolves to no user and the session is signed out. Making a user inactive, or deleting the user, also ends that user's tracked sessions on the node and rotates the user's login token, and the Maintenance session tools rotate the login tokens of the users whose sessions they end.

Changes

  • SessionUserResolver (new, com.dotcms.cms.login): resolves the session user from USER_ID (or the stored User copy), re-checks the record through UserAPI.loadUserById, signs the session out (user id, cached copy, Login As attributes) when the user is missing or inactive, refreshes the cached copy when the record changed, and checks the administrator behind a Login As session as well. On a lookup error the cached copy is dropped for that request and the user id stays, so the next request checks again. ALLOW_INACTIVE_ACCOUNTS_TO_LOGIN is honoured.
  • PortalUtil.getUser(HttpSession) delegates to the resolver; getUser(HttpServletRequest) resolves a request-level USER_ID through it as well. CategoriesWebAPI uses PortalUtil.getUser(request) instead of its own session read.
  • UserAPIImpl.save: when a stored active user is saved as inactive, the login token is rotated (invalidateLoginTokens, the new token copied to the saved object) and the user's tracked sessions are ended after commit. delete ends the tracked sessions. UserFactory.loadStoredUser reads the stored record without the cache, because the object being saved can be the cached instance. The permission and validation errors of save now drop the possibly modified cached instance.
  • SessionMonitor: public track(HttpSession) and endSessionsOf(userId) (ends every tracked session of that user, including Login As sessions where the user is the principal, with the remember-me flag set so the cookie is not reissued; a session that fails to end is forgotten and the rest still end). Sessions are tracked from attributeAdded / attributeReplaced on USER_ID, so every login path registers the session before the next request.
  • MaintenanceResource: kill-session and kill-all-sessions rotate the login tokens of the session's user and, for a Login As session, of the principal. The listing shows a session whose user can no longer be resolved under the stored user record instead of the anonymous user. openapi.yaml regenerated for the updated descriptions.

Tests

  • Unit (dotcms-core, 40 tests): SessionUserResolverTest (new, 24), SessionMonitorTest (15), UserAPIImplTest (new, 1).
  • Integration, as new methods on existing registered classes (no suite change): UserWebAPIImplTest (user made inactive or deleted after login is no longer the logged-in user), WebResourceIntegrationTest (back-end session answered 401 after the user is made inactive or deleted), LoginTokenInvalidationTest (token rotated and tracked session ended on deactivation and deletion; unchanged on an ordinary save and on reactivation; a real doActionLogin registers the session), MaintenanceResourceIntegrationTest (kill-session and kill-all reject the affected users' tokens and keep the caller's), UserResourceIntegrationTest (Login As keeps working with an active target; the session is signed out when the impersonated user or the administrator is made inactive). All green locally.

Risk

  • Behaviour change: a session whose user is made inactive or removed is signed out on its next request and stays signed out after reactivation (the person logs in again). Kill-all-sessions and kill-session now also invalidate the login tokens of the affected users, so their persistent logins on other clients end too; a caller whose other session is killed loses its own token as well.
  • The session listing no longer reports a session of an inactive user as the anonymous user.
  • Ending the tracked sessions is node-local (the SessionMonitor map is per JVM); the status re-check applies on every node. Ending sessions across nodes is a follow-up.
  • Rollback-safe: no schema, index or REST contract change (descriptions only).

Checklist

  • Tests
  • Translations
  • Security Implications Contemplated (add notes if applicable)

Fixes dotCMS/private-issues#902

🤖 Generated with Claude Code

Resolve the user of a session through a new SessionUserResolver that reads
the current user record (cache-backed) instead of trusting the copy stored at
login. A user that is no longer active, or no longer exists, resolves to no
user and the session is signed out, so REST, the back-end UI, DWR and the
Velocity tools all see the change on the next request, on any node. The
existing ALLOW_INACTIVE_ACCOUNTS_TO_LOGIN switch keeps inactive accounts
usable when it is on.

Saving a user as inactive, and deleting a user, end that user's tracked
sessions on the node after commit and rotate the user's login token, so a
persistent login cookie issued before the change is rejected. The Maintenance
session tools rotate the login tokens of the users whose sessions they end,
and the session listing keeps showing sessions whose user is now inactive.
Sessions are registered with the SessionMonitor as soon as the user id is
stored, so every login path is tracked before the next request.

Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
@jcastro-dotcms
jcastro-dotcms marked this pull request as ready for review November 6, 2026 14:55
…er-status

# Conflicts:
#	dotcms-integration/src/test/java/com/dotcms/rest/WebResourceIntegrationTest.java
@github-actions github-actions Bot added the Area : Backend PR changes Java/Maven backend code label Nov 6, 2026
@claude

claude Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @jcastro-dotcms's task in 7m 21s —— View job


Code Review — fix(users): re-check the session user's status on every request

I reviewed the session-revalidation change set across its touched backend files:
SessionUserResolver (new), PortalUtil.getUser, SessionMonitor, UserAPIImpl.save/delete,
UserFactoryImpl.loadUserById/loadStoredUser, MaintenanceResource (_sessions list / kill /
kill-all), LoginServiceAPIFactory.doActionLogin, and UserResource/UserResourceHelper Login As.

New Issues

None found. I traced every state transition I could reach from the diff (session resolve → sign-out,
Login As principal check, the request-id-copy removal in PortalUtil, deactivation-on-commit in
UserAPIImpl.save, and the token-before-end ordering in MaintenanceResource) and could not construct
an input that produces a wrong result from code introduced here.

A few things I checked specifically and found correct, not bugs:

  • PortalUtil.getUser(req) precedence — the session user is still resolved before the request's
    USER_ID copy is consulted (PortalUtil.java:426-436), matching the old precedence; a request-id
    that equals the session's own id is removed rather than re-resolved, so a signed-out session is not
    resurrected via resolveActive. A genuinely different request-id (login filter) still resolves.
  • UserAPIImpl.save deactivation gate — storedUser is read from the data source only when the
    incoming user is inactive, and deactivates(...) requires stored.active && saving.!active
    (UserAPIImpl.java:480-489), so re-save-active, create-inactive, and re-activate do not trigger a
    token rotation / session kill. endTrackedSessionsOnCommit fires on the outermost commit, so a
    rolled-back deactivation ends no sessions.
  • MaintenanceResource kill-all — userIds.removeAll(invalidatedUserIds) can empty the set for a
    user already handled, but invalidateLoginTokens(emptySet) is a no-op and the session is still
    ended/counted, so multiple sessions of one user are all terminated with the tokens rotated once.
  • MaintenanceResource.sessionUser reads the stored record directly instead of
    PortalUtil.getUser(session), deliberately avoiding a listing read that would sign the session out
    and strip the ids the kill tools need (MaintenanceResource.java sessionUser javadoc) — correct.

Existing

  • 🟡 UserAPIImpl.save evicts the cache with the raw userId on a failed permission/validation
    (UserAPIImpl.java:487). UserCacheImpl.remove re-reads the entry and also evicts by the canonical
    user.getUserId()/email, and in this flow userToSave.getUserId() is the canonical stored id, so
    the eviction hits. It would only miss on a space-padded id, which this path does not produce — so
    it is not reachable today, just one more spot that depends on ids being pre-trimmed. Non-blocking.

Resolved

  • ✅ Prior concern: a space-padded / mixed-case session USER_ID creating a stale user-cache alias or a
    session that the kill tools can't match. This is addressed from several directions now and I could not
    reproduce it: the form-login path stores the canonical user.getUserId() (LoginServiceAPIFactory.java:408,
    commit e118275) and Login As stores loginAsUser.getUserId() (UserResourceHelper.java:248);
    UserFactoryImpl.loadUserById trims the cache key and the data-source param
    (UserFactoryImpl.java:165-191); ChainableCacheAdministratorImpl lower-cases every key
    (:274,293,311); and SessionMonitor.belongsTo matches with trim().equalsIgnoreCase(...)
    (SessionMonitor.java:241-250). Sessions evicted from the bounded tracked map are still caught by the
    per-request status check in SessionUserResolver.resolve, which is the documented backstop.

Test coverage looks proportional to the change (new SessionUserResolverTest, PortalUtilTest,
UserAPIImplTest, LoginTokenInvalidationTest, and expanded SessionMonitorTest /
MaintenanceResourceIntegrationTest / UserResourceIntegrationTest), including the mixed-case /
post-deactivation cases.

Verdict: No blocking issues introduced by this change set.

jcastro-dotcms and others added 2 commits November 6, 2026 11:10
The session listing resolved each session's user through the regular
session user lookup, which drops the user ids of a session whose user is
no longer usable. The row then fell back to the anonymous user and a
later kill of that session had no user id left to rotate login tokens
for. The listing now reads the stored user id and loads the record as
it is, so the row names the right user and the session keeps its ids.

Two integration tests cover the listing of a session whose user was
made inactive and a user id set as a request attribute.

Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
A session that was signed out (its user id removed) and then invalidated
stayed in the SessionMonitor registry until its entry expired, because the
monitor only forgot sessions that still carried a user id when destroyed,
and the Maintenance kill tools invalidated sessions without going through
the monitor. Listing sessions then failed with 400 ("Session already
invalidated") for as long as the stale entry lived.

The monitor now forgets a session when it is destroyed, whatever its
attributes, and when its user id attribute is removed. The kill tools end
sessions through the monitor, which removes the entry before invalidating,
and the listing skips and forgets an entry whose session is already gone
instead of failing. The shared SessionUserResolver reads the UserAPI
through the APILocator on every call, so it follows a replaced API in
tests and at runtime.

Unit tests cover the monitor's new behavior and the resolver; integration
tests cover kill-all, single kill and listing with a signed-out session.

Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
Ending one session by token called the monitor without the guard that
kill-all has, so a session that had ended on its own between the listing
and the kill answered with a server error instead of as a kill. The ids
collected for the login token rotation also included the anonymous user
when a tracked session ran as that user, which rewrote the shared anonymous
record on every kill for no purpose, since that user holds no login token.

The single kill now treats an already-ended session as ended, and the
anonymous user id is dropped from the rotation set. Two integration tests
cover both cases.

Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
@jcastro-dotcms

Copy link
Copy Markdown
Member Author

On the two items in the automated review (#38469 (comment)), both are intentional and verified:

  • A Login As session ends when the impersonated user is made inactive; the administrator logs in again. Documented in the description and covered by UserResourceIntegrationTest.
  • Deactivating your own account, or the user or administrator of the Login As session you are calling from, returns the saved user normally; nothing on the way out reads the ended session. Checked on a running instance for the three cases.

@jcastro-dotcms

Copy link
Copy Markdown
Member Author

On the remaining item of the automated review (#38469 (comment)), the per-request lookup after a cache flush:

  • The lookup is new on the session path on purpose: before this change the session returned the User copy stored at login and asked the cache only when that copy was missing. That stored copy is what went stale, so the current record is now read through the user cache on every request, which is a cache hit in normal operation.
  • The cost of a miss is bounded: UserFactoryImpl.loadUserById does one select by user id and caches the result, so after a flush the extra load is one read per distinct active user, plus duplicates only for requests of the same user arriving at the same instant. Other per-request lookups behave the same way after a flush, so no code change is planned for this.

A session kill ended the session first and then invalidated the login
tokens of its users, logging a failure of that second step at WARN. The
caller got a success while the user's login cookie still worked, and a
retry found no session to kill. Both kill tools now invalidate the tokens
first and only then end the session: the single kill lets the failure
reach the caller with the session left as it was, kill-all leaves that
session out of the count for a later call. The anonymous user id is now
excluded ignoring case, as the session monitor compares it.

Also from the review: the resolver's constructors are no longer public
(the shared instance is the only entry from production code), a failed
user lookup is logged at ERROR with the user id and the exception, as the
code it replaced did, the save's cache eviction also covers a runtime
exception from the permission check, and the Javadoc of the id-only
lookup says why it has nothing to sign out. Four integration tests cover
the kill order, the anonymous id in another case and the eviction.

Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ AI review failed

Claude did not produce a review — the backend call errored before generating any output (provider: anthropic-bedrock, model: global.anthropic.claude-opus-4-8). This usually means the model has no Bedrock access grant in the target account, or the model ID is invalid — not a problem with this PR.

Run: #42869696695

…ts now see

killSession declares DotDataException since the login tokens are invalidated
before the session ends, so the six tests that call it directly have to declare
it too. The integration test module did not compile on CI.

Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
@jcastro-dotcms jcastro-dotcms added the PR : dotbot review Trigger dotbot AI code review and the post-merge QA test plan label Nov 10, 2026
@github-actions

github-actions Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

dotbot code review:

Workflow run 43050845052 — attempt 1

  • Reviewer: panel (medium)
  • Reviewed head: 36e1325e2ab6e4bb5cfb4883cb62f803cc38d3e8
  • Overall: patch is incorrect
  • New findings this run: 3
  • Prior unresolved dotbot findings still relevant: 0
  • Active findings total: 3

openai/gpt-6.1-terra: Object-only sessions escape the new termination mechanism and can expose an unvalidated user after a lookup failure. A concurrent lookup can also leave a deleted user cached after commit.
Security: Reviewed session resolution, legacy login tracking, template fallbacks, deletion cache invalidation, and maintenance token handling. Runtime tests were not executed.

anthropic/claude-haiku-5.5: F1 is retained: the legacy form login stores only the CMS_USER object, so those sessions are never tracked, ended, or status-checked, contrary to the PR's stated guarantee. F2 is rejected because the unvalidated CMS_USER read predates this patch.
Security: Reviewed the legacy form login path (LoginFactory.doLogin), SessionMonitor tracking, CMSUserTool.getLoggedInUser, and the resolver's lookup-error handling. Legacy-login sessions escape deactivation termination and status re-checks on the CMS_USER path. Not covered: runtime execution of unit and integration tests, and cross-node behaviour, which the PR documents as node-local.

Tip: comment with "/dotbot address comments" to attempt automated fixes for unresolved review threads.

reviewed by dotbot · panel · medium

Participants: openai/gpt-6.1-terra, anthropic/claude-haiku-5.5
Decision policy: unanimous
Reviewed base: 18964813087df13ee7849e0d8123ad3ed6467a71

…saction

A copy of the user cached before the commit, as a concurrent lookup would
leave behind, is evicted once the delete commits, so a session of that user
that the delete did not reach resolves to no user afterwards. The test
documents the guarantee the existing cache administrator already gives.

Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

dotbot code review:

Workflow run 43055504781 — attempt 1

  • Reviewer: panel (medium)
  • Reviewed head: f1cc4473911cd48690345f065b97ba00027772a2
  • Overall: patch is incorrect
  • New findings this run: 2
  • Prior unresolved dotbot findings still relevant: 0
  • Active findings total: 2

openai/gpt-6.1-terra: A retained administrator cookie can undo the intended forced sign-out, and WebSocket authentication can bypass the new Login As principal check on lookup failure.
Security: Reviewed Login As termination, cookie auto-login, resolver error handling, and WebSocket authentication. Runtime tests were not executed.

anthropic/claude-haiku-5.5: F1 is rejected. Deactivating the impersonated user ends the Login As session and flags it so its remember-me cookie is not reissued. The administrator's remaining cookie authenticates the administrator as themselves, not as the deactivated target, so it gives no escalation. No retained or new actionable defects remain.
Security: Reviewed the Login As termination path in UserAPIImpl.save and SessionMonitor.end/endSessionsOf. Ending and flagging the session prevents its remember-me cookie from being reissued. A remaining administrator cookie authenticates only the administrator. Coverage limits: no tests were run, DefaultAutoLoginWebInterceptor.java was not readable, and cross-node termination is documented as node-local.

Tip: comment with "/dotbot address comments" to attempt automated fixes for unresolved review threads.

reviewed by dotbot · panel · medium

Participants: openai/gpt-6.1-terra, anthropic/claude-haiku-5.5
Decision policy: unanimous
Reviewed base: 18964813087df13ee7849e0d8123ad3ed6467a71

The WebSocket configurator asked the session for its user and, when that gave
none, loaded a user straight from the session's user id without the status
check the session resolution applies. That fallback dates from when the session
only handed back its cached user object; the resolution now serves a session
that holds only the user id, so the fallback is removed and a session that
yields no user for a request yields no user on the socket either.

Tests: DotCmsWebSocketConfiguratorTest (new): user id only session, Login As
session whose administrator cannot be read, lookup error, no session user.

Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

dotbot code review:

Workflow run 43059849476 — attempt 1

  • Reviewer: panel (medium)
  • Reviewed head: 8331de53121edf364e4c0414620c6e29e35050bc
  • Overall: patch is incorrect
  • New findings this run: 2
  • Prior unresolved dotbot findings still relevant: 0
  • Active findings total: 2

openai/gpt-6.1-terra: The retained wrapper issues can restore a rejected impersonated identity or leave Login As state in a subsequent session.
Security: Reviewed session resolution, Velocity wrappers, Login As cleanup, session tracking, token invalidation, and WebSocket authentication. Runtime tests were not executed.

anthropic/claude-haiku-5.5: Both ledger issues are retained. VelocityRequestWrapper.removeAttribute refuses USER_ID through NOT_SETTABLE_ATTRIBUTES, so the copied id survives the failed cleanup and a later resolveActive call can restore the impersonated user. VelocitySessionWrapper refuses removal of PRINCIPAL_USER_ID and LoginAsSession.SESSION_KEY, so sign-out through a wrapped session leaves Login As state behind.
Security: F1 is an authorization gap on Velocity-wrapped requests: a Login As session refused for principal rejection can resolve the impersonated user again within the same request. F2 leaves Login As attributes after wrapped-session sign-out. Coverage limits: MainServlet's copy of the session id onto the request was not re-read, and no tests were run.

Tip: comment with "/dotbot address comments" to attempt automated fixes for unresolved review threads.

reviewed by dotbot · panel · medium

Participants: openai/gpt-6.1-terra, anthropic/claude-haiku-5.5
Decision policy: unanimous
Reviewed base: 18964813087df13ee7849e0d8123ad3ed6467a71

@github-actions

github-actions Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

dotbot code review:

Workflow run 43067372717 — attempt 1

  • Reviewer: panel (medium)
  • Reviewed head: 571f815f05116890519ea3489ddb4ee0c692a73d
  • Overall: patch is incorrect
  • New findings this run: 3
  • Prior unresolved dotbot findings still relevant: 0
  • Active findings total: 3
  • Findings remapped to general PR comments: 1 (missing file map=1)

openai/gpt-6.1-terra: The patch introduces an invalidated-session race and can partially revoke login tokens when a Login As session kill fails.
Security: Reviewed session resolution, maintenance token rotation, legacy login tracking, and affected upload callers. Runtime tests were not executed.

anthropic/claude-haiku-5.5: F3 stays retained: the legacy form login stores only CMS_USER, so SessionMonitor never tracks those sessions, contrary to the PR's stated guarantee. F1, F2 and F4 are rejected; F4 duplicates F3.
Security: Reviewed the legacy form login path at LoginFactory.java:272-275 and the CMS_USER readers in the repo. Legacy sessions escape termination and listing, but per-request status checks still run through PortalUtil.getUser. Tests were not run. Cross-node behaviour is documented as node-local and not assessed.

Tip: comment with "/dotbot address comments" to attempt automated fixes for unresolved review threads.

reviewed by dotbot · panel · medium

Participants: openai/gpt-6.1-terra, anthropic/claude-haiku-5.5
Decision policy: unanimous
Reviewed base: 59b89f6fe1af068820847866c9f394442958eaad

@github-actions

Copy link
Copy Markdown
Contributor

🟡 [P2] LoginFactory.java:274 Legacy form login sessions are never tracked

Posted as a general PR comment because the referenced file is not part of this PR's diff.
Original target: dotCMS/src/main/java/com/dotmarketing/cms/login/factories/LoginFactory.java:272-275.

Current code:

session.removeAttribute(com.dotmarketing.util.WebKeys.VISITOR);
// session stuff
session.setAttribute(WebKeys.CMS_USER, user);

Problem: This login stores only CMS_USER, never USER_ID, so SessionMonitor never tracks the session and deactivation cannot end it.

Fix:

session.setAttribute(WebKeys.CMS_USER, user);
session.setAttribute(com.liferay.portal.util.WebKeys.USER_ID, user.getUserId());

… one unit

When a session kill invalidates the login tokens of more than one user (the
session user and the user behind a Login As session), the invalidations now
run in one transaction: when one of them fails, none is kept, the session is
left as it was and the kill can be tried again. The ids are handled in a fixed
order, the session user first.

The request user resolution returns a user already resolved for the request
without reading the session again, as it did before the per-request check.

Tests: MaintenanceResourceIntegrationTest (principal invalidation fails, the
user's token still works), PortalUtilTest (new, cached user read first).

Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
@jcastro-dotcms

Copy link
Copy Markdown
Member Author

On the remapped finding about LoginFactory.java:274 (front-end login sessions never tracked): same point as the earlier thread on SessionMonitor.java:225. The front-end login stores only the user object and those sessions were not tracked before this PR either; they are still checked on every request through PortalUtil.getUser. Tracking them is handled in a follow-up, not in this PR.

@github-actions

github-actions Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

dotbot code review:

❌ Review failed

This attempt has no completed verdict. Earlier summaries do not represent this attempt.
No automatic approval is granted by this status.

Head: f3693006d9ddfb83bef144ee6b9ae59879e89001
Workflow run 43079965899 — attempt 1

@github-actions

github-actions Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

dotbot code review:

Workflow run 43079965899 — attempt 2

  • Reviewer: panel (medium)
  • Reviewed head: f3693006d9ddfb83bef144ee6b9ae59879e89001
  • Overall: patch is incorrect
  • New findings this run: 1
  • Prior unresolved dotbot findings still relevant: 0
  • Active findings total: 1

openai/gpt-6.1-terra: Mixed-case login IDs can populate a cache alias that survives deactivation, allowing a session on another node to resolve a stale active user.
Security: Traced the authentication endpoint, ID normalization, session creation, user cache keys, and token-rotation eviction. Runtime tests were not executed.

anthropic/claude-haiku-5.5: F1 is retained at P2 rather than P1. Login passes the raw login id to loadUserById and stores it in USER_ID, and UserCacheImpl.remove does not evict that raw-case alias on deactivation, so the per-request check can read a stale active record. Tracked local sessions are still ended case-insensitively, so the exposure is limited to untracked sessions on other nodes, which the PR documents as node-local.
Security: Reviewed login id handling in LoginServiceAPIFactory, cache key behaviour in UserFactoryImpl and UserCacheImpl, and eviction in save and updateRememberMeToken. Coverage limits: I did not trace end to end whether the login form delivers mixed-case ids, and no tests were run.

Tip: comment with "/dotbot address comments" to attempt automated fixes for unresolved review threads.

reviewed by dotbot · panel · medium

Participants: openai/gpt-6.1-terra, anthropic/claude-haiku-5.5
Decision policy: unanimous
Reviewed base: 59b89f6fe1af068820847866c9f394442958eaad

…eactivation

A session may name its user in a letter case other than the stored one. Resolving
it fills the user cache through that id, and the cache administrator keys its
entries without regard to case, so the eviction made when the user is saved as
inactive removes that entry as well. The new integration test keeps that visible:
an untracked session holding the id in upper case resolves while the user is
active, and resolves to no user once the user is made inactive, with no cache
entry left under the session's spelling.

Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

dotbot code review:

Workflow run 43254745968 — attempt 1

  • Reviewer: panel (medium)
  • Reviewed head: 3b2418960dd5cbcfaf752d45a6ed1cf9830dbee7
  • Overall: patch is incorrect
  • New findings this run: 1
  • Prior unresolved dotbot findings still relevant: 0
  • Active findings total: 1

openai/gpt-6.1-terra: A whitespace-padded login ID can leave a stale active-user cache entry and allow its session to survive deactivation.
Security: Reviewed authentication input, session tracking, cache keys and eviction, Login As, and token invalidation. Runtime tests were not executed.

anthropic/claude-haiku-5.5: F1 is retained: a whitespace-padded login id can keep a stale active user cache entry after deactivation, and the session tools do not match that session to its user. Severity is P2 because reachability of padded ids through the login form was not fully traced.
Security: A stale active-user cache entry under an untrimmed id could let a session that the session tools do not end keep resolving a deactivated user. Coverage limits: the login form's whitespace handling was not traced end to end, and no tests were run.

Tip: comment with "/dotbot address comments" to attempt automated fixes for unresolved review threads.

reviewed by dotbot · panel · medium

Participants: openai/gpt-6.1-terra, anthropic/claude-haiku-5.5
Decision policy: unanimous
Reviewed base: 59b89f6fe1af068820847866c9f394442958eaad

@github-actions

github-actions Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

dotbot code review:

Workflow run 43267350928 — attempt 1

  • Reviewer: panel (medium)
  • Reviewed head: 06acd07319215dc5274ae266c02f8a6bf2b907f5
  • Overall: patch is incorrect
  • New findings this run: 1
  • Prior unresolved dotbot findings still relevant: 0
  • Active findings total: 1

openai/gpt-6.1-terra: A padded login ID can bypass both the deactivation status check and node-local session termination.
Security: Reviewed authentication input, cache keys and eviction, session resolution, and session tracking. Runtime tests were not executed.

anthropic/claude-haiku-5.5: F1 is retained at P2. A padded login id is stored untrimmed in USER_ID and keys the user cache under the raw spelling, while deactivation evicts only the trimmed id and session matching does not trim, so a deactivated user's untracked session can still resolve. Reachability through the login form was not fully traced, which keeps this below P1.
Security: A padded user id stored at login can keep an active cache entry after deactivation, letting an untracked session on this node keep resolving the deactivated user. Reachability through the login form was not traced end to end. No tests were run.

Tip: comment with "/dotbot address comments" to attempt automated fixes for unresolved review threads.

reviewed by dotbot · panel · medium

Participants: openai/gpt-6.1-terra, anthropic/claude-haiku-5.5
Decision policy: unanimous
Reviewed base: c2c0facb2abcf4424caeae73aac745cab564e662

A user id typed with surrounding spaces reached the session as typed when the
company authenticates by user id, and a Login As request stored the posted id
as typed. The user cache keys its entries without regard to letter case but
keeps surrounding spaces, so a session with a padded id filled an entry that
the evictions made on a save or a login token change never reached, and the
session registry did not match that session to its user. Such a session kept
resolving the copy cached before the user was made inactive.

The login and Login As now store the id of the loaded user record, the user
factory trims the id before reading or filling the cache, and the session
registry ignores surrounding spaces when matching a session to a user.

Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

dotbot code review:

Workflow run 43287571346 — attempt 1

  • Reviewer: panel (medium)
  • Reviewed head: e118275df684093c5fee92425bab30a4c58ff980
  • Overall: patch is incorrect
  • New findings this run: 1
  • Prior unresolved dotbot findings still relevant: 0
  • Active findings total: 1

openai/gpt-6.1-terra: GraphQL can return a cached response associated with a session identity that failed the new status or principal check.
Security: Traced session lookup failures through WebResource authentication and GraphQL cache lookup, including Login As principal failures. Runtime tests were not executed.

anthropic/claude-haiku-5.5: Both ledger issues depend on GraphqlCacheKey reading the retained object-only USER copy after a lookup failure. An anonymous request computes a key without the sessionUser component, so it cannot read an entry keyed to that user, and no anonymous read of private data was found. A response computed while the session user was refused could be stored under that user's key, which is a cache-correctness concern rather than a leak. Confidence is moderate because runtime tests were not run and the other PR-thread items were not re-verified.
Security: Traced GraphqlCacheKey.from, sessionUserId, and the interceptor's use of the key in GraphqlCacheWebInterceptor around line 147. No anonymous read path to a credentialed cache entry was found. Coverage limits: runtime tests were not executed, and the other PR-thread items were not re-verified.

Tip: comment with "/dotbot address comments" to attempt automated fixes for unresolved review threads.

reviewed by dotbot · panel · medium

Participants: openai/gpt-6.1-terra, anthropic/claude-haiku-5.5
Decision policy: unanimous
Reviewed base: c2c0facb2abcf4424caeae73aac745cab564e662

+ "', treating the session as having no user for this request: "
+ e.getMessage(), e);
// the copy stays when it is the only thing that identifies the session
if (null != session.getAttribute(WebKeys.USER_ID)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 [P1] SessionUserResolver.java:130 Fail closed on lookup errors

Current code:

if (null != session.getAttribute(WebKeys.USER_ID)) {
    session.removeAttribute(WebKeys.USER);
}
return Optional.empty();

Problem: GraphQL can serve a cached private response using the retained session ID after authentication falls back to anonymous.

Fix:

throw new DotRuntimeException("Cannot validate session user", e);

Also fail the request when the Login As principal lookup returns UNKNOWN. Alternatively, key GraphQL responses by the authenticated user rather than session attributes.

This branch has not been deployed

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

Labels

AI: Safe To Rollback Area : Backend PR changes Java/Maven backend code PR : dotbot review Trigger dotbot AI code review and the post-merge QA test plan

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants