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:
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_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 NULLThen 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:
xc-shared-base-id failed with 401xc-auth still succeeded with 200200200200200200401200That established the central security claim:
One successful shared-base invite would already have been enough to show an authorization failure.
But the full validation chain mattered for two reasons.
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:
It proved this was not self-revoking.
The more serious impact came after shared-link disablement:
That is what turned temporary link access into durable access persistence.
This vulnerability allows anyone with a shared-base link to:
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.
This issue is reasonably classified as a cross-scope authorization flaw with confidentiality impact.
CVSS:
That vector fits the core behavior here:
The fix direction is straightforward.
Shared-base sessions should not inherit member-management capabilities.
At minimum:
baseUserList and userInvite from any permissions reachable through xc-shared-base-idGET and POST /api/v2/meta/bases/:baseId/usersThis issue was validated locally against NocoDB 0.301.3 on commit dac49b0122c5ee655fb8f46a1b6e42dfeec1f3ad.
The report demonstrated:
xc-shared-base-id into normal base rolesThe issue was assigned:
CVE-2026-46552
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:
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.
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.