CVE

CVE-2026-46552: NocoDB Shared-Base Links Could Invite Real Base Members and Survive Share Revocation

A public shared-base link in NocoDB inherited member-management permissions, which let an anonymous share session enumerate members, invite arbitrary users into the base, and convert temporary link access into durable authenticated membership.

date: 2026-05-29
CONTENTS

Intro

I found this issue while reviewing NocoDB, with a simple security question in mind:

Can a public shared-base link cross the boundary from temporary shared access into real authenticated base membership?

In this case, the answer was yes.

A shared-base session authenticated only by xc-shared-base-id was treated as a normal base viewer for ACL purposes. Because viewer permissions still reached member-management endpoints, a user with only the shared-base UUID could enumerate existing base members and invite an arbitrary email address into the base as a real member.

That invited user could then redeem the invite through the normal signup flow, obtain a standard authenticated account, and keep base access even after the owner disabled the shared-base link.

That issue became CVE-2026-46552.

NocoDB: NocoDB

Affected version validated: 0.301.3

This affected NocoDB, on its official site, NocoDB is presented as trusted by 35,000+ organizations, with 20+ million downloads. The site also lists companies such as Accenture, Western Digital, Hyundai, Walmart, PwC, Bosch, and American Express.

NocoDB

Attack Chain

public shared-base link -> xc-shared-base-id treated as normal base viewer -> viewer ACL reaches member-management endpoints -> attacker lists base users and invites arbitrary email -> invited user redeems normal signup token -> durable authenticated base access survives shared-link revocation


What NocoDB Does

NocoDB is a database-oriented collaboration platform that exposes browser-based base access, sharing, metadata management, and user membership workflows.

That means its sharing model is a real security boundary.

The important question here was not whether shared-base links can read shared content.

The real question was:

Can a public share principal perform actions that should belong only to authenticated base members?

In this case, it could.


Why This Surface Was Worth Looking At

Public sharing features are easy to underestimate.

That is a mistake.

Once an application supports:

  • anonymous or link-based access,
  • role mapping,
  • and ordinary authenticated management APIs behind the same ACL system,

the main risk is not just data exposure.

The stronger risk is boundary collapse:

  • a lower-trust principal inherits higher-trust capabilities,
  • management actions become reachable from a public-share context,
  • and temporary access can be converted into persistent access.

That was the real issue here.

This was not a bug in login validation. It was not a token forgery issue. It was not a password-reset flaw.

It was a classic authorization boundary failure:

  • a shared-link principal was mapped into normal base roles,
  • those roles still included member-management capabilities,
  • and real durable access-control state could then be modified from the public-share context.

The Boundary I Focused On

I did not approach this by randomly probing endpoints and hoping something interesting would respond.

The stronger path was to identify the highest-value trust boundary first.

For NocoDB, that was the boundary between:

  • shared-base access
  • and authenticated base membership

Those two states should not be interchangeable.

A shared-base link is supposed to represent scoped, revocable, link-based access. It should not be able to mint new long-lived principals inside the base.

That is the exact boundary that failed here.


Root Cause

The vulnerability came from how shared-base access was integrated into the normal ACL path.

In the shared-base frontend flow, xc-shared-base-id was injected while ordinary auth headers were removed.

Then, in the backend, BaseViewStrategy accepted xc-shared-base-id and translated the shared link directly into ordinary roles / base_roles derived from the shared-base configuration.

That was the first problem.

The second problem was that viewer-level permissions still included member-management actions.

In the ACL layer, ProjectRoles.VIEWER could reach:

  • baseUserList
  • userInvite

Those permissions guarded normal meta routes:

  • GET /api/v2/meta/bases/:baseId/users
  • POST /api/v2/meta/bases/:baseId/users

So a public-share session was effectively allowed to hit membership endpoints intended for real base participants.

The last step was in the invite flow itself.

BaseUsersService.userInvite() checked role power, then created:

  • a real user row with an invite_token
  • a real base-membership row for the target base

And for shared-base sessions:

  • invited_by became null

because there was no real authenticated inviter identity behind the request.

That is the whole bug chain.

Why this is exploitable

Because possession of a shared-base link was enough.

The attacker did not need:

  • xc-auth
  • a pre-existing account
  • stolen credentials
  • or prior membership in the base

The exploit chain was straightforward:

  • attacker obtains a shared-base UUID
  • the UUID is accepted as a base viewer principal
  • viewer ACL reaches member-management endpoints
  • attacker lists current base members
  • attacker invites an arbitrary email address
  • the invited user redeems the token through the normal signup flow
  • the new account becomes a real authenticated base member
  • the owner later disables the shared link
  • the invited account still keeps normal authenticated access

That converts revocable link-sharing into durable membership.


What Makes This a Security Issue, Not Just Odd Sharing Behavior

The important distinction is persistence across revocation.

This was not just:

"a viewer could call a viewer endpoint"

The vulnerable principal was not a normal authenticated viewer.

It was a public-share session.

That matters because the application treated a transient, link-scoped principal as if it were trusted enough to:

  • enumerate real members,
  • change access-control state,
  • and create new durable principals inside the base

The real question was not:

"Can a shared user read shared data?"

The real question was:

"Can public-share access be converted into permanent authenticated access that survives share revocation?"

The answer was yes.

That is why this is a real authorization vulnerability rather than just surprising application behavior.


PoC

I validated this locally against:

  • product version: 0.301.3
  • commit: dac49b0122c5ee655fb8f46a1b6e42dfeec1f3ad
  • base URL: http://127.0.0.1:8080

The reproduction was straightforward.

First, I signed in as a normal owner account, created a fresh base, created a table, and enabled shared-base access as viewer:

