Skip to content

fix(oauth): require dashboard consent before Meta Muse keychain import - #5234

Closed
luvs01 wants to merge 2 commits into
lidge-jun:devfrom
luvs01:fix/oauth-muse-import-consent
Closed

luvs01 wants to merge 2 commits into
lidge-jun:devfrom
luvs01:fix/oauth-muse-import-consent

Conversation

@luvs01

@luvs01 luvs01 commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • /api/oauth/login admitted any holder of a management credential for every public OAuth provider. For meta-muse, starting login imports an existing credential from the user's macOS Keychain and persists it in OpenCodex — a credential move with vendor ToS implications that administrative access alone does not prove a person acknowledged.
  • Require the gui-session principal for meta-muse logins so the import can only start through the dashboard acknowledgement path; other providers are unchanged. Non-GUI callers receive 403 with code: "oauth_consent_required".
  • The gate keys on the server-resolved principal, not request headers — x-opencodex-gui-origin and the CSRF token are forgeable by an admin-token holder and are not evidence of consent.

Verification

  • bun test tests/oauth/oauth-public-surface.test.ts — 15 pass, including new coverage: undefined, admin-token, and gui-pair-capability principals each receive 403 oauth_consent_required; a gui-session request passes the gate and stops at 404 (unknown account) before any platform-specific import runs.
  • bun x tsc --noEmit — clean.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

@coderabbitai

coderabbitai Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

⚙️ Run configuration

Configuration used: Repository: lidge-jun/opencodex/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 5a7b8072-1a60-4c81-8019-eefea1d52ed3

📥 Commits

Reviewing files that changed from the base of the PR and between f781737 and d1691f0.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: lidge-jun/opencodex/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 0aa935ec-a098-4159-9bcf-b7f43837a598

📥 Commits

Reviewing files that changed from the base of the PR and between 2a2c01a and f781737.

📒 Files selected for processing (2)
  • src/server/management/oauth-account-routes.ts
  • tests/oauth/oauth-public-surface.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The management OAuth route now requires a gui-session principal for Meta Muse login requests. Tests verify rejection for other principals and continued route processing for GUI sessions.

Changes

Meta Muse OAuth authorization

Layer / File(s) Summary
Consent gate and route validation
src/server/management/oauth-account-routes.ts, tests/oauth/oauth-public-surface.test.ts
The route reads principal from ManagementContext. Meta Muse requests without a gui-session principal return HTTP 403 with code: "oauth_consent_required". Tests cover missing, admin, GUI-pair, and GUI-session principals.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: requiring dashboard consent before Meta Muse keychain import.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Create a new PR

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.

@github-actions github-actions Bot added the intake: hygiene-blocked Deterministic PR hygiene checks failed label Sep 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Deterministic hygiene checks failed.

  • unsponsored_surface — This changes an authentication, workflow, release-automation, or dependency surface. MAINTAINERS.md requires security review for these; ask a maintainer to apply maintainer-sponsored once they have reviewed it. Paths: src/server/management/oauth-account-routes.ts.

@github-actions github-actions Bot added the bug Something isn't working label Sep 20, 2026
@luvs01
luvs01 marked this pull request as ready for review September 20, 2026 01:34
@github-actions

github-actions Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

✅ READY

  • all PR quality gates passed.

Hygiene

Deterministic PR hygiene checks passed.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@github-actions
github-actions Bot marked this pull request as draft September 20, 2026 01:35
@lidge-jun

Copy link
Copy Markdown
Owner

리뷰 · 우선순위 67 / 80

이 PR은 Meta Muse 로그인을 막을 때, “관리 토큰만 있으면 된다”는 길을 닫습니다. Muse 로그인은 macOS 키체인에 있는 비밀을 읽어 OpenCodex에 넣습니다. 예전에는 /api/oauth/loginmeta-muse를 넣으면, 관리 자격(admin-token 등)만 있어도 그 가져오기가 시작됐습니다. 코딩 에이전트처럼 디스크의 관리 토큰을 읽을 수 있는 쪽도, 대시보드 경고 창을 거치지 않고 같은 길을 탈 수 있었습니다.

지금은 서버가 이미 정해 둔 principal을 봅니다. meta-muse이고 principalgui-session이 아니면 403을 주고, 코드는 oauth_consent_required입니다. 요청 헤더의 GUI 표시나 CSRF 글자는 보지 않습니다. 관리 토큰을 가진 쪽이 그 헤더를 가짜로 넣을 수 있기 때문입니다. 다른 OAuth 공급자는 그대로입니다. 테스트는 undefined / admin-token / gui-pair-capability는 403, gui-session은 문을 지나 없는 계정 404에서 멈춘다고 적습니다. 헤더를 일부러 가짜로 넣어서, 헤더만으로는 통과하지 못한다는 점도 보여 줍니다.

