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

Security(M-01): Keycloak group names interpolated unquoted into a root sh -c init command

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

Nobody has claimed this yet.

area: security priority: medium ⚡
Dominant language
Python
Stars
5
Forks
7
Avg merge
1d 20h
Merged PRs (30d)
13

Description

Summary

The spawner interpolates Keycloak group names, unquoted, into a shell command that runs as root in an init container. There is no safe-character validation, so shell metacharacters in a group name alter command execution.

Severity: Medium · CWE-78 (OS Command Injection)
Validation: Confirmed against HEAD f932d80 on 2026-07-14 (assessed at 69c84f7; unchanged since).

Evidence

  • config/jupyterhub/01-spawner.py:858-861 builds mkdir -p /mnt/shared/{g} && chown 0:100 /mnt/shared/{g} && chmod 2775 /mnt/shared/{g} for each group with {g} unquoted, joins them, and at 01-spawner.py:865-866 runs them via ["sh", "-c", mkdir_cmds] with securityContext: {runAsUser: 0}. Line 866 is the only runAsUser: 0 in the file.
  • A postStart hook does the same for /home/jovyan/shared/{group}: 01-spawner.py:989 (unquoted) joined into ["/bin/sh", "-c", ...] at 01-spawner.py:1004. This one runs in the singleuser container as the notebook user, not root, so its impact is lower than the init container.
  • No sanitization: the only transform on group names is Path(g).name (01-spawner.py:821), which is a path-leaf extractor, not a shell escaper. escapism.escape exists in the file (01-spawner.py:1022) but is used only for the PVC-name slug. For contrast, usernames written to passwd/group files are safely quoted with Python repr() (01-spawner.py:938-941), so the group-name path is an inconsistency.

Impact

An actor who can create, rename, or influence a Keycloak group and place a user in it can execute arbitrary shell syntax in the init container as root, modify shared data, and manipulate startup state. The precondition is control of a group name, which is normally a delegated-admin or IdP operation, so this is an admin-or-IdP-to-root escalation rather than a self-service user path.

Remediation

  • Derive a strict filesystem identifier from an immutable group ID.
  • Use filesystem APIs or argument arrays rather than a shell string.
  • Reject unsafe historical group names.
  • Give the init container a non-root, minimal-capability design.

Acceptance criteria

  • Generated pods contain no group-derived text inside an sh -c string.
  • Fuzz tests prove that spaces, quotes, substitutions, semicolons, newlines, slashes, and Unicode confusables cannot alter execution.

Source: data-science-pack 0.1.0 security assessment (pinned commit 69c84f72df259ec755ed40bfc83f20158c550d55), finding M-01.

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 lines 821, 858-866, 989, and 1004, tracing how Keycloak group names enter the init and postStart shell commands. Review the listed remediation options and add fuzz coverage for spaces, quotes, substitutions, semicolons, newlines, slashes, and Unicode confusables. Done means generated pods contain no group-derived text inside an sh -c string.

Written by the indexing model from the issue text.

Assessment

Tech stack
kubernetes, python, shell
Domain
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.