Summary
The hub's OIDC login client doubles as its Keycloak Admin API service account, and the same client secret is forwarded into the user-facing japps service. The login client is granted broad realm-management read roles, so compromise of this widely distributed credential permits realm-wide enumeration of users, groups, clients, and configuration.
Severity: Medium · CWE-250 (Execution with Unnecessary Privileges)
Validation: Confirmed against HEAD f932d80 on 2026-07-14 (assessed at 69c84f7; unchanged since).
Evidence
- Login credentials reused for Admin API:
config/jupyterhub/00-gateway-auth.py:480-490 builds the Admin API client from the same OAuth client_id/client_secret, and _client_credentials_token (00-gateway-auth.py:149-161) performs a client_credentials grant with them.
- Secret forwarded to japps:
config/jupyterhub/02-jhub-apps.py:79-84 injects JUPYTERHUB_OIDC_CLIENT_SECRET into the japps subprocess environment.
- Broad roles:
files/keycloak_rbac_bootstrap.py:64 assigns view-clients, view-groups, view-realm, view-users. Note the values.yaml:266 comment advertises only three of these (it omits view-users), so the docs understate what the code grants.
Impact
The platform needs limited authorization-lookup capability, but binding realm-management roles to the login credential and then forwarding that credential to a user-facing service means a single compromise can enumerate every user, group, and client in the realm.
Remediation
- Separate login credentials from administration credentials.
- Use a purpose-specific identity or an authorization broker that exposes only the required group/profile decision.
- Never forward administrative credentials into a user-facing service.
- Rotate the existing secret.
- Fix the
values.yaml comment to match the roles actually granted.
Acceptance criteria
Source: data-science-pack 0.1.0 security assessment (pinned commit 69c84f72df259ec755ed40bfc83f20158c550d55), finding M-07.
Summary
The hub's OIDC login client doubles as its Keycloak Admin API service account, and the same client secret is forwarded into the user-facing japps service. The login client is granted broad realm-management read roles, so compromise of this widely distributed credential permits realm-wide enumeration of users, groups, clients, and configuration.
Severity: Medium · CWE-250 (Execution with Unnecessary Privileges)
Validation: Confirmed against HEAD
f932d80on 2026-07-14 (assessed at69c84f7; unchanged since).Evidence
config/jupyterhub/00-gateway-auth.py:480-490builds the Admin API client from the same OAuthclient_id/client_secret, and_client_credentials_token(00-gateway-auth.py:149-161) performs aclient_credentialsgrant with them.config/jupyterhub/02-jhub-apps.py:79-84injectsJUPYTERHUB_OIDC_CLIENT_SECRETinto the japps subprocess environment.files/keycloak_rbac_bootstrap.py:64assignsview-clients,view-groups,view-realm,view-users. Note thevalues.yaml:266comment advertises only three of these (it omitsview-users), so the docs understate what the code grants.Impact
The platform needs limited authorization-lookup capability, but binding realm-management roles to the login credential and then forwarding that credential to a user-facing service means a single compromise can enumerate every user, group, and client in the realm.
Remediation
values.yamlcomment to match the roles actually granted.Acceptance criteria
Source: data-science-pack 0.1.0 security assessment (pinned commit
69c84f72df259ec755ed40bfc83f20158c550d55), finding M-07.