베이스는 dev입니다. 이 글을 쓰는 지금 dev 끝은 2a2c01a7102e입니다. 이 브랜치는 그 끝보다 커밋 5개가 뒤입니다. 겹치는 파일은 이 두 개뿐입니다. types.tsconfig.ts는 안 바꿉니다. 상태는 드래프트이고, 라벨은 bugintake: hygiene-blocked입니다. 게이트는 unsponsored_surface(인증 면)라서 메인테이너의 maintainer-sponsored가 필요합니다. 본문 준비 체크는 4/4입니다. 작성자가 적은 bun test(15통과)와 tsc는 여기서 다시 돌리지 않았습니다.

라인 - src/server/management/oauth-account-routes.ts:180 - 서버가 보는 것은 gui-session인가뿐이고, 경고 창의 체크박스를 눌렀는지는 모릅니다. 브라우저에 유효한 GUI 세션이 있으면, 개발자 도구로 같은 POST를 보내도 이 문은 통과합니다. 막는 대상은 “관리 토큰만 가진 쪽(에이전트 등)”이고, “세션이 있는 브라우저에서 창을 건너뛰는 것”까지는 막지 않습니다. management-auth.ts도 consent 길은 gui-session을 보라고 적혀 있어, 이 PR의 그 규칙과 맞습니다.

라인 - tests/oauth/oauth-public-surface.test.ts:84 - “문을 통과했다”는 신호를 없는 계정 404로 잡습니다. 나중에 reauth/accountId 검사가 바뀌면 이 신호가 흔들릴 수 있습니다. 지금 구조에서는 키체인 읽기 전에 멈추므로 안전합니다.

메인테이너의 판단이 필요한 지점

gui-session만으로 Muse 가져오기를 허용할지, 아니면 서버에 “경고를 확인했다”는 짧은 표시(일회 토큰 등)까지 둘지 정해 주세요. 위협이 “관리 토큰을 읽는 에이전트”면 지금으로 충분합니다. 다른 HIGH_RISK 공급자(예: anthropic)에도 같은 gui-session 문을 둘지는 이번 범위 밖이지만, 같은 패턴을 넓힐지 한 번만 정해 두면 좋습니다.

너의 추천

방향은 맞습니다. 관리 토큰으로 키체인 가져오기를 시작하던 구멍을 서버에서 닫고, 헤더가 아니라 principal을 보는 점도 기존 인증 설명과 같습니다. tip(2a2c01a7) 위로 rebase하고, 메인테이너가 보안 면을 본 뒤 maintainer-sponsored를 붙인 다음 Ready로 올리면 됩니다. 체크박스까지 서버에 남기려면 이 PR과 따로 이어서 하면 됩니다. types.ts/config.ts 분할과 무관해서 이 PR을 닫지 마세요.

이 댓글은 grok-bot이 작성했습니다

@luvs01

luvs01 commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator Author

This PR is held at the gate on unsponsored_surface: it touches src/server/management/oauth-account-routes.ts (authentication surface). The change only narrows Meta Muse import consent to the server-resolved gui-session principal; other OAuth providers are unchanged. Requesting maintainer review and the maintainer-sponsored label.

@github-actions github-actions Bot removed the intake: hygiene-blocked Deterministic PR hygiene checks failed label Sep 22, 2026
@github-actions
github-actions Bot marked this pull request as ready for review September 22, 2026 00:31
@luvs01

luvs01 commented Sep 22, 2026

Copy link
Copy Markdown
Collaborator Author

Consolidated into #5591 as a single related-function aggregate.

Source head: d1691f04a8118b46829eedd8229771a23173556b. Replacement head: bffb533baa1d54be42ee9114d5948f2fb875ef08.

This is a complete Meta-login feature consolidation, not removal through an unrelated types.ts/config.ts split. The complete merge-head contribution has stable patch ID ef98ff4a8e236591db2b2fd7aaada4815909a6b1, equal to source f781737 carried as 2251d3f. The replacement retains the server-resolved gui-session admission gate and forged-header refusal for local import/device/add-account/reauth, with other provider and direct CLI policies unchanged. Its 76-case pinned Bun verification includes the admission matrix and actual bounded device-reader integration. The review concern about preserving this distinct login fix is therefore retained in the focused replacement. Maintainer sponsorship/security review and readiness remain pending there; no per-checkbox consent proof is claimed.

