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.
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:
baseUserListuserInvite
Those permissions guarded normal meta routes:
GET /api/v2/meta/bases/:baseId/usersPOST /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_bybecamenull
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:
That returned the shared-base UUID.
Then, without sending any xc-auth, I used only:
Using only that header, I called:
That returned 200 OK and exposed real base members, including email addresses.
Still using only xc-shared-base-id, I called:
That also returned 200 OK.
For local lab validation without email delivery, I confirmed directly in the SQLite meta database that:
nc_users_v2contained the invited user with a non-nullinvite_tokennc_base_users_v2contained a real membership row for the target baseinvited_bywasNULL
Then I redeemed the invite through the normal signup flow:
Using the returned xc-auth, I called:
That returned 200 OK.
Finally, as the owner, I disabled the shared-base link:
After that:
- shared-link access with
xc-shared-base-idfailed with401 - the invited account using normal
xc-authstill succeeded with200
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:
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
baseUserListanduserInvitefrom any permissions reachable throughxc-shared-base-id - enforce an explicit block so shared/public principals cannot call base membership endpoints such as
GETandPOST /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-idinto 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.
