VYPR
Medium severity6.9GHSA Advisory· Published Jun 5, 2026· Updated Jun 5, 2026

NocoDB: Hidden Column Exposure in Public Shared View Endpoints

CVE-2026-47378

Description

Summary

Public shared-view endpoints exposed values from columns that the view owner had hidden, via three independent paths: groupBy returned raw values for any column named in the request, filter and sort arrays operated on hidden columns enabling boolean-blind extraction, and the related-data list accepted arbitrary link-column IDs from other tables in the same base.

Details

A new sanitizeListArgsForPublicView helper now strips request keys that should never be caller-controlled (e.g. getHiddenColumn, nested), parses where clauses against a restricted alias map that only contains visible columns, and recursively removes filter/sort entries whose fk_column_id is not in the visible set. validateGroupByColumnNames and validateGroupColumnId reject groupBy requests whose column_name (CSV-style) or groupColumnId is not in the visible or group-by column set. relDataList now checks column.fk_model_id === currentModel.id before resolving the linked table, matching the pre-existing check on publicMmList and publicHmList.

Impact

Anyone with a shared-view UUID could enumerate hidden-column values directly (via groupBy), confirm hidden-column values by observing row counts (via filter), or read records from unrelated tables in the same base (via the related-data list). No authentication was required.

Credit

This issue was reported by @0xBassia. It was independently reported by @b-hermes.

Affected products

1

Patches

1
4e6037f9fa1e

Merge pull request #13527 from nocodb/nc-feat/improve-feat-gating

https://github.com/nocodb/nocodbRamesh ManeApr 15, 2026Fixed in 2026.04.1via release-tag
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

3

News mentions

1