Summary
A user-selected Nebi workspace value is inserted into a shell command that runs nebi pull and pixi install. The value is interpolated into an sh -c string rather than passed as an argument array, and any failure in the chain is downgraded to a warning so the pod continues after a failed or partial environment operation. The init container also holds a short-lived Nebi credential and has no explicit security context in this custom code.
Severity: Medium · CWE-78 (OS Command Injection)
Validation: Confirmed against HEAD f932d80 on 2026-07-14 (assessed at 69c84f7; unchanged since).
Evidence
- User-controlled value:
config/jupyterhub/01-spawner.py:692-694 reads conda_env from spawner.user_options and derives workspace_name via rsplit, with no validation anywhere in config/jupyterhub/.
- Shell interpolation + masked failure:
01-spawner.py:751-761 builds /bin/sh -c "mkdir -p {ws_dir} && nebi pull {workspace_name} -o {ws_dir} --force && pixi install ... || echo 'WARNING: ...'". The trailing || echo catches any failure in the && chain and exits 0, so the init container succeeds even when the environment operation failed.
- Credential in init env:
01-spawner.py:719-722 sets NEBI_AUTH_TOKEN to the exchanged JWT.
- No explicit security context: the nebi-pull init container dict (
01-spawner.py:747-764) has no securityContext; the pod-level context (01-spawner.py:133) sets only fsGroup. This container runs as the pod default (non-root), so injected code runs at the user's own identity, not root.
Impact
The user already has code-execution authority in their own environment, so this is not an independent tenant escape. It does broaden command interpretation before the intended environment starts, places a credential in a shell-driven path, hides integrity failures, and undermines reproducibility. A user_options value like foo;<cmd> (no slash) survives rsplit intact into the shell string.
Remediation
- Validate workspace IDs against a strict grammar.
- Pass arguments without a shell.
- Give the init container an explicit restricted security context.
- Use a single-purpose, workspace-bound token.
- Fail the spawn when pull or install fails, rather than logging a warning and continuing.
Acceptance criteria
Source: data-science-pack 0.1.0 security assessment (pinned commit 69c84f72df259ec755ed40bfc83f20158c550d55), finding M-05.
Summary
A user-selected Nebi workspace value is inserted into a shell command that runs
nebi pullandpixi install. The value is interpolated into ansh -cstring rather than passed as an argument array, and any failure in the chain is downgraded to a warning so the pod continues after a failed or partial environment operation. The init container also holds a short-lived Nebi credential and has no explicit security context in this custom code.Severity: Medium · CWE-78 (OS Command Injection)
Validation: Confirmed against HEAD
f932d80on 2026-07-14 (assessed at69c84f7; unchanged since).Evidence
config/jupyterhub/01-spawner.py:692-694readsconda_envfromspawner.user_optionsand derivesworkspace_nameviarsplit, with no validation anywhere inconfig/jupyterhub/.01-spawner.py:751-761builds/bin/sh -c "mkdir -p {ws_dir} && nebi pull {workspace_name} -o {ws_dir} --force && pixi install ... || echo 'WARNING: ...'". The trailing|| echocatches any failure in the&&chain and exits 0, so the init container succeeds even when the environment operation failed.01-spawner.py:719-722setsNEBI_AUTH_TOKENto the exchanged JWT.01-spawner.py:747-764) has nosecurityContext; the pod-level context (01-spawner.py:133) sets onlyfsGroup. This container runs as the pod default (non-root), so injected code runs at the user's own identity, not root.Impact
The user already has code-execution authority in their own environment, so this is not an independent tenant escape. It does broaden command interpretation before the intended environment starts, places a credential in a shell-driven path, hides integrity failures, and undermines reproducibility. A
user_optionsvalue likefoo;<cmd>(no slash) survivesrsplitintact into the shell string.Remediation
Acceptance criteria
Source: data-science-pack 0.1.0 security assessment (pinned commit
69c84f72df259ec755ed40bfc83f20158c550d55), finding M-05.