
NocoDB Shared-Base Links Could Invite Real Base Members and Survive Share Revocation
NocoDB Shared-Base Links Could Invite Real Base Members and Survive Share Revocation
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.
Project: 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.
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
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.
Public sharing features are easy to underestimate.
That is a mistake.
Once an application supports:
the main risk is not just data exposure.
The stronger risk is boundary collapse:
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:
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:
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.
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:
baseUserListuserInviteThose permissions guarded normal meta routes:
GET /api/v2/meta/bases/:baseId/usersPOST /api/v2/meta/bases/:baseId/usersSo 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:
invite_tokenAnd for shared-base sessions:
invited_by became nullbecause there was no real authenticated inviter identity behind the request.
That is the whole bug chain.
Because possession of a shared-base link was enough.
The attacker did not need:
xc-authThe exploit chain was straightforward:
That converts revocable link-sharing into durable membership.
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:
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.
I validated this locally against:
0.301.3dac49b0122c5ee655fb8f46a1b6e42dfeec1f3adhttp://127.0.0.1:8080The 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:
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:
xc-shared-base-id: <sharedBaseUuid>
Using only that header, I called:
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:
POST /api/v2/meta/bases/<baseId>/users
Content-Type: application/json
{
"email": "[email protected]",
"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_tokennc_base_users_v2 contained a real membership row for the target baseinvited_by was NULL