NocoDB: User Enumeration via Sign-In Timing
Description
Summary
Sign-in response timing differed between known and unknown email addresses because the unknown-user branch returned without performing a password hash comparison.
Details
The unknown-user branch in auth.service.ts now performs a bcrypt.compare against a fixed dummy hash so the response time of failed sign-ins is approximately independent of whether the address exists. Rate limiting on the sign-in endpoint is implemented in the Enterprise build only and is not affected by this advisory.
Impact
A network-positioned attacker could enumerate registered email addresses by timing sign-in responses. Exploitation requires only the ability to send unauthenticated sign-in requests.
Credit
This issue was reported by @AndyAnh174.
Affected products
1Patches
14e6037f9fa1eMerge pull request #13527 from nocodb/nc-feat/improve-feat-gating
6 files changed · +96 −15
packages/nc-gui/components/dashboard/TreeView/Table/Node.vue+17 −9 modified@@ -675,17 +675,25 @@ const enabledOptions = computed(() => { </NcMenuItem> </template> </PaymentUpgradeBadgeProvider> - <NcMenuItem + <PaymentUpgradeBadgeProvider v-if="enabledOptions.tableRowLevelSecurity" - :data-testid="`sidebar-table-rls-${table.title}`" - class="nc-table-rls" - @click="onRowLevelSecurity" + :feature="PlanFeatureTypes.FEATURE_RLS" > - <div v-e="['c:table:rls']" class="flex gap-2 items-center w-full"> - <GeneralIcon icon="ncShield" class="opacity-80" /> - <div class="flex-1">{{ $t('objects.permissions.rlsPolicy.rowLevelSecurity') }}</div> - </div> - </NcMenuItem> + <template #default="{ click }"> + <NcMenuItem + :data-testid="`sidebar-table-rls-${table.title}`" + class="nc-table-rls" + @click="click(PlanFeatureTypes.FEATURE_RLS, onRowLevelSecurity)" + > + <div v-e="['c:table:rls']" class="flex gap-2 items-center w-full"> + <GeneralIcon icon="ncShield" class="opacity-80" /> + <div class="flex-1">{{ $t('objects.permissions.rlsPolicy.rowLevelSecurity') }}</div> + + <LazyPaymentUpgradeBadge :feature="PlanFeatureTypes.FEATURE_RLS" remove-click /> + </div> + </NcMenuItem> + </template> + </PaymentUpgradeBadgeProvider> <PaymentUpgradeBadgeProvider v-if="enabledOptions.tableDateDependency" :feature="PlanFeatureTypes.FEATURE_DATE_DEPENDENCY"
packages/nc-gui/components/workspace/integrations/IntegrationsTab.vue+14 −3 modified@@ -1,6 +1,6 @@ <script lang="ts" setup> import type { VNodeRef } from '@vue/runtime-core' -import { IntegrationCategoryType } from 'nocodb-sdk' +import { IntegrationCategoryType, PlanFeatureTypes } from 'nocodb-sdk' import NcModal from '~/components/nc/Modal.vue' import { type IntegrationItemType, SyncDataType } from '#imports' @@ -42,7 +42,7 @@ const { activeWorkspace } = storeToRefs(useWorkspace()) const { isSyncFeatureEnabled } = storeToRefs(useSyncStore()) -const { isEEFeatureBlocked } = useEeConfig() +const { isEEFeatureBlocked, blockAiIntegrations, showUpgradeToUseAiIntegrations } = useEeConfig() const easterEggToggle = computed(() => isFeatureEnabled(FEATURE_FLAG.INTEGRATIONS)) @@ -238,6 +238,11 @@ const handleAddIntegration = async (category: IntegrationCategoryType, integrati return } + if (category === IntegrationCategoryType.AI && blockAiIntegrations.value) { + showUpgradeToUseAiIntegrations({}) + return + } + await addIntegration(integration) } @@ -464,8 +469,14 @@ watch(activeViewTab, (value) => { > <div class="category-type-title flex gap-2"> {{ $t(category.title) }} + <LazyPaymentUpgradeBadge + v-if="category.value === IntegrationCategoryType.AI && blockAiIntegrations" + :feature="PlanFeatureTypes.FEATURE_AI_INTEGRATIONS" + :feature-enabled-callback="() => !blockAiIntegrations" + remove-click + /> <NcBadge - v-if="!category.isAvailable" + v-else-if="!category.isAvailable" :border="false" class="text-nc-content-brand !h-5 bg-nc-bg-brand text-xs font-normal px-2" >{{ $t('msg.toast.futureRelease') }}</NcBadge
packages/nc-gui/components/workspace/ViewTabs.vue+14 −3 modified@@ -13,7 +13,7 @@ const { loadCollaborators } = workspaceStore const { isUIAllowed, isBaseRolesLoaded } = useRoles() -const { isWsAuditEnabled, handleUpgradePlan, showUpgradeToUseTeams } = useEeConfig() +const { isWsAuditEnabled, handleUpgradePlan, showUpgradeToUseTeams, blockTeamsManagement, isEEFeatureBlocked } = useEeConfig() const { hasTeamsEditPermission, wsTabVisibility } = useWorkspaceTabVisibility(activeWorkspace) @@ -22,7 +22,7 @@ interface TabItem { key: string icon: string label: string - upgradeBadge?: { feature: string } + upgradeBadge?: { feature: PlanFeatureTypes; blocked: boolean } hidden?: boolean } @@ -38,6 +38,9 @@ const tabItems = computed<TabItem[]>(() => { key: 'teams', icon: 'ncBuilding', label: t('general.teams'), + upgradeBadge: isEEFeatureBlocked.value + ? { feature: PlanFeatureTypes.FEATURE_TEAM_MANAGEMENT, blocked: blockTeamsManagement.value } + : undefined, // Only show badge in On-prem without license hidden: !wsTabVisibility.value.teams, }, { @@ -56,6 +59,9 @@ const tabItems = computed<TabItem[]>(() => { key: 'audits', icon: 'audit', label: t('title.audits'), + upgradeBadge: isEEFeatureBlocked.value + ? { feature: PlanFeatureTypes.FEATURE_AUDIT_WORKSPACE, blocked: !isWsAuditEnabled.value } + : undefined, // Only show badge in On-prem without license hidden: !wsTabVisibility.value.audits, }, { @@ -105,7 +111,12 @@ const activeTab = computed({ <div class="tab-title"> <GeneralIcon :icon="item.icon" class="h-4 w-4" /> {{ item.label }} - <LazyPaymentUpgradeBadge v-if="item.upgradeBadge" :feature="item.upgradeBadge.feature" remove-click /> + <LazyPaymentUpgradeBadge + v-if="item.upgradeBadge" + :feature="item.upgradeBadge.feature" + :feature-enabled-callback="() => !item.upgradeBadge!.blocked" + remove-click + /> </div> </template> </a-tab-pane>
packages/nc-gui/composables/useEeConfig.ts+45 −0 modified@@ -90,6 +90,8 @@ export const useEeConfig = createSharedComposable(() => { const blockAiChat = computed(() => true) + const blockAiIntegrations = computed(() => true) + const blockDocAi = computed(() => true) const blockButtonVisibility = computed(() => true) @@ -212,6 +214,8 @@ export const useEeConfig = createSharedComposable(() => { const showUpgradeToUseAiChat = (..._args: any[]) => {} + const showUpgradeToUseAiIntegrations = (..._args: any[]) => {} + const showUpgradeToUseDocAi = (..._args: any[]) => {} const showUpgradeToUseButtonVisibility = (..._args: any[]) => {} @@ -234,8 +238,16 @@ export const useEeConfig = createSharedComposable(() => { const blockRecordTemplates = computed(() => false) + const blockRls = computed(() => true) + const showUpgradeToUseRecordTemplates = (..._args: any[]) => {} + const showUpgradeToUseRls = (..._args: any[]) => {} + + const showUpgradeToDuplicateTableToOtherWs = (..._args: any[]) => {} + + const showUpgradeToDuplicateTableToOtherBase = (..._args: any[]) => {} + const blockFormScheduling = computed(() => true) const showUpgradeToUseFormScheduling = (..._args: any[]) => {} @@ -269,12 +281,30 @@ export const useEeConfig = createSharedComposable(() => { const showUpgradeForEEFeature = (..._args: any[]) => {} + const blockSSO = computed(() => true) + const showUpgradeToUseSSO = (..._args: any[]) => {} const blockScim = computed(() => true) const showUpgradeToUseScim = (..._args: any[]) => {} + const blockSnapshots = computed(() => true) + + const showUpgradeToUseSnapshots = (..._args: any[]) => {} + + const blockCustomUrls = computed(() => true) + + const showUpgradeToUseCustomUrls = (..._args: any[]) => {} + + const blockScripts = computed(() => true) + + const showUpgradeToUseScripts = (..._args: any[]) => {} + + const blockWorkflows = computed(() => true) + + const showUpgradeToUseWorkflows = (..._args: any[]) => {} + return { calculatePrice, getLimit, @@ -358,6 +388,8 @@ export const useEeConfig = createSharedComposable(() => { showUpgradeToUseAiButtonField, blockAiChat, showUpgradeToUseAiChat, + blockAiIntegrations, + showUpgradeToUseAiIntegrations, blockDocAi, showUpgradeToUseDocAi, blockButtonVisibility, @@ -373,13 +405,17 @@ export const useEeConfig = createSharedComposable(() => { isHigherActivePlan, blockCardFieldHeaderVisibility, blockSync, + blockRls, blockUnique, blockUuidField, blockAutoNumberField, showUpgradeToUseSync, + showUpgradeToUseRls, showUpgradeToUseUnique, showUpgradeToUseUuidField, showUpgradeToUseAutoNumberField, + showUpgradeToDuplicateTableToOtherWs, + showUpgradeToDuplicateTableToOtherBase, blockAddNewSandbox, showSandboxPlanLimitExceededModal, blockRecordTemplates, @@ -403,8 +439,17 @@ export const useEeConfig = createSharedComposable(() => { showUpgradeToCreateWorkspace, showUpgradeToManageWorkspaceMembers, showUpgradeForEEFeature, + blockSSO, showUpgradeToUseSSO, blockScim, showUpgradeToUseScim, + blockSnapshots, + showUpgradeToUseSnapshots, + blockCustomUrls, + showUpgradeToUseCustomUrls, + blockScripts, + showUpgradeToUseScripts, + blockWorkflows, + showUpgradeToUseWorkflows, } })
packages/nc-gui/lang/en.json+2 −0 modified@@ -209,6 +209,8 @@ "upgradeToUseAiButtonFieldSubtitle": "Upgrade to the {plan} plan to let AI Button use record data with NocoAI to fill multiple fields automatically.", "upgradeToUseAiChat": "Upgrade to use AI Chat", "upgradeToUseAiChatSubtitle": "Upgrade to the {plan} plan to use AI Chat to manage your base with natural language.", + "upgradeToUseAiIntegrations": "Upgrade to add AI Integrations", + "upgradeToUseAiIntegrationsSubtitle": "Upgrade to the {plan} plan to connect your own AI provider.", "upgradeToUseDocAi": "Upgrade to use Document AI", "upgradeToUseDocAiSubtitle": "Upgrade to the {plan} plan to use AI writing, summarization, and translation in documents.", "upgradeToUseColourField": "Upgrade to use Colour fields",
packages/nocodb-sdk/src/lib/payment/index.ts+4 −0 modified@@ -103,6 +103,8 @@ export enum PlanFeatureTypes { FEATURE_DATE_DEPENDENCY = 'feature_date_dependency', FEATURE_API_COMMENT_V3 = 'feature_api_comment_v3', FEATURE_API_WORKFLOW_MANAGEMENT = 'feature_api_workflow_management', + /** On-prem: core EE capability flag — true for all paid plans, false for free */ + FEATURE_EE_CORE = 'feature_ee_core', } export enum PlanTitles { @@ -113,6 +115,7 @@ export enum PlanTitles { } export enum OnPremPlanTitles { + FREE = 'Free', SELF_HOSTED_STARTER = 'Self-hosted Starter', SELF_HOSTED_SCALE = 'Self-hosted Scale', SELF_HOSTED_ENTERPRISE = 'Self-hosted Enterprise', @@ -362,6 +365,7 @@ export const PlanFeatureUpgradeMessages: Record<PlanFeatureTypes, string> = { [PlanFeatureTypes.FEATURE_DATE_DEPENDENCY]: 'to use date dependencies.', [PlanFeatureTypes.FEATURE_API_COMMENT_V3]: 'to use comment api.', [PlanFeatureTypes.FEATURE_API_WORKFLOW_MANAGEMENT]: 'to use workflow api.', + [PlanFeatureTypes.FEATURE_EE_CORE]: 'to access enterprise features.', }; export const getUpgradeMessage = (
Vulnerability mechanics
No source-code context for this CVE — mechanics is only generated when we can read the actual fix diff. Without that, the four sections (root cause, attack vector, affected code, fix) would be speculation rather than analysis.
References
3News mentions
1- Nocodb: 14 Vulnerabilities Disclosed Together, Including XSS and SQL InjectionVypr Intelligence · Jun 5, 2026