Skip to content

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

Description

@dcmcand

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: securitySecurity findings and hardening workpriority: medium ⚡Medium priority - standard queue

    Type

    No type

    Fields

    Priority

    None yet

    Start date

    None yet

    Target date

    None yet

    Size

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions