You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ru...@apache.org on 2026/06/02 00:12:45 UTC
(superset) 01/01: fix(security): stop exposing registration_hash in the registrations API
This is an automated email from the ASF dual-hosted git repository.
rusackas pushed a commit to branch fix/hide-registration-hash
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 6210235b2bb4aef2d1131794956e20fcda9be32b
Author: Claude Code <no...@anthropic.com>
AuthorDate: Mon Jun 1 17:12:32 2026 -0700
fix(security): stop exposing registration_hash in the registrations API
UserRegistrationsRestAPI listed registration_hash in list_columns, exposing it
in admin API responses (and thus proxy logs, caches, browser history). The hash
is a bearer token for /register/activation/<hash>: anyone holding it can
activate the pending account, bypassing the email delivery channel.
Remove registration_hash from list_columns and from search_columns (so it can't
be enumerated via filter probing either). Remove the corresponding column,
filter, and type field from the UserRegistrations admin page and update its
test to assert the hash column is no longer rendered.
Co-Authored-By: Claude Opus 4.8 <no...@anthropic.com>
---
.../src/pages/UserRegistrations/UserRegistrations.test.tsx | 7 ++++++-
superset-frontend/src/pages/UserRegistrations/index.tsx | 14 --------------
superset/security/api.py | 13 ++++++++++++-
3 files changed, 18 insertions(+), 16 deletions(-)
diff --git a/superset-frontend/src/pages/UserRegistrations/UserRegistrations.test.tsx b/superset-frontend/src/pages/UserRegistrations/UserRegistrations.test.tsx
index 67856b35661..d57cac266ac 100644
--- a/superset-frontend/src/pages/UserRegistrations/UserRegistrations.test.tsx
+++ b/superset-frontend/src/pages/UserRegistrations/UserRegistrations.test.tsx
@@ -30,7 +30,6 @@ const mockUserRegistrations = Array.from({ length: 5 }, (_, i) => ({
last_name: `Test${i}`,
email: `user${i}@test.com`,
registration_date: new Date(2025, 2, 25, 11, 4, 32 + i).toISOString(),
- registration_hash: `hash${i}`,
}));
fetchMock.get(userRegistrationsEndpoint, {
@@ -53,4 +52,10 @@ describe('UserRegistrations', () => {
const calls = fetchMock.callHistory.calls(userRegistrationsEndpoint);
expect(calls.length).toBeGreaterThan(0);
});
+
+ test('does not expose the registration hash', async () => {
+ expect(await screen.findByText('User registrations')).toBeVisible();
+ // The activation hash is a bearer token and must not be shown in the UI.
+ expect(screen.queryByText('Registration hash')).not.toBeInTheDocument();
+ });
});
diff --git a/superset-frontend/src/pages/UserRegistrations/index.tsx b/superset-frontend/src/pages/UserRegistrations/index.tsx
index 07fa3b51c5b..8230b233962 100644
--- a/superset-frontend/src/pages/UserRegistrations/index.tsx
+++ b/superset-frontend/src/pages/UserRegistrations/index.tsx
@@ -40,7 +40,6 @@ export type UserRegistration = {
last_name: string;
email: string;
registration_date: string;
- registration_hash: string;
};
export default function UserRegistrations() {
@@ -110,12 +109,6 @@ export default function UserRegistrations() {
Header: t('Email'),
Cell: ({ row: { original } }: any) => original.email,
},
- {
- accessor: 'registration_hash',
- id: 'registration_hash',
- Header: t('Registration hash'),
- Cell: ({ row: { original } }: any) => original.registration_hash,
- },
{
accessor: 'registration_date',
id: 'registration_date',
@@ -177,13 +170,6 @@ export default function UserRegistrations() {
input: 'search',
operator: ListViewFilterOperator.Contains,
},
- {
- Header: t('Registration hash'),
- key: 'registration_hash',
- id: 'registration_hash',
- input: 'search',
- operator: ListViewFilterOperator.Contains,
- },
{
Header: t('Registration date'),
key: 'registration_date',
diff --git a/superset/security/api.py b/superset/security/api.py
index 9c8d035760e..b3f2cfc79de 100644
--- a/superset/security/api.py
+++ b/superset/security/api.py
@@ -355,6 +355,11 @@ class UserRegistrationsRestAPI(BaseSupersetModelRestApi):
resource_name = "security/user_registrations"
datamodel = SQLAInterface(RegisterUser)
allow_browser_login = True
+ # NOTE: registration_hash is intentionally excluded from both list_columns
+ # and search_columns. It is a bearer token for the
+ # /register/activation/<hash> flow; exposing it in API responses (and thus
+ # logs/caches) or allowing it to be filtered on would let a holder activate
+ # the pending account.
list_columns = [
"id",
"username",
@@ -362,5 +367,11 @@ class UserRegistrationsRestAPI(BaseSupersetModelRestApi):
"first_name",
"last_name",
"registration_date",
- "registration_hash",
+ ]
+ search_columns = [
+ "username",
+ "email",
+ "first_name",
+ "last_name",
+ "registration_date",
]