Skip to content

fix(connections): admin-gate app profile updates + member account-profile rebind UI#812

Merged
buremba merged 3 commits into
mainfrom
fix/connection-profile-gaps
May 17, 2026
Merged

fix(connections): admin-gate app profile updates + member account-profile rebind UI#812
buremba merged 3 commits into
mainfrom
fix/connection-profile-gaps

Conversation

@buremba
Copy link
Copy Markdown
Member

@buremba buremba commented May 17, 2026

Summary

  • Server hole-fix. manage_connections.handleUpdate had no role check on auth_profile_slug / app_auth_profile_slug. A non-admin who knew any oauth_app slug for the org could PATCH a connection to use it, and the connection owner could pivot the connection onto another member's oauth_account profile. Mirrors the gates that already exist in handleCreate:
    • app_auth_profile_slug: non-admin must pass the connector's pinned default; clearing is admin-only.
    • auth_profile_slug: caller must be the connection's created_by (or admin/owner), and the target profile must be owned by the caller for oauth_account / browser_session. env profiles stay admin-only.
  • Member-facing rebind UI. New AccountProfileSection on the connection settings tab lets the connection owner (and admins) switch which credential a connection runs under without dropping and re-creating it. Non-admins see only profiles they own — matches the new server target-profile check so we never show a picker the server would reject.
  • Default app profile flag (is_default_for_connector) UX is unchanged: the /oauth-apps admin page is still the only place to pin it, and the connection settings tab shows the "Org default" badge read-only.

What this addresses

The "doubling" / asymmetric-permissions audit on the connection page surfaced two real gaps:

  1. App-profile updates were UI-gated only — bypassable via direct API.
  2. Account profiles had no rebind UI; users had to delete + recreate the connection.

Plan iterated with pi --provider google review, which caught that an initial "owner of connection" check wasn't enough — also needed target-profile ownership. That's now in.

Test plan

  • make build-packages clean
  • make typecheck clean
  • As admin: rebind an oauth_account connection on the settings tab; status inherits the new profile's status.
  • As member (the connection's created_by): same rebind works; picker only shows profiles I own.
  • As member (not created_by): no picker rendered.
  • Direct API: manage_connections action=update with another user's auth_profile_slug as a member → 4xx with the new error.
  • Direct API: manage_connections action=update with a non-default app_auth_profile_slug as a member → 4xx.

Summary by CodeRabbit

Release Notes

  • New Features

    • Members can now update and manage connection configurations in addition to creating new connections.
  • Bug Fixes

    • Strengthened validation for connection updates to prevent unauthorized modifications to sensitive profile bindings and enforce proper access controls.

Review Change Stack

…rebind

handleUpdate previously had no role check on auth_profile_slug or
app_auth_profile_slug — non-admins could pivot a connection onto another
user's OAuth app or another member's account profile by passing the slug
directly. Mirror handleCreate's existing gates:

- app_auth_profile_slug: non-admins must pass the pinned default;
  clearing is admin-only.
- auth_profile_slug: caller must be the connection's created_by (or
  admin/owner); target profile must be owned by the caller for
  oauth_account / browser_session, and env profiles are admin-only.

UI surfaces the new account-profile picker on the connection settings
tab (web submodule bump) so members can rebind without dropping the
connection.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8ecc0366-62c2-428b-a058-dd75e4cfeaab

📥 Commits

Reviewing files that changed from the base of the PR and between 9fb131a and 441bdc7.

📒 Files selected for processing (2)
  • packages/owletto
  • packages/server/src/auth/tool-access.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/owletto
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/server/src/auth/tool-access.ts

📝 Walkthrough

Walkthrough

The PR adds role-gated ownership and profile validation to connection updates. The handler now fetches created_by upfront, rejects non-admin updates for unowned connections, restricts non-admin app profile changes to the connector's pinned default, and guards post-selection auth profile rebindings. Tool-access permissions shift update from admin-only to member-scoped with inline enforcement. The owletto submodule pointer is incremented.

