nebari-dev / nebari-dev/data-science-pack

Security(H-03): hierarchical Keycloak group paths collapse to leaf name, merging storage/profile principals

Open
#165 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

area: security priority: high 🔥
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:821 computes name = Path(g).name, then 01-spawner.py:848-853 mounts it at subPath: shared/{group}. So /division-a/research and /division-b/research both resolve to shared/research on the same RWX PVC.
  • Profile gating: config/jupyterhub/01-spawner.py:296 applies the same Path(g).name, and _filter_profiles at 01-spawner.py:335-342 compares the leaf against a profile's groups: list. A profile author writing groups: [research] admits members of any .../research group.

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.