ADR-0002 — Jupyter fails closed: its own server command, one trusted issuer, no server token
Date: 2026-10-04 Status: accepted
Context
The Jupyter image put its configuration at ~/.jupyter/jupyterhub_config.py, a file that
jupyter lab never reads. The JWT authorizer was therefore never loaded: the server ran with
Jupyter's own token (random and printed in the log, or JUPYTER_TOKEN), which opened
everything, terminals included, to whoever held it. Nothing failed, so nothing showed it.
Renaming the file is not enough on its own. Jupyter logs an error raised in a configuration file and starts anyway with its own token; a server extension that fails to load is a warning; a missing configuration file is no error at all. Each of these turns "the authorizer could not be loaded" back into "the server token opens the server".
The authorizer also took the issuer from the unverified token and fetched that issuer's keys, so a validly signed token from any other issuer (another realm of the same Keycloak included) was accepted.
Decision
- The image runs
jupyter celine-server(celine.jupyter.serverapp): Jupyter Server whose identity provider and authorizer are the CELINE ones by default, and which exits instead of serving if either was replaced, if unauthenticated access or the XSRF check was switched back on, or if no trusted issuer is configured. - The shipped
jupyter_server_config.pyinstalls the same settings and enables the same check as a server extension, and raisesSystemExit(the one thing Jupyter does not swallow) whenceline.jupytercannot be imported, so a stockjupyter labgiven that file fails closed too. - Authentication is an identity provider, not only an authorizer: a request without a platform administrator's token has no user. There is no server token (any configured one is dropped), no password and no login page.
- One issuer is trusted,
CELINE_JUPYTER_JWT_ISSUER. A token'sissis compared with it before any key is fetched.
Consequences
- A deployment must set
CELINE_JUPYTER_JWT_ISSUER, or the container exits at start. - The image cannot be opened for local debugging with a token or password; that needs a platform administrator's access token from the configured issuer.
- A stock
jupyter labstarted without the shipped configuration file is not protected by anything here; onlyjupyter celine-servercarries the protection without a file. - Upgrades of jupyter-server must keep
ServerApp.identity_provider_class,authorizer_classandallow_unauthenticated_access; the startup tests fail when they change. - Loosening any of this "for convenience" (a token, a fallback issuer, logging instead of exiting) reopens the hole this record closes and needs a new ADR that supersedes this one.