Changes

Connection Update Authorization

Layer / File(s) Summary
Query includes ownership and pre-update ownership check
packages/server/src/tools/admin/manage_connections.ts
The existing connection SELECT now includes created_by for ownership checks. The update handler resolves caller role once, rejects non-admin updates to unowned connections, and restricts non-admin app_auth_profile_slug changes to the connector's pinned default profile only.
Post-selection profile rebinding guards
packages/server/src/tools/admin/manage_connections.ts
After auth selection resolution, non-admins are blocked from env profile bindings and prevented from rebinding oauth_account/browser_session profiles unless they own them (created_by === ctx.userId).
Tool access permission change
packages/server/src/auth/tool-access.ts
manage_connections update action is moved from admin-scoped to member-scoped permissions; update comments reflect member-owned connection editing with handler-enforced ownership and role gates.

Owletto Submodule Update

Layer / File(s) Summary
Owletto package commit pin update
packages/owletto
Submodule reference for packages/owletto updated to a new commit hash.

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly Related PRs

  • lobu-ai/lobu#764: Introduces the is_default_for_connector schema and OAuth app profile selection logic that this PR enforces via ownership and role-gated validation in the connection update handler.

Poem

🐰 Connections now know who they're keeping close,
Admins guard the apps, members claim their own,
Role-gated paths prevent the wayward rebind—
A safer update flow, ownership in mind!
Owletto hops forward to newer ground.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main changes: adding admin-gating for app profile updates and member rebind UI for account profiles, matching the core objectives and file changes.
Description check ✅ Passed The description covers the key changes (security fixes and new UI), addresses affected audit issues, and includes a comprehensive test plan matching the template structure.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/connection-profile-gaps

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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

