Skip to content

fix(ai-gateway): require explicit provider approval#2826

Merged
marius-kilocode merged 8 commits intomainfrom
marius/fix-enterprise-provider-allowlists
Apr 28, 2026
Merged

fix(ai-gateway): require explicit provider approval#2826
marius-kilocode merged 8 commits intomainfrom
marius/fix-enterprise-provider-allowlists

Conversation

@marius-kilocode
Copy link
Copy Markdown
Contributor

@marius-kilocode marius-kilocode commented Apr 27, 2026

Summary

This PR changes enterprise provider/model access policy to use provider allow lists and model deny lists.

  • Providers now require explicit approval after migration: newly synced providers are disabled until an org selects them.
  • Models remain deny-list based: newly synced models from already-approved providers are available by default unless an org disables them.
  • Existing legacy provider deny-list settings are backfilled into provider allow-list snapshots so currently effective provider choices are preserved at migration time.

Why

Enterprise customers use provider restrictions as a vendor/privacy boundary. The previous deny-list-only provider behavior meant that when the global provider catalog changed, a newly introduced provider could become effectively enabled because it was not present in an older org-level provider_deny_list. That change did not mutate organization settings, so there was no corresponding org audit event for “enabling” the provider.

The previous deny-list direction had a valid product motivation for models: users expect new models from trusted providers to become available without manually approving every model SKU. This PR preserves that model behavior while making provider access strict.

History

Implementation

  • Adds provider_policy_mode: 'allow' as the marker for orgs that should use provider_allow_list as source of truth.
  • Ignores stale legacy provider_allow_list values unless provider_policy_mode is set, preventing old pre-deny-list fields from accidentally overriding newer policy.
  • Sends provider.only to OpenRouter for migrated enterprise orgs; falls back to legacy provider.ignore before migration.
  • Keeps model enforcement on model_deny_list so new models from approved providers remain allowed unless explicitly disabled.
  • Updates provider/model configuration UI so provider toggles update allow lists, while model toggles update deny lists.
  • Adds apps/web/src/scripts/db/backfill-provider-allow-lists.ts to convert enterprise orgs from provider deny-list snapshots into provider allow-list snapshots.

Verification

Manual browser verification not run; behavior is covered by targeted policy, router, defaults, and UI state tests.

  • pnpm --filter web test -- \"apps/web/src/components/organizations/providers-and-models/allowLists.domain.test.ts\" \"apps/web/src/components/organizations/providers-and-models/useProvidersAndModelsAllowListsState.test.ts\" \"apps/web/src/components/organizations/OrganizationProvidersAndModelsConfigurationCard.test.ts\" \"apps/web/src/lib/ai-gateway/llm-proxy-helpers.test.ts\" \"apps/web/src/lib/model-allow.server.test.ts\" \"apps/web/src/app/api/organizations/[id]/defaults/route.test.ts\" \"apps/web/src/routers/organizations/organization-settings-router.test.ts\" \"apps/web/src/lib/organizations/organization-usage.test.ts\" \"apps/web/src/lib/organizations/organization-subscription-event.test.ts\"
  • pnpm --filter web typecheck

Both pass locally with the existing Node engine warning because this shell is on Node v25.2.1, while the repo expects >=24 <25.

@marius-kilocode
Copy link
Copy Markdown
Contributor Author

PR history / root cause context

This fixes a latent regression introduced by #799 (Model/provider allow list to deny list, merge commit 0c4e09032). That PR made deny lists canonical, changed provider enforcement from provider.only to provider.ignore, and made the UI compute selected providers as current synced providers - provider_deny_list. As a result, any provider added to the synced OpenRouter/provider catalog after an org's deny-list snapshot was saved is treated as enabled because it is absent from the stored deny list.

Related history:

Why this surfaced later: the bug only becomes visible after the global provider snapshot changes. Existing denied providers stay denied, but new provider slugs are not present in older org provider_deny_list values, and no org audit log is emitted because the org settings row does not change.

