Summary
Authorization revocation fails open during Keycloak errors. A transient refresh failure keeps the current auth state, and a failed Admin API query after a successful token refresh preserves the previous mount and profile grants. There is no maximum age on stale grants, so a revoked user can retain access for as long as the Admin API keeps erroring.
Severity: Medium · CWE-754 (Improper Handling of Exceptional Conditions)
Validation: Confirmed against HEAD f932d80 on 2026-07-14 (assessed at 69c84f7; unchanged since).
Evidence
config/jupyterhub/00-gateway-auth.py:359-363: on a transient refresh error, refresh_user logs and return True, keeping the current auth state. Only 400 invalid_grant returns False and forces re-login (00-gateway-auth.py:353-358).
- After a successful refresh, a failed group query preserves the old set:
00-gateway-auth.py:387-396 restores groups_with_permission_to_mount; 00-gateway-auth.py:406-414 does the same for allowed_jupyterlab_profiles.
- Initial login fails closed:
update_auth_model sets filtered = [] on Admin API failure (00-gateway-auth.py:445-452) and _resolve_allowed_profiles returns [] on failure (00-gateway-auth.py:472-478).
- No max stale-grant age:
refresh_user has no timestamp or age comparison; preservation is unconditional on old_* is not None.
Impact
A user removed from a shared-storage role or a restricted profile keeps that access across every subsequent refresh while the Keycloak Admin API is unavailable. The initial-login path is safe; the gap is mid-session refresh.
Remediation
- Timestamp the last successful authorization evaluation.
- Apply a short maximum age for sensitive mounts and profiles, and fail closed past that age while still allowing lower-risk login where appropriate.
- Consume Keycloak identity events where available.
- Alert on stale authorization state.
Acceptance criteria
Source: data-science-pack 0.1.0 security assessment (pinned commit 69c84f72df259ec755ed40bfc83f20158c550d55), finding M-02.
Summary
Authorization revocation fails open during Keycloak errors. A transient refresh failure keeps the current auth state, and a failed Admin API query after a successful token refresh preserves the previous mount and profile grants. There is no maximum age on stale grants, so a revoked user can retain access for as long as the Admin API keeps erroring.
Severity: Medium · CWE-754 (Improper Handling of Exceptional Conditions)
Validation: Confirmed against HEAD
f932d80on 2026-07-14 (assessed at69c84f7; unchanged since).Evidence
config/jupyterhub/00-gateway-auth.py:359-363: on a transient refresh error,refresh_userlogs andreturn True, keeping the current auth state. Only400 invalid_grantreturnsFalseand forces re-login (00-gateway-auth.py:353-358).00-gateway-auth.py:387-396restoresgroups_with_permission_to_mount;00-gateway-auth.py:406-414does the same forallowed_jupyterlab_profiles.update_auth_modelsetsfiltered = []on Admin API failure (00-gateway-auth.py:445-452) and_resolve_allowed_profilesreturns[]on failure (00-gateway-auth.py:472-478).refresh_userhas no timestamp or age comparison; preservation is unconditional onold_* is not None.Impact
A user removed from a shared-storage role or a restricted profile keeps that access across every subsequent refresh while the Keycloak Admin API is unavailable. The initial-login path is safe; the gap is mid-session refresh.
Remediation
Acceptance criteria
Source: data-science-pack 0.1.0 security assessment (pinned commit
69c84f72df259ec755ed40bfc83f20158c550d55), finding M-02.