@codecov-commenter
Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ba83ee0ccd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1694 to +1697
if (hasAuthProfileArg && !callerIsAdmin) {
if (!ctx.userId || existing.created_by !== ctx.userId) {
return {
error: 'You can only change the auth profile of a connection you created.',
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Allow members to reach the rebind handler

This new non-admin ownership gate is unreachable for the member rebind flow because manage_connections.update is still classified as owner/admin-only in packages/server/src/auth/tool-access.ts lines 44-49, and routeAction() enforces that before handleUpdate() runs. As a result, a member who created a connection and selects one of their own profiles will still get the generic admin-access error instead of this intended path; move update into the member-write policy or add an action/field-specific access exception before relying on these handler checks.

Useful? React with 👍 / 👎.

…o member-write)

The server-side gates added in the previous commit were unreachable —
`manage_connections action='update'` lived in OWNER_ADMIN_ACTIONS so
routeAction rejected members before the handler ran. Move 'update' into
MEMBER_WRITE_ACTIONS and add a top-level 'caller is admin OR
created_by === ctx.userId' gate so the per-field role checks (app
profile pinned-default, target-profile ownership) actually fire for
members editing their own connection. Members still can't touch other
members' connections.

Caught by pi PR review of #812.
@buremba
Copy link
Copy Markdown
Member Author

buremba commented May 17, 2026

Pushed fixup commit 9fb131a6 addressing pi's review.

Critical fix from pi review: manage_connections action='update' was in OWNER_ADMIN_ACTIONS, so routeAction rejected member callers before handleUpdate ran — making the per-field role gates added in the first commit dead code. Moved update into MEMBER_WRITE_ACTIONS and added a top-level "caller is admin OR created_by === ctx.userId" gate at the top of handleUpdate. The per-field gates (app pinned-default, target-profile ownership) now actually fire.

Other pi findings (not addressed in this PR):

  • handleGet doesn't enforce private-connection visibility (pre-existing, separate concern).
  • list_auth_profiles is broad — non-admins see all org profiles. Pre-existing; UI now filters client-side.
  • handleUpdate's status-validation logic for auth_profile is slightly looser than handleCreate (allows pending_auth on env profiles). Pre-existing; not made worse here.

All addressed in conversation; flagging here for follow-up tickets.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@packages/server/src/tools/admin/manage_connections.ts`:
- Around line 1668-1687: The current guard for app_auth_profile_slug only blocks
non-admins from clearing or picking an unpinned profile but does not prevent a
non-admin from rebinding another member's connection to the pinned default;
inside the existing if (hasAppAuthProfileArg && !callerIsAdmin) block add a
check that the caller is the connection owner (compare the request caller's id
to existing.created_by) and return an error if they are not (so only the owner
or an admin can change app_auth_profile_slug), then proceed to call
getAuthProfileBySlug and the pinned checks as before.

In `@packages/web`:
- Line 1: The submodule pointer for packages/web is set to an unreachable SHA
(a63fba52d10ca72ee6751e409ce59980433eddbf) causing Submodule Drift failures; fix
it by repinning the packages/web submodule to a commit that exists on the
tracked branch (owletto/main) or merge the referenced submodule commit into that
branch, then update the parent repo's submodule pointer to that reachable commit
and commit the updated gitlink. Locate the packages/web submodule entry in the
repo's git modules/submodule config and update the gitlink (or perform the merge
into owletto/main) so CI and FluxCD can checkout the submodule successfully.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1bdd6e36-d5f7-487e-8506-d349c705d00b

📥 Commits

Reviewing files that changed from the base of the PR and between 76aaf2d and ba83ee0.

📒 Files selected for processing (2)
  • packages/server/src/tools/admin/manage_connections.ts
  • packages/web

Comment thread packages/server/src/tools/admin/manage_connections.ts
Comment thread packages/web Outdated
@@ -1 +1 @@
Subproject commit b01682708902e51fc33b3f68edbc0f4319836509
Subproject commit a63fba52d10ca72ee6751e409ce59980433eddbf
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Pin packages/web to a reachable commit.

Line 1 points the submodule at a63fba52d10ca72ee6751e409ce59980433eddbf, but the Submodule Drift job already proves that SHA is not reachable from owletto/main. That leaves CI red and can break FluxCD checkout/deploys. Please either repin to a commit on the tracked branch or merge this submodule commit there before updating the parent pointer.

🧰 Tools
🪛 GitHub Actions: Submodule Drift / 0_check-drift.txt

[error] 1-1: Pinned SHA $PINNED is not reachable from owletto/main. FluxCD deployment may break because the parent pinned commit is not on origin/main.

🪛 GitHub Actions: Submodule Drift / check-drift

[error] 1-1: Pinned SHA is not reachable from owletto/main. Step failed on check: 'if ! git -C packages/web merge-base --is-ancestor "$PINNED" origin/main; then echo "::error::Pinned SHA $PINNED is not reachable from owletto/main." fi'.

🤖 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 `@packages/web` at line 1, The submodule pointer for packages/web is set to an
unreachable SHA (a63fba52d10ca72ee6751e409ce59980433eddbf) causing Submodule
Drift failures; fix it by repinning the packages/web submodule to a commit that
exists on the tracked branch (owletto/main) or merge the referenced submodule
commit into that branch, then update the parent repo's submodule pointer to that
reachable commit and commit the updated gitlink. Locate the packages/web
submodule entry in the repo's git modules/submodule config and update the
gitlink (or perform the merge into owletto/main) so CI and FluxCD can checkout
the submodule successfully.

Submodule path moved from packages/web to packages/owletto in #817.
Updated the pointer to 8ede17c (lobu-ai/owletto#149 merged into main),
which preserves the account-profile rebind UI from this PR.
@buremba buremba merged commit 54ee582 into main May 17, 2026
21 of 23 checks passed
@buremba buremba deleted the fix/connection-profile-gaps branch May 17, 2026 12:18
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.

2 participants