Closing this duplicate standalone review entry as part of the requested consolidation after verifying coverage. This is not a merge or release claim; remaining integration checks and reviews are tracked on the replacement. Original branches are retained.

@luvs01 luvs01 closed this Sep 22, 2026
lidge-jun added a commit that referenced this pull request Sep 23, 2026
… fixes (#5619)

* fix(cursor): bound capability reads and buffered tool budgets (#5533)

Carries #5533 (and the closed #5233 it consolidates) onto current dev.

Co-authored-by: Epinephrine <luvs01@hanmail.net>

* fix(moonshot): bound normalized tool-schema expansion (#5547)

Carries #5547, which consolidates #5464 and the request-wide inline budget, onto current dev.

Co-authored-by: yeongjunyoo <47925973+yeongjunyoo@users.noreply.github.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Epinephrine <luvs01@hanmail.net>

* fix(moonshot): restore rejected inline budgets and charge nested growth once

A rejected sibling-reference expansion now restores the byte, node and expansion allowances it consumed, and outer growth no longer re-charges nested copies, so later independent expansions in the same request keep their allowance. Documents the provider-driven object type inference as a deliberate tradeoff and rewrites ADR-0355 in English.

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>

* feat(reasoning): consolidate replay, opt-in tag parsing, and summary policy (#5566)

Carries #5566, which consolidates #5449, #5205 and #5491, onto current dev. The provider guide keeps the current bridge replay paragraph and adds the inline-tag and summary paragraphs.

Co-authored-by: Joonsuh Park <trckstr4422@gmail.com>
Co-authored-by: Daniel Sjöstrand <16033062+Danielsjostrand1979@users.noreply.github.com>
Co-authored-by: alexph-dev <alexph-dev@users.noreply.github.com>
Co-authored-by: Yum-wu <1172989563@qq.com>

* fix: bound Fernet slot runs, Kiro error-body read, and skill-path line slice (#5310)

Carries #5310 onto current dev. The follow-up commit makes the Fernet run cap fail closed and moves the Kiro regression out of the capped stream suite.

* docs(reasoning): reconcile inline-tag whitespace contract

Interleaved inline-tag parsing preserves answer whitespace; only Kiro single-block mode drops the whitespace after its leading block.

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>

* fix(responses): fail closed on Fernet run overflow and keep the Kiro suite under its cap

A slot with more than 64 structurally valid Fernet runs is now treated as unreadable or omitted as a whole, so no unexamined tail reaches the provider as text. The bounded Kiro fallback error-body regression moves byte for byte into a registered sibling file, and the Kiro, Responses and inbound contracts document the new bounds.

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>

* fix(reasoning): scan inline think tags with a moving cursor

The parser copied, rescanned and reserved the whole remaining response after every block, so one upstream chunk carrying many short blocks cost quadratic work. It now scans each chunk from an offset and charges the translator budget only for retained carry: undecided leading input or a trailing tag fragment.

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>

* fix(reasoning): keep undecided leading whitespace incremental

Before the format was decided, every content delta rebuilt, trimmed and re-reserved the whole leading prefix, so a stream of one-character whitespace deltas cost quadratic work. Leading whitespace is now kept in segments whose bytes are reserved once and joined only when the format is decided or the stream flushes.

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>

* fix(meta-muse): consolidate login admission and bounded response handling (#5591)

Carries #5591, which consolidates the closed #5234 and #5432, onto current dev. The provider contract keeps the inline-tag paragraph and adds the Meta Muse admission paragraph.

Co-authored-by: Epinephrine <luvs01@hanmail.net>

* fix(claude-desktop): keep applied state consistent across profile edits (#5590)

Carries #5590, which consolidates the closed #5337, onto current dev.

Co-authored-by: Epinephrine <luvs01@hanmail.net>
Co-authored-by: luvs01 <luvs01@users.noreply.github.com>

* fix(claude-desktop): commit applied markers only over the observed baseline

Both Desktop writers, provider-change auto-apply and client sync, now capture the desired profile and its applied marker before the Desktop write and commit the new marker only if profile presence, content, fingerprint and timestamp are unchanged. A concurrent edit, deletion or newer marker keeps its state and the write reports a skipped marker. The provider-change path no longer saves a whole stale config snapshot.

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>

* fix(claude-desktop): commit profile edits against the persisted marker

The Desktop profile PUT built its response from an earlier snapshot and saved that whole snapshot, so a marker committed by another writer during the awaited state build could be replaced by an older one. The edit now commits in one persisted-config mutation that keeps the latest marker for unchanged content and answers 409 when the profile itself changed meanwhile. The Meta Muse overflow test now asserts that the bounded-body limit, not a generic failure, produced the error.

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>

* fix(claude-desktop): report an unreadable config separately from an edit conflict

A missing or invalid config now answers 500 with its reason; only a concurrent profile change or exhausted rebase answers 409.

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>

* feat(desktop): consolidate consent-based runtime takeover and ownership contracts (#5564)

Carries #5564, which consolidates #5459 and #5457, onto current dev. The review screenshot stays in the pull request description rather than the tree.

Co-authored-by: jun <bitkyc08@gmail.com>
Co-authored-by: sanggyulee <andy53295774@gmail.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(desktop): bind takeover stop to the approved runtime and fail closed

Desktop takeover re-resolves ownership immediately before stopping and passes the approved PID, endpoint, config home, CLI version and compatibility token to an opt-in guarded stop. The guard is checked under the ownership mutation lease before any manager or signal stop; the approved PID and endpoint must settle and the service manager must then be proven inactive, otherwise the stop answers approval-changed or manager-still-active and the desktop neither waits for silence nor claims. Unreadable or unparseable stop output is terminal as well. A second unreadable service-state read now blocks takeover, Windows managing-CLI discovery follows PATHEXT with file-only candidates and refuses command-interpreter metacharacters, the claim refusal test uses real sandbox state, and the runtime and desktop contracts record that the claim token is a consistency check rather than consent proof. Plain ocx stop is unchanged.

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>

* fix(desktop): keep plain stop entry points and format the takeover changes

Desktop exit keeps its plain runtime_stop::run entry while takeover uses run_approved, AttachPlan::Ask no longer carries an unread field, the Rust changes follow rustfmt, the plain CLI stop path keeps its literal outcome return, the stop source oracles follow the reader and outcome union that now include the two guarded refusals, and the runtime contract fits its 600-line budget.

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>

* fix(desktop): run takeover seam tests without tokio macros and harden manager and shim checks

The two async takeover seam tests now run on the shell runtime already used by the crate instead of tokio test macros, which this crate does not enable. Windows command-shim probes refuse command-interpreter metacharacters in every recorded argument as well as the executable, and the guarded stop re-inspects the service manager identity immediately before the manager command, answering approval-changed without stopping if it moved.

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>

* fix(responses): keep effort-based reasoning visible after routing

Final-route normalization recomputed hideThinkingSummary without the validated active-effort condition, so routed Chat and Kiro requests with an active effort and an omitted summary still hid raw reasoning. It now uses the same predicate as the parser; explicit "none" and requests without an active effort stay hidden.

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>

* fix(service): match the running CLI case-insensitively only on Windows

On case-sensitive filesystems a PATH executable that differs only in case is a different file, so it must get its own version probe instead of reporting the running CLI version.

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>

* fix(meta-muse): require the dashboard session for manual login codes

The manual-code continuation now applies the same dashboard-session admission as the login start, so a management token cannot advance a pending Meta Muse login.

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>

* fix(reasoning): reserve the joined leading-whitespace copy

Joining retained leading whitespace allocated a second copy outside the translator budget; the join is now reserved first and released once the segments are cleared.

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>

* fix(service): skip CLI probes for an absent runtime and treat failed systemd units as stopped

Resolve no longer spawns managing-CLI version probes when no runtime is live, since takeover is only offered for a live runtime. A systemd unit reported failed with no main PID is stopped, so a guarded stop that leaves it failed succeeds and a leftover failed unit does not block takeover.

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>

* fix(service): keep failed systemd units fail-closed and assess takeover only for a live runtime in tests

systemd can report failed before an automatic restart, so failed with no main PID is again treated as unknown rather than stopped. The resolve contract tests that assert ownership and takeover fields now use a live runtime, matching the skip of managing-CLI probes when no runtime is live.

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>

---------

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>
Co-authored-by: Epinephrine <luvs01@hanmail.net>
Co-authored-by: yeongjunyoo <47925973+yeongjunyoo@users.noreply.github.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Joonsuh Park <trckstr4422@gmail.com>
Co-authored-by: Daniel Sjöstrand <16033062+Danielsjostrand1979@users.noreply.github.com>
Co-authored-by: alexph-dev <alexph-dev@users.noreply.github.com>
Co-authored-by: Yum-wu <1172989563@qq.com>
Co-authored-by: luvs01 <luvs01@users.noreply.github.com>
Co-authored-by: sanggyulee <andy53295774@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants