Skip to content

improvement(enterprise): feature flagging + runtime checks consolidation - #2730

Merged
icecrasher321 merged 6 commits into
stagingfrom
improvement/enterprise-features
Jan 8, 2026
Merged

improvement(enterprise): feature flagging + runtime checks consolidation#2730
icecrasher321 merged 6 commits into
stagingfrom
improvement/enterprise-features

Conversation

@icecrasher321

@icecrasher321icecrasher321 commented Jan 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Enterprise feature gating for BYOK and Credential Sets: BYOK is now enterprise-only with server-side checks at both API and execution level (falls back to hosted keys if downgraded). Credential Sets (Polling Groups) require team/enterprise plan with direct organization billing checks in polling services.

  • Admin API and code cleanup: Added /api/v1/admin/byok endpoint for clearing BYOK keys on enterprise churn. Fixed circular dependency between subscription.ts and usage.ts by extracting getHighestPrioritySubscription to plan.ts. Removed dynamic imports where possible.

  • Enterprise documentation: Created /enterprise docs section with BYOK provider usage (OpenAI for embeddings/agent, Mistral for OCR, etc.), SSO setup, and self-hosted environment variables.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation
  • Other: Code Improvement + plan check reliability

Testing

Tested manually

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel

vercelBot commented Jan 8, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
ProjectDeploymentReviewUpdated (UTC)
docsSkippedSkippedJan 8, 2026 9:51pm

@greptile-appsgreptile-appsBot left a comment

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.

Greptile Overview

Greptile Summary

This PR consolidates enterprise feature gating for BYOK and Credential Sets with both API-level and runtime-level checks.

Key Changes:

  • Circular dependency fix: Extracted getHighestPrioritySubscription to new plan.ts file, eliminating cycle between subscription.ts and usage.ts
  • BYOK enterprise gating: Added dual-layer protection with API checks (isEnterpriseOrgAdminOrOwner) for management and runtime checks (isWorkspaceOnEnterprisePlan) for execution, with graceful fallback to hosted keys
  • Credential Sets team/enterprise gating: Added hasCredentialSetsAccess checks to all API routes and polling services with direct organization billing verification
  • Admin tooling: New /api/v1/admin/byok endpoint for cleaning up BYOK keys on enterprise churn
  • UI improvements: Enterprise gate component with upgrade CTA when BYOK is unavailable
  • Documentation: Comprehensive enterprise docs covering BYOK providers, SSO, and self-hosted env vars

Critical Issues Found:

  • isHosted hardcoded to true breaks self-hosted env var overrides for CREDENTIAL_SETS_ENABLED and SSO_ENABLED
  • isEnterpriseOrgAdminOrOwner and isTeamOrgAdminOrOwner only check first org membership with .limit(1), incorrectly denying access to users with multiple organizations

The implementation correctly separates API-level authorization (who can configure features) from runtime authorization (which workspaces can use features), ensuring proper billing enforcement at execution time.

Confidence Score: 3/5

  • Solid architecture with dual-layer security, but critical bugs in multi-org handling and self-hosted deployments
  • The PR implements excellent separation of concerns with API-level and runtime-level checks, clean circular dependency resolution, and comprehensive feature gating. However, the hardcoded isHosted = true completely breaks self-hosted deployments' ability to use env var overrides, and the .limit(1) in org admin checks creates incorrect access denial for multi-org users. These are production-impacting bugs that affect core enterprise feature access control.
  • Pay close attention to apps/sim/lib/core/config/feature-flags.ts (breaks self-hosted) and apps/sim/lib/billing/core/subscription.ts (multi-org access bugs)

Important Files Changed

File Analysis

