nebari-dev / nebari-dev/data-science-pack
Security(M-01): Keycloak group names interpolated unquoted into a root sh -c init command
Nobody has claimed this yet.
- 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-861buildsmkdir -p /mnt/shared/{g} && chown 0:100 /mnt/shared/{g} && chmod 2775 /mnt/shared/{g}for each group with{g}unquoted, joins them, and at01-spawner.py:865-866runs them via["sh", "-c", mkdir_cmds]withsecurityContext: {runAsUser: 0}. Line 866 is the onlyrunAsUser: 0in the file.- A postStart hook does the same for
/home/jovyan/shared/{group}:01-spawner.py:989(unquoted) joined into["/bin/sh", "-c", ...]at01-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.escapeexists 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 Pythonrepr()(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 -cstring. - 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
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 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