Skip to content

fix(credentials): grant admin role to workspace admins on credential creation#5431

Open
tsushanth wants to merge 1 commit into
simstudioai:mainfrom
tsushanth:fix/4698-workspace-admin-credential-role
Open

fix(credentials): grant admin role to workspace admins on credential creation#5431
tsushanth wants to merge 1 commit into
simstudioai:mainfrom
tsushanth:fix/4698-workspace-admin-credential-role

Conversation

@tsushanth

Copy link
Copy Markdown

Fixes #4698

Problem

When seeding credential_member rows for workspace-scoped credentials (env_workspace, service_account), the role was determined by userId === actingUserId — only the person performing the action received admin. All other workspace members, including explicit workspace admins, got member and were therefore unable to edit or delete secrets they should control.

This affected three separate creation paths:

  • ensureWorkspaceCredentialMemberships (called from syncWorkspaceEnvCredentials) — hardcoded role: 'member' for everyone
  • createWorkspaceEnvCredentialsactingUserId === memberUserId check
  • POST /api/credentials handler — session.user.id === memberUserId check

Fix

getWorkspaceMembership now also selects permissionType from the permissions table and returns an adminUserIds: Set<string> containing the workspace owner plus all members with explicit admin permission. All three creation paths use adminUserIds.has(userId) to assign 'admin' vs 'member'.

No behavior change for personal (env_personal, oauth) credentials. Existing rows are unaffected — only new inserts receive the corrected role.

…creation

When seeding credential_member rows for workspace-scoped credentials, the
role was determined solely by whether the acting user is the creator
(actingUserId === memberUserId). Workspace admin users therefore received
the 'member' role and could not edit or delete secrets they should control.

Fix: read permissionType from the permissions table inside
getWorkspaceMembership and expose an adminUserIds set. All three creation
paths (ensureWorkspaceCredentialMemberships, createWorkspaceEnvCredentials,
and the POST /api/credentials handler) now use adminUserIds.has(userId) to
assign 'admin' vs 'member', matching the workspace's permission structure.
@vercel

vercel Bot commented Jul 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jul 5, 2026 10:07pm

Request Review

@cursor

cursor Bot commented Jul 5, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes authorization data on new credential membership inserts in security-sensitive credential flows; scope is limited to new rows and aligns roles with existing workspace admin semantics.

Overview
Fixes workspace-scoped credential membership so credential_member.role reflects workspace admin status instead of only the user who triggered the action.

getWorkspaceMembership now returns adminUserIds (workspace owner plus users with explicit workspace admin permission). When seeding members for env_workspace and service_account credentials, POST /api/credentials, createWorkspaceEnvCredentials, and ensureWorkspaceCredentialMemberships (via syncWorkspaceEnvCredentials) assign admin when adminUserIds.has(userId) and member otherwise—replacing checks against the acting session user or hardcoded member for everyone.

Existing credential_member rows are unchanged; only new inserts get the corrected roles. Personal and OAuth credential paths are unaffected.

Reviewed by Cursor Bugbot for commit 999bc60. Bugbot is set up for automated code reviews on this repo. Configure here.

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 999bc60. Configure here.

credentialId,
userId: memberUserId,
role: (memberUserId === actingUserId ? 'admin' : 'member') as 'admin' | 'member',
role: (adminUserIds.has(memberUserId) ? 'admin' : 'member') as 'admin' | 'member',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Write creator loses credential admin

Medium Severity

Replacing the acting-user check with only adminUserIds stops workspace Write members from receiving credential_member admin when they create shared secrets. Env PUT/DELETE treats existing keys as editable only when workspace permission is admin or the user has per-key credential admin, so those creators can add secrets but later edits or deletes are denied.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 999bc60. Configure here.

@greptile-apps

greptile-apps Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a bug where workspace admins received member instead of admin role in credential_member rows for env_workspace/service_account credentials. getWorkspaceMembership now returns an adminUserIds Set (workspace owner + explicit admin-permission holders) and all three creation paths use it to drive role assignment.

  • getWorkspaceMembership fetches permissionType alongside userId and builds adminUserIds from the filtered result; the owner is unconditionally added to both sets.
  • ensureWorkspaceCredentialMemberships, createWorkspaceEnvCredentials, and the POST /api/credentials handler all switch from a creator-identity check to adminUserIds.has(userId).
  • onConflictDoUpdate in ensureWorkspaceCredentialMemberships deliberately skips the role column, so pre-existing incorrectly downgraded rows are not corrected — only new inserts benefit.

Confidence Score: 3/5

The PR partially fixes the admin-role bug but introduces a regression: any workspace member with write (not admin) permission who creates a workspace credential is now seeded as member on their own credential, blocking them from editing or deleting it.