FilenameScoreOverview
apps/sim/lib/core/config/feature-flags.ts2/5Added isCredentialSetsEnabled flag but hardcoded isHosted = true breaks self-hosted env var overrides
apps/sim/lib/billing/core/plan.ts5/5New file extracts getHighestPrioritySubscription to resolve circular dependency, clean implementation
apps/sim/lib/billing/core/subscription.ts2/5Added enterprise/team admin checks and feature access functions, but .limit(1) incorrectly handles multi-org users
apps/sim/lib/api-key/byok.ts5/5Added enterprise plan check before BYOK key lookup, removes dynamic imports, falls back to hosted keys gracefully
apps/sim/app/api/v1/admin/byok/route.ts5/5New admin endpoint for listing/deleting BYOK keys on enterprise churn, comprehensive filtering and logging
apps/sim/lib/webhooks/gmail-polling-service.ts5/5Added organization plan check for credential set polling, blocks execution if org lacks team/enterprise plan

Sequence Diagram

sequenceDiagram
participant User
participant UI as BYOK UI
participant API as BYOK API Route
participant Auth as isEnterpriseOrgAdminOrOwner
participant BYOK as byok.ts
participant Runtime as Workflow Execution
participant Plan as isWorkspaceOnEnterprisePlan
Note over User,Plan: API-Level Gating (User Management)
User->>UI: Access BYOK Settings
UI->>API: GET /api/workspaces/{id}/byok-keys
API->>Auth: Check enterprise admin/owner
Auth-->>API: true/false
alt Not Enterprise Admin
API-->>UI: Return empty keys, byokEnabled=false
UI-->>User: Show Enterprise Gate UI
else Enterprise Admin
API-->>UI: Return keys array, byokEnabled=true
UI-->>User: Show BYOK Management
User->>API: POST /api/workspaces/{id}/byok-keys
API->>Auth: Check enterprise admin/owner
Auth-->>API: Authorized
API-->>User: Key Saved Successfully
end
Note over User,Plan: Runtime-Level Gating (Execution)
User->>Runtime: Execute Workflow
Runtime->>BYOK: getApiKeyWithBYOK(provider, model, workspaceId)
BYOK->>Plan: isWorkspaceOnEnterprisePlan(workspaceId)
Plan-->>BYOK: Check billed account plan
alt Workspace on Enterprise
BYOK->>BYOK: getBYOKKey(workspaceId, provider)
alt BYOK Key Found
BYOK-->>Runtime: Return user provided credential
else No BYOK Key
BYOK-->>Runtime: Return hosted credential
end
else Not Enterprise
BYOK-->>Runtime: Return hosted credential
end
Runtime->>Runtime: Execute with appropriate credential
Loading

Comment threadapps/sim/lib/billing/core/subscription.ts
Comment threadapps/sim/lib/billing/core/subscription.ts
@greptile-apps

Copy link
Copy Markdown
Contributor
Additional Comments (1)

apps/sim/lib/core/config/feature-flags.ts
hardcoded isHosted = true breaks self-hosted deployments that depend on this flag

The billing functions use isHosted to enable env var overrides (lines 260, 287, 308 in subscription.ts). With this hardcoded to true, self-hosted deployments can never use CREDENTIAL_SETS_ENABLED or SSO_ENABLED overrides.

export const isHosted =
env.NEXT_PUBLIC_APP_URL === 'https://www.sim.ai' ||
env.NEXT_PUBLIC_APP_URL === 'https://www.staging.sim.ai'

@icecrasher321

Copy link
Copy Markdown
CollaboratorAuthor

@greptile

@icecrasher321

Copy link
Copy Markdown
CollaboratorAuthor

@greptile

@greptile-appsgreptile-appsBot left a comment

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.

Greptile Overview

Greptile Summary

This PR implements enterprise feature gating for BYOK and Credential Sets with multi-layered enforcement and graceful fallback mechanisms.

Key Changes

  • BYOK Enterprise Gating: Added server-side checks at both API level (isEnterpriseOrgAdminOrOwner) and execution level (isWorkspaceOnEnterprisePlan). When a workspace loses enterprise status, workflows automatically fall back to hosted keys without breaking.

  • Credential Sets Plan Enforcement: Credential Sets (Polling Groups) now require team/enterprise plans with direct organization billing checks in polling services (isOrganizationOnTeamOrEnterprisePlan). This prevents plan bypass through cached state.

  • Circular Dependency Resolution: Extracted getHighestPrioritySubscription from subscription.ts to new plan.ts file, breaking the circular dependency with usage.ts. Both files now import from plan.ts instead of each other.

  • Admin Tooling: New /api/v1/admin/byok endpoint for listing and deleting BYOK keys when enterprises churn, with organization-level filtering.

  • Enterprise Documentation: Created /enterprise docs section documenting BYOK provider usage (OpenAI for embeddings/agent, Mistral for OCR), SSO setup, and self-hosted environment variables.

