feat(server): persistent origin-scoped dashboard sessions via POST /api/auth/session - #3080
feat(server): persistent origin-scoped dashboard sessions via POST /api/auth/session#3080x3M3x wants to merge 4 commits into
Conversation
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
⏳ DRAFT
What to do
Review readiness checklist
0/4 boxes ticked. This pull request was already a draft. Its draft status will be preserved after every issue above is resolved. |
Ingwannu
left a comment
There was a problem hiding this comment.
Requesting changes on exact head 4a76dc9f434a1f7c4acea520b3a06482cedb174d. The opaque process-local session direction is useful, but this draft weakens an existing admission boundary and does not yet have a safe remote transport/origin contract.
-
src/server/management-auth.ts:300-302changes the existing session rule fromclaimedOrigin === session.originto!claimedOrigin || claimedOrigin === session.origin. A stolen session token can therefore authorize every GET/HEAD management read with only a matching request URL/Host and no GUI-origin arm. The GUI already sendsX-OpenCodex-GUI-Originfor session requests. Keep that header mandatory for safe methods too; only the CSRF token and browserOriginmay be mutation-only. Add a negative GET test with the session token but no/mismatched claimed origin. -
POST /api/auth/sessionis mounted on the ordinary listener for every configuration, even a non-loopback plain-HTTP hostname bind. The comment that “the credential, not the transport, carries the trust” is not valid for the first exchange: the raw long-lived admin token crosses that transport. Require an explicit persistence/remote-session opt-in and refuse non-loopback insecure origins by default, or retain a dedicated authenticated TLS/trusted-proxy boundary. Do not silently enable 12-hour localStorage credentials on every existing hostname deployment. -
TLS termination is currently broken even when the operator explicitly allowlists the external origin.
isAllowedManagementOriginaccepts an externalhttps://...Origin throughcorsAllowOrigins, butissueGuiSessionForAdminstoresmanagementRequestOrigin(req, config), derived from the server-observed request URL/Host (commonlyhttp://...behind a reverse proxy). The GUI then rejects the returned session because it does not equalwindow.location.origin. Bind to the exact validated browser Origin when present, with an explicit trusted-proxy policy, or reject this topology. Add an external HTTPS-Origin/internal-HTTP regression. -
handleGuiSessionEndpointsays the route is mounted only on a dedicated remote listener, whilesrc/server/index.ts:1058-1064mounts it on the regular listener and the PR body explicitly removes the dedicated listener. Correct the contract and updatestructure/05_gui-and-management-api.mdplus user docs for storage, expiry, logout/restart invalidation, secure transport, and opt-in behavior. Return the token response with explicitCache-Control: no-store. -
The readiness claim is not supported yet: repository hygiene/target checks are red and the author reports 248 changed-suite failures plus a full-suite watchdog expiry. Keep this Draft, rebase after the
2.39.0dev version bump, and obtain exact-head auth/server plus GUI tests, typecheck, privacy, builds/lint, and hosted matrix results before requesting security sponsorship.
리뷰 · 우선순위 54 / 80이 PR은 대시보드 로그인 한 번이 새로고침마다 다시 안 뜨게 하려는 칸입니다. 관리자 토큰을 브라우저에 안 남기고, 서버가 만든 불투명 세션만 12시간 동안 같은 출처(origin)에 붙입니다. 파일은 여섯입니다. 지금 HEAD에서 루프백 대시보드는 이미 짧은 GUI 세션이 있습니다. 서버 쪽은 클라이언트는 다만 문서와 게이트에 남은 티끌이 있습니다. 경로 handleGuiSessionEndpoint JSDoc - 전용 remote listener에만 붙는다고 적혀 있다. 실제는 일반 listener의 /api/auth/session이다 메인테이너의 판단이 필요한 지점
너의 추천 이 댓글은 grok-bot이 작성했습니다 |
|
Thank you for the detailed review. All five points are addressed in 56ba6f4:
One note: the PR description was reworded to drop a literal string that falsely tripped the screenshot requirement, since this PR changes no rendered interface pixels. |
Ingwannu
left a comment
There was a problem hiding this comment.
Re-reviewed exact head 56ba6f411e38d6eb7b51144bb16ccefcc365141e. The mandatory claimed-origin arm, TLS-terminator binding, no-store responses, stale contract text, and focused negative tests address the concrete blockers from my prior review. Two policy/lifecycle boundaries remain before sponsorship.
-
persistentSessionTransportAllowedenables every non-loopback HTTPS origin withoutallowRemoteDashboardSessions, treating an existingcorsAllowOriginsentry as consent to a new 12-hour bearer in localStorage. That allowlist previously meant only “this origin may call the management API”; upgrading must not silently widen it into persistent credential storage. MakeallowRemoteDashboardSessions === truethe explicit gate for every non-loopback persistent mint. Then separately require HTTPS, or a clearly documented private/tailnet plain-HTTP exception, inside that opted-in path. Loopback can retain its existing short-lived injected-session behavior without the persistent feature. -
The docs say “dashboard logout deletes it,” but the implementation only removes localStorage. The server-side bearer remains valid in
state.sessionsuntil expiry or process restart, so a copied token survives logout for up to 12 hours. Either add an authenticated, CSRF-protected session-revocation/logout operation that deletes the exact server record and have the GUI call it best-effort before local cleanup, or state explicitly that logout is client-local and does not revoke a stolen session. For a full-management bearer, server revocation is the preferable contract; add a regression that the revoked token no longer authorizes reads or mutations.
The browser-origin/claimed-header checks protect browser-origin use; they should not be documented as making a copied bearer non-replayable by a non-browser client. Keep the localStorage/XSS tradeoff explicit.
This branch is now six commits behind current dev@06d688087 (2.39.0), and exact-head hygiene/target are still red with no hosted runtime/GUI matrix. Keep it Draft, rebase after these changes, then run the required isolated auth/server tests, full GUI test/lint/build, docs build, typecheck, privacy scan, and hosted CI before requesting maintainer-sponsored.
…pi/auth/session Exchanges the admin token once for a 12-hour opaque, process-local session bound to the exact dashboard origin with a per-session CSRF token. The GUI persists only the opaque session (never the admin token) in origin-scoped localStorage and re-arms requests after refresh without re-prompting. Serves on the regular listener; no dedicated remote listener required.
- Require X-OpenCodex-GUI-Origin on safe methods again; only CSRF and the browser Origin stay mutation-only. - Gate POST /api/auth/session transport: loopback mints by default, allowlisted HTTPS is its own opt-in, non-loopback plain HTTP requires server.allowRemoteDashboardSessions. - Bind sessions to the validated browser Origin so TLS-terminating proxies bind to the external allowlisted origin; safe requests re-assert the binding via the claimed header when no Origin header is present. - Send Cache-Control: no-store on every /api/auth/session response; correct the mounted-on-ordinary-listener contract. - Document storage, expiry, invalidation, secure transport, and opt-in in structure and docs-site; add regression tests for each.
56ba6f4 to
8b41020
Compare
|
Rebased onto current Verified on the rebased head:
The review findings remain addressed: mandatory claimed origin on safe methods, explicit insecure-remote opt-in, TLS-terminating proxy origin binding, corrected route/docs contract, and |
|
Addressed the latest review boundaries in 0635b95 and pushed to the fork branch.
The PR remains draft pending hosted CI and maintainer security sponsorship. |
|
The latest hosted run confirms the code-side review work is complete on
No further code change is warranted from this result. Please have a maintainer/security reviewer apply |
Ingwannu
left a comment
There was a problem hiding this comment.
Re-reviewed exact head 0635b9549a0b26805085c7f6f9788ceda8fea99a. The two prior policy/lifecycle blockers are fixed in code: every non-loopback persistent mint now requires allowRemoteDashboardSessions === true, and a CSRF/origin-admitted revoke removes the server-side session so it cannot be reused.
I am keeping changes requested because the security contract and its coverage are now inconsistent with that implementation:
-
Update every stale contract statement.
issueGuiSessionForAdmin()still says an allowlisted HTTPS origin is its own opt-in.structure/05_gui-and-management-api.md,docs-site/.../management-api.md, anddocs-site/.../configuration/server.mdstill say loopback and allowlisted HTTPS mint without the flag, and describe the flag as plain-HTTP-only. The code now correctly requires the flag for every non-loopback origin. Document that gate first, then the additional HTTPS/CORS allowlist or explicitly risky plain-HTTP condition. This is an auth boundary, not cosmetic wording. -
The docs say “dashboard logout” deletes/revokes the session, but this branch does not expose or wire an explicit logout action.
clearToken()is reached only from 401 retry handling. Either wire the actual user logout path to clear and revoke, or describe the implemented lifecycle honestly as rejection/expiry cleanup rather than claiming a UI action that does not exist. -
Add GUI-level coverage proving the clear/logout path sends exactly one revoke request with the session token, claimed origin, and CSRF evidence, removes localStorage immediately, and never reuses that token afterward. The new server test proves the endpoint itself, but not the browser lifecycle claimed by the PR.
This head has diverged from current dev, remains Draft/hygiene-blocked, and has no exact-head cross-platform GUI/auth CI. Rebase after these changes; sponsorship and approval remain gated on that final exact head.
|
Pushed 3e8b06e with the remaining review fixes.\n\n- Corrected the auth contract in the JSDoc, structure SOT, management API docs, and server configuration docs: every non-loopback persistent session requires allowRemoteDashboardSessions: true, followed by the HTTPS/allowlist or explicitly risky private/plain-HTTP policy.\n- Clarified the actual GUI lifecycle: rejection-driven session clearing removes localStorage immediately and attempts best-effort server revocation; there is no separate dashboard logout action in this path.\n- Added GUI regression coverage proving exactly one revoke request carries the session token, claimed origin, and CSRF token, localStorage is removed immediately, and the rejected token is not reused.\n\nVerified: GUI persistent-session tests (6 pass), dashboard session endpoint tests (13 pass), typecheck, privacy scan, GUI build, and docs build. GUI lint could not load the repository's TypeScript oxlint plugin under the installed oxlint runtime; no source lint changes were made. Maintainer security sponsorship (maintainer-sponsored) and hosted exact-head CI remain required; the PR stays draft. |
Summary
Supersedes the session-persistence half of #2414; the dedicated-listener part of that PR is no longer needed now that the proxy supports hostname binds. #2414 is closed in favor of this PR.
Verification
Checklist
Review readiness checklist
This PR stays in draft until every box below is ticked. Tick all four boxes once the requirements are met:
All CI tests are green on my local testing.
I pushed my PR to the latest dev commit.
I resolved all correct Codex and CodeRabbit findings.
My PR is ready for review.