The route guard (canWrite) admits both write- and admin-permission users to the env_workspace/service_account creation path, but adminUserIds only covers the owner and explicit admin rows. Dropping the actingUserId === memberUserId fallback means the creator loses guaranteed admin access if they hold only write permission — the same issue exists in createWorkspaceEnvCredentials. This regression affects a current, reachable code path on every affected credential creation.

Both apps/sim/app/api/credentials/route.ts and apps/sim/lib/credentials/environment.ts (specifically createWorkspaceEnvCredentials) need the creator-gets-admin condition restored alongside the new adminUserIds check.

Important Files Changed

Filename Overview
apps/sim/lib/credentials/environment.ts Adds adminUserIds Set to getWorkspaceMembership (owner + admin-permission rows) and threads it through three credential-seeding paths; fixes workspace admins receiving member role but introduces a regression for write-permission creators who now also lose admin.
apps/sim/app/api/credentials/route.ts Destructures adminUserIds from getWorkspaceMembership and uses workspaceAdminUserIds.has(memberUserId) instead of session.user.id === memberUserId; same creator-regression risk as in environment.ts.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant U as User (write/admin)
    participant R as POST /api/credentials
    participant GWM as getWorkspaceMembership
    participant DB as Database

    U->>R: POST env_workspace credential
    R->>R: checkWorkspaceAccess → canWrite (allows write+admin)
    R->>GWM: getWorkspaceMembership(workspaceId)
    GWM->>DB: SELECT ownerId FROM workspace
    GWM->>DB: SELECT userId, permissionType FROM permissions
    GWM-->>R: "{ ownerId, memberUserIds[], adminUserIds(owner+admin-perm) }"

    R->>DB: INSERT credential
    loop for each memberUserId
        alt memberUserId in adminUserIds
            R->>DB: "INSERT credential_member (role='admin')"
        else
            R->>DB: "INSERT credential_member (role='member')"
            Note over R,DB: write-perm creator also gets 'member'
        end
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant U as User (write/admin)
    participant R as POST /api/credentials
    participant GWM as getWorkspaceMembership
    participant DB as Database

    U->>R: POST env_workspace credential
    R->>R: checkWorkspaceAccess → canWrite (allows write+admin)
    R->>GWM: getWorkspaceMembership(workspaceId)
    GWM->>DB: SELECT ownerId FROM workspace
    GWM->>DB: SELECT userId, permissionType FROM permissions
    GWM-->>R: "{ ownerId, memberUserIds[], adminUserIds(owner+admin-perm) }"

    R->>DB: INSERT credential
    loop for each memberUserId
        alt memberUserId in adminUserIds
            R->>DB: "INSERT credential_member (role='admin')"
        else
            R->>DB: "INSERT credential_member (role='member')"
            Note over R,DB: write-perm creator also gets 'member'
        end
    end
Loading

Comments Outside Diff (3)

  1. apps/sim/app/api/credentials/route.ts, line 543-557 (link)

    P1 Creator loses admin rights when they have write but not admin workspace permission

    The route guard on line 320 allows any user with canWrite (which includes write-permission members, not just admins) to create env_workspace/service_account credentials. Before this fix, the creator always received 'admin' via session.user.id === memberUserId. Now, workspaceAdminUserIds only contains workspace owners and users with permissionType === 'admin' — so a write-permission member who creates a credential is inserted as 'member' in credential_member, immediately losing the ability to edit or delete the resource they just created.

    The same regression exists in createWorkspaceEnvCredentials where actingUserId was the former tiebreaker. To preserve creator-gets-admin while also correctly granting admins their role, the condition should be workspaceAdminUserIds.has(memberUserId) || memberUserId === session.user.id.

  2. apps/sim/lib/credentials/environment.ts, line 302-308 (link)

    P1 write-permission creator gets member role in createWorkspaceEnvCredentials

    This mirrors the regression in route.ts: actingUserId (the caller) previously guaranteed 'admin' via memberUserId === actingUserId, but that guard was removed. A workspace member with write permission who triggers this path (e.g. via env-variable sync) will be seeded as 'member', preventing them from managing the credentials they just provisioned. The role expression should be adminUserIds.has(memberUserId) || memberUserId === actingUserId ? 'admin' : 'member'.

  3. apps/sim/lib/credentials/environment.ts, line 155-167 (link)

    P2 Existing misassigned roles are not corrected by ensureWorkspaceCredentialMemberships

    The onConflictDoUpdate at line 174 intentionally skips updating role (it only sets status, joinedAt, and updatedAt). This means workspace admins who previously received 'member' because of the pre-fix bug will stay as 'member' even after syncWorkspaceEnvCredentials runs on a workspace with updated env keys. The PR description documents this ("Existing rows are unaffected"), but it may be worth a follow-up to backfill or provide a migration path for affected rows if users have already been incorrectly downgraded.

Reviews (1): Last reviewed commit: "fix(credentials): grant admin role to wo..." | Re-trigger Greptile

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.

fix(credentials): workspace admin users get 'member' role on credential_member instead of 'admin'

1 participant