nebari-dev / nebari-dev/data-science-pack
Security(H-03): hierarchical Keycloak group paths collapse to leaf name, merging storage/profile principals
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 5
- Forks
- 7
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 13
Description
Summary
Shared-storage mounts and YAML profile gating both reduce each Keycloak group path to its leaf name with Path(group).name, discarding parent segments. Two groups in different hierarchies that share a leaf (for example /division-a/research and /division-b/research) collapse into one storage directory and one authorization principal.
Severity: High · CWE-706 (Use of Incorrectly-Resolved Name or Reference)
Validation: Confirmed against HEAD f932d80 on 2026-07-14 (assessed at 69c84f7; unchanged since).
Evidence
Leaf normalization happens in two independent places, and both feed an authorization or storage decision:
- Shared storage:
config/jupyterhub/01-spawner.py:821computesname = Path(g).name, then01-spawner.py:848-853mounts it atsubPath: shared/{group}. So/division-a/researchand/division-b/researchboth resolve toshared/researchon the same RWX PVC. - Profile gating:
config/jupyterhub/01-spawner.py:296applies the samePath(g).name, and_filter_profilesat01-spawner.py:335-342compares the leaf against a profile'sgroups:list. A profile author writinggroups: [research]admits members of any.../researchgroup.
The docstrings at 01-spawner.py:284,298 state this mirrors classic Nebari behavior, so it is a deliberate compatibility choice rather than an accidental slip. Exploiting it requires an admin or IdP that can create colliding leaf names.
Impact
- Unrelated teams receive the same writable shared directory and can read, delete, or alter each other's data.
- Group-gated YAML profiles can be exposed to the wrong unit.
- Group renames or reorganization can silently transfer storage access.
- Logs and filesystem paths obscure the full identity principal.
Remediation
- Preserve the full canonical Keycloak path, or preferably the immutable group UUID, as the authorization identity.
- Derive Kubernetes and filesystem names from a collision-resistant encoding of that identifier.
- Keep display names separate from authorization identities.
- Reject duplicate derived names and provide migration tooling for historical
shared/<leaf>data.
Acceptance criteria
- Same-leaf groups in different hierarchies receive distinct mounts and profile decisions.
- A group rename cannot silently transfer access.
- Upgrade tooling detects and reports every historical collision before changing paths.
Source: data-science-pack 0.1.0 security assessment (pinned commit 69c84f72df259ec755ed40bfc83f20158c550d55), finding H-03.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in config/jupyterhub/01-spawner.py at the group normalization around lines 284, 296, 298, 335-342, and 821-853. Trace how group identities reach profile filtering and shared-storage subPaths, then determine a collision-resistant identity and migration checks. Done means hierarchical groups remain distinct, renames do not transfer access, and historical shared-path collisions are detected before upgrades.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- kubernetes, python
- Domain
- authorization, infrastructure, security
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100