Architecture

The implementation uses a defense-in-depth approach with checks at multiple layers: API authorization (who can manage keys), runtime execution (what gets used), and polling services (direct org billing verification). This ensures plan enforcement even if UI or cached state becomes stale.

Confidence Score: 5/5

  • Safe to merge - well-architected feature gating with proper fallback mechanisms
  • The changes demonstrate strong engineering practices: circular dependency properly resolved, multi-layered security checks, graceful degradation on plan downgrade, comprehensive error handling, and proper use of logging. The feature flags allow self-hosted deployments to override restrictions. No breaking changes or risky patterns detected.
  • No files require special attention

Important Files Changed

File Analysis

FilenameScoreOverview
apps/sim/lib/billing/core/plan.ts5/5extracted getHighestPrioritySubscription to resolve circular dependency between subscription.ts and usage.ts
apps/sim/lib/billing/core/subscription.ts5/5added enterprise plan checks (isWorkspaceOnEnterprisePlan, isOrganizationOnTeamOrEnterprisePlan) for BYOK and credential sets feature gating
apps/sim/app/api/v1/admin/byok/route.ts5/5new admin API endpoint for listing and deleting BYOK keys when enterprises churn
apps/sim/app/api/workspaces/[id]/byok-keys/route.ts5/5added enterprise plan check using isEnterpriseOrgAdminOrOwner for BYOK key management API
apps/sim/lib/api-key/byok.ts5/5added runtime enterprise check (isWorkspaceOnEnterprisePlan) for BYOK key retrieval with fallback to hosted keys
apps/sim/app/api/credential-sets/route.ts5/5added team/enterprise plan check using hasCredentialSetsAccess for credential sets API
apps/sim/lib/webhooks/gmail-polling-service.ts5/5added direct organization billing check (isOrganizationOnTeamOrEnterprisePlan) in polling execution for credential sets
apps/sim/lib/webhooks/outlook-polling-service.ts5/5added direct organization billing check (isOrganizationOnTeamOrEnterprisePlan) in polling execution for credential sets

Sequence Diagram

sequenceDiagram
participant User
participant API as BYOK API
participant PlanCheck as Plan Check
participant DB as Database
participant Runtime as Runtime Execution
participant Provider as AI Provider
Note over User,Provider: Enterprise BYOK Feature Flow
User->>API: POST /api/workspaces/{id}/byok-keys
API->>PlanCheck: isEnterpriseOrgAdminOrOwner(userId)
PlanCheck->>DB: Query organization subscription
DB-->>PlanCheck: Enterprise plan active
PlanCheck-->>API: ✓ Authorized
API->>DB: Encrypt & store BYOK key
DB-->>API: Key saved
API-->>User: Success
Note over Runtime,Provider: Workflow Execution with BYOK
Runtime->>PlanCheck: isWorkspaceOnEnterprisePlan(workspaceId)
PlanCheck->>DB: Check workspace billing account
DB-->>PlanCheck: Enterprise plan active
PlanCheck-->>Runtime: ✓ Enterprise access
Runtime->>DB: getBYOKKey(workspaceId, provider)
DB-->>Runtime: Decrypted BYOK key
Runtime->>Provider: Call AI API with BYOK key
Provider-->>Runtime: Response
Note over PlanCheck,Runtime: Downgrade/Churn Scenario
PlanCheck->>Runtime: isWorkspaceOnEnterprisePlan = false
Runtime->>DB: getBYOKKey returns null
Runtime->>Provider: Fallback to hosted keys
Provider-->>Runtime: Response
Note over User,DB: Credential Sets (Polling Groups)
User->>API: POST /api/credential-sets
API->>PlanCheck: hasCredentialSetsAccess(userId)
PlanCheck->>DB: Check team/enterprise plan
DB-->>PlanCheck: Team plan active
PlanCheck-->>API: ✓ Authorized
API->>DB: Create credential set
DB-->>API: Success
Note over Runtime,PlanCheck: Polling Service Runtime Check
Runtime->>PlanCheck: isOrganizationOnTeamOrEnterprisePlan(orgId)
PlanCheck->>DB: Query organization subscription directly
DB-->>PlanCheck: Team plan active
PlanCheck-->>Runtime: ✓ Process emails
Runtime->>Runtime: Poll & trigger workflows
Loading