HTTP
PATCH /api/v2/meta/bases/<baseId>/shared
Content-Type: application/json
 
{
  "roles": "viewer"
}

That returned the shared-base UUID.

Then, without sending any xc-auth, I used only:

HTTP
xc-shared-base-id: <sharedBaseUuid>

Using only that header, I called:

HTTP
GET /api/v2/meta/bases/<baseId>/users

That returned 200 OK and exposed real base members, including email addresses.

Still using only xc-shared-base-id, I called:

HTTP
POST /api/v2/meta/bases/<baseId>/users
Content-Type: application/json
 
{
  "email": "attacker@example.com",
  "roles": "viewer"
}

That also returned 200 OK.

For local lab validation without email delivery, I confirmed directly in the SQLite meta database that:

  • nc_users_v2 contained the invited user with a non-null invite_token
  • nc_base_users_v2 contained a real membership row for the target base
  • invited_by was NULL

Then I redeemed the invite through the normal signup flow:

HTTP
POST /api/v2/auth/user/signup
Content-Type: application/json
 
{
  "email": "attacker@example.com",
  "password": "Password123.",
  "token": "<invite_token>"
}

Using the returned xc-auth, I called:

HTTP
GET /api/v2/meta/bases/<baseId>/tables

That returned 200 OK.

Finally, as the owner, I disabled the shared-base link:

HTTP
DELETE /api/v2/meta/bases/<baseId>/shared

After that:

  • shared-link access with xc-shared-base-id failed with 401
  • the invited account using normal xc-auth still succeeded with 200

Observed results

  • shared user list: 200
  • shared invite: 200
  • signup: 200
  • invited authenticated table access before share disable: 200
  • share disable: 200
  • shared-link table access after disable: 401
  • invited authenticated table access after disable: 200

That established the central security claim:

  • public share access could reach membership endpoints
  • membership changes created real durable authenticated access
  • revoking the original share did not remove that access

Why the Reproduction Matters

One successful shared-base invite would already have been enough to show an authorization failure.

But the full validation chain mattered for two reasons.

First

It showed that this was not just endpoint exposure.

The public-share session did not merely reach a restricted API. It completed the full privilege-conversion chain:

  • enumerate members
  • invite a new principal
  • redeem the invite
  • obtain normal authenticated access

Second

It proved this was not self-revoking.

The more serious impact came after shared-link disablement:

  • the original link died
  • the attacker-created account did not

That is what turned temporary link access into durable access persistence.


Impact

This vulnerability allows anyone with a shared-base link to:

  • enumerate real base members and their email addresses
  • invite arbitrary email addresses into the base as real members
  • convert temporary link-based access into persistent authenticated membership
  • preserve that access even after the owner revokes the shared link

The primary impact is confidentiality, because an attacker can maintain lasting read access to shared-base data through an ordinary authenticated account.

There is also integrity impact, because a public-share principal can modify access-control state by adding new members to the base.

That is a stronger outcome than ordinary data leakage. It is a privilege-boundary break between anonymous sharing and authenticated membership.


Severity and Classification

This issue is reasonably classified as a cross-scope authorization flaw with confidentiality impact.

CVSS:

TEXT
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N

That vector fits the core behavior here:

  • network-reachable behavior
  • no prior authenticated account required
  • no user interaction required from the victim during exploitation
  • scope changes because a public-share principal crosses into normal membership-management capability
  • confidentiality impact through lasting unauthorized base access

Suggested Mitigation

The fix direction is straightforward.

Shared-base sessions should not inherit member-management capabilities.

At minimum:

  • remove baseUserList and userInvite from any permissions reachable through xc-shared-base-id
  • enforce an explicit block so shared/public principals cannot call base membership endpoints such as GET and POST /api/v2/meta/bases/:baseId/users
  • treat shared-base access as a distinct principal type instead of mapping it directly onto normal base viewer permissions
  • add regression tests that verify shared-base requests cannot enumerate members, cannot invite users, and cannot create durable access that survives share revocation

Disclosure

This issue was validated locally against NocoDB 0.301.3 on commit dac49b0122c5ee655fb8f46a1b6e42dfeec1f3ad.

The report demonstrated:

  • the ACL mapping from xc-shared-base-id into normal base roles
  • the viewer permission path into member-management endpoints
  • the creation of real invited users and base-membership rows
  • the ability to redeem the invitation through the normal signup flow
  • the persistence of authenticated access after shared-link revocation

The issue was assigned:

CVE-2026-46552


What This Bug Actually Teaches

The key lesson here is simple:

shared-link access is not the same thing as trusted membership.

A lot of systems get into trouble when they collapse those two ideas into the same role model.

A shared link may look operationally similar to a viewer account, but the trust assumptions are different:

  • shared links are easy to redistribute
  • shared links are meant to be revocable
  • shared links are usually lower-assurance principals

If that lower-assurance principal can perform management actions or mint new durable identities, the sharing boundary is already broken.

That is the real takeaway.


Key Points

  • public sharing features are security boundaries
  • shared-link principals should not inherit ordinary membership-management capabilities
  • member enumeration from a public-share context is already sensitive
  • unauthorized invitation is worse because it creates real durable principals
  • revoking the original share is not enough if the attacker-created account survives
  • treating public-share access as a separate principal type is the safer design

Final Words

This vulnerability was not about bypassing authentication entirely.

It was about collapsing two trust levels that should have remained separate.

In NocoDB, a shared-base link was supposed to provide temporary, revocable access to shared content. Instead, it could be used to enumerate members, invite a real user into the base, and convert public-share access into durable authenticated membership that survived share revocation.

That is why this became CVE-2026-46552.