Comment thread apps/web/src/lib/model-allow.server.ts Outdated
Comment thread apps/web/src/lib/model-allow.server.ts Outdated
Comment thread apps/web/src/scripts/db/backfill-model-allow-lists.ts Outdated
@kilo-code-bot
Copy link
Copy Markdown
Contributor

kilo-code-bot Bot commented Apr 27, 2026

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (1 file)
  • apps/web/src/app/api/organizations/[id]/defaults/route.test.ts

Reviewed by gpt-5.5-2026-04-23 · 774,585 tokens

@marius-kilocode
Copy link
Copy Markdown
Contributor Author

marius-kilocode commented Apr 27, 2026

I validated the bug class against production data. The observed state is consistent with the root cause this PR fixes:

  • Enterprise provider/model policy can exist as a deny-list snapshot while the provider catalog continues to change globally.
  • Providers that are present in the latest catalog but absent from the stored deny list are treated as enabled by the current implementation.
  • This can happen without an org settings audit event because the org settings row is not changed when the global provider catalog changes.
  • Existing audit history can still show users later disabling an unexpected provider, which records as adding that provider to the deny list, but that is remediation rather than evidence of an earlier explicit enable action.

The customer-specific evidence is mixed for individual providers because some providers had prior user actions, but the production shape confirms the underlying deny-list semantics are unsafe: newly introduced provider slugs can become effectively enabled unless every restricted enterprise org deny list is updated. This PR moves enforcement back to explicit allow-list semantics so new providers remain disabled until selected.

@marius-kilocode
Copy link
Copy Markdown
Contributor Author

marius-kilocode commented Apr 27, 2026

  • Explicitly allowed non-OpenRouter models with no provider metadata are now allowed even when a provider allow list exists, so custom/direct model IDs that are intentionally listed are not rejected by OpenRouter-only provider lookup.
  • Mixed legacy settings are handled more deliberately: legacy deny lists are used only when no allow lists exist; once allow-list settings exist, the explicit allow-list policy is the source of truth.
  • The backfill now includes org-scoped custom LLM IDs and enabled direct BYOK model IDs in generated model_allow_list values instead of deriving only from the OpenRouter snapshot.

@chrarnoldus
Copy link
Copy Markdown
Contributor

If we're going to bring back the allow list, please keep the custom LLMs and direct BYOK models out of it. They already require manual steps to enable and the change as proposed will probably break them, because there's no way to allowlist them.

@marius-kilocode marius-kilocode changed the title fix(ai-gateway): pin enterprise provider access fix(ai-gateway): require explicit provider approval Apr 28, 2026
@marius-kilocode
Copy link
Copy Markdown
Contributor Author

Policy alignment update

Updated the PR to match the agreed product direction:

  • Provider access is now allow-list based after migration, so newly synced providers stay disabled until explicitly selected.
  • Model access remains deny-list based, so new models from approved providers remain available by default unless explicitly disabled.
  • Added provider_policy_mode: 'allow' so stale legacy provider_allow_list values are ignored until an org has been migrated/backfilled.
  • Replaced the model/provider backfill with a provider-only backfill that snapshots currently effective provider access from the latest provider catalog minus the org's legacy provider deny list.
  • Rewrote the PR description to explain what changed, why it changed, and the PR history behind the allow-list vs deny-list behavior.

Re-ran targeted tests and typecheck; both pass locally with the existing Node engine warning (v25.2.1 vs expected >=24 <25).

Comment thread packages/db/src/schema-types.ts Outdated
Comment thread packages/db/src/schema-types.ts
chrarnoldus
chrarnoldus previously approved these changes Apr 28, 2026
Comment thread apps/web/src/lib/organizations/organization-models.ts Outdated
@marius-kilocode marius-kilocode merged commit 5ce5b66 into main Apr 28, 2026
40 checks passed
@marius-kilocode marius-kilocode deleted the marius/fix-enterprise-provider-allowlists branch April 28, 2026 14:52
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