@greptile-appsgreptile-appsBot left a comment

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.

Greptile Overview

Greptile Summary

This PR consolidates enterprise feature gating for BYOK and Credential Sets with both API-level and runtime checks. While the architectural approach is sound, there are several critical bugs that need to be addressed before merging.

Key Changes

  • BYOK enterprise gating: Adds isEnterpriseOrgAdminOrOwner checks to gate BYOK management to enterprise org admins/owners, with proper fallback to hosted keys on downgrade
  • Credential Sets team/enterprise gating: Implements hasCredentialSetsAccess with runtime isOrganizationOnTeamOrEnterprisePlan checks in polling services
  • Code cleanup: Extracted getHighestPrioritySubscription to plan.ts to resolve circular dependency
  • Admin tooling: New /api/v1/admin/byok endpoint for cleaning up BYOK keys on enterprise churn
  • Documentation: Comprehensive enterprise docs for BYOK, SSO, and self-hosted setup

Critical Issues Found

  • Multi-org membership bug: isEnterpriseOrgAdminOrOwner and isTeamOrgAdminOrOwner only check the first org a user belongs to (using limit(1)). Users who are admins in multiple orgs will have inconsistent access
  • Polling service error handling: When plan checks fail in Gmail/Outlook polling services, webhooks are not marked as failed - they just log and continue, leaving users unaware their webhooks stopped working
  • Authorization race condition: Credential set creation checks plan access before verifying org membership, allowing potential unauthorized access
  • Inconsistent env var naming: Server checks CREDENTIAL_SETS_ENABLED while client checks NEXT_PUBLIC_CREDENTIAL_SETS_ENABLED

Recommendations

  1. Fix the limit(1) bugs in org admin/owner checks by iterating through all memberships
  2. Add markWebhookFailed calls in polling services when plan checks fail
  3. Reorder credential set authorization to verify org membership before plan access
  4. Consider adding more specific error messages to help users understand why features are unavailable

Confidence Score: 2/5

  • Critical bugs in multi-org membership checks and polling service error handling make this unsafe to merge
  • Score reflects multiple critical logic bugs: (1) isEnterpriseOrgAdminOrOwner and isTeamOrgAdminOrOwner only check first org membership, causing access issues for multi-org admins, (2) Polling services don't mark webhooks as failed when plan restrictions trigger, leaving users unaware of failures, (3) Race condition in credential set authorization. These bugs will cause production issues for enterprise customers and users in multiple organizations.
  • Pay close attention to apps/sim/lib/billing/core/subscription.ts (fix limit(1) bugs), apps/sim/lib/webhooks/gmail-polling-service.ts and apps/sim/lib/webhooks/outlook-polling-service.ts (add markWebhookFailed calls), and apps/sim/app/api/credential-sets/route.ts (reorder authorization checks)

Important Files Changed

File Analysis

FilenameScoreOverview
apps/sim/lib/billing/core/subscription.ts2/5Critical bugs in isEnterpriseOrgAdminOrOwner and isTeamOrgAdminOrOwner - only checks first org membership with limit(1)
apps/sim/lib/billing/core/plan.ts5/5Clean extraction of getHighestPrioritySubscription from subscription.ts - resolves circular dependency
apps/sim/lib/webhooks/gmail-polling-service.ts3/5Plan check for credential sets doesn't call markWebhookFailed on access denial
apps/sim/lib/webhooks/outlook-polling-service.ts3/5Same plan check issue as Gmail service - missing markWebhookFailed call
apps/sim/app/api/credential-sets/route.ts3/5Race condition: plan check before org ownership verification in POST handler
apps/sim/app/api/workspaces/[id]/byok-keys/route.ts4/5BYOK enterprise gating works correctly, minor UX issue with error specificity

Sequence Diagram

sequenceDiagram
participant User
participant UI as Settings Modal
participant API as API Endpoints
participant Billing as Billing Service
participant DB as Database
participant Polling as Polling Service
Note over User,Polling: BYOK Feature Flow (Enterprise Only)
User->>UI: Access BYOK Settings
UI->>API: GET /api/workspaces/{id}/byok-keys
API->>Billing: isEnterpriseOrgAdminOrOwner(userId)
Billing->>DB: Query user membership
Billing->>DB: Query org subscription
Billing-->>API: true/false
alt Enterprise Admin/Owner
API->>DB: Fetch BYOK keys
API-->>UI: {keys: [...], byokEnabled: true}
User->>UI: Add/Update BYOK key
UI->>API: POST /api/workspaces/{id}/byok-keys
API->>Billing: isEnterpriseOrgAdminOrOwner(userId)
Billing-->>API: true
API->>DB: Encrypt and store key
API-->>UI: Success
else Not Enterprise
API-->>UI: {keys: [], byokEnabled: false}
UI->>User: Show "Enterprise Feature" gate
end
Note over User,Polling: Credential Sets Flow (Team/Enterprise Only)
User->>UI: Access Email Polling Settings
UI->>API: GET /api/credential-sets
API->>Billing: hasCredentialSetsAccess(userId)
Billing->>Billing: Check env var override OR isTeamOrgAdminOrOwner
Billing-->>API: true/false
alt Has Access
API->>DB: Fetch credential sets
API-->>UI: Credential sets data
User->>UI: Create credential set
UI->>API: POST /api/credential-sets
API->>Billing: hasCredentialSetsAccess(userId)
Billing-->>API: true
API->>DB: Verify user is org admin/owner
API->>DB: Create credential set
API-->>UI: Success
else No Access
API-->>UI: 403 Forbidden
end
Note over User,Polling: Runtime Checks
Polling->>DB: Fetch active webhooks with credential sets
loop For each webhook
Polling->>DB: Get credential set organization
Polling->>Billing: isOrganizationOnTeamOrEnterprisePlan(orgId)
Billing->>DB: Query org subscription
Billing-->>Polling: true/false
alt Plan Valid
Polling->>Polling: Process emails
else Plan Downgraded
Polling->>Polling: Log error and skip (BUG: should mark failed)
end
end
Note over User,Polling: Admin BYOK Cleanup Flow
User->>API: DELETE /api/v1/admin/byok?organizationId={orgId}
API->>DB: Find workspaces billed to org
API->>DB: Delete all BYOK keys for workspaces
API-->>User: {deletedCount, workspacesAffected}
Loading

Comment threadapps/sim/lib/billing/core/subscription.ts
Comment threadapps/sim/lib/billing/core/subscription.ts
Comment threadapps/sim/lib/billing/core/subscription.ts
Comment threadapps/sim/lib/webhooks/gmail-polling-service.ts
Comment threadapps/sim/lib/webhooks/outlook-polling-service.ts
Comment threadapps/sim/app/api/workspaces/[id]/byok-keys/route.ts
@greptile-apps

Copy link
Copy Markdown
Contributor
Additional Comments (1)

apps/sim/app/api/credential-sets/route.ts
Race condition: plan check happens before org ownership verification

The hasCredentialSetsAccess check (line 99) happens before verifying the user is actually an admin/owner of the organization (lines 111-123). A user could pass the plan check through a different org's team plan, then create a credential set for an org they're not authorized for.

Move the organization membership verification before the plan access check, or pass the organizationId to hasCredentialSetsAccess for direct org-level validation.

@icecrasher321
icecrasher321 merged commit c2180bf into stagingJan 8, 2026
5 of 6 checks passed
@waleedlatif1
waleedlatif1 deleted the improvement/enterprise-features branch January 10, 2026 08:21
Sign up for freeto 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.

1 participant

@icecrasher321