<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Jupyter Blog - Min RK</title><link href="https://jupyter.org/blog/" rel="alternate"/><link href="https://jupyter.org/blog/feeds/author-min-rk.atom.xml" rel="self"/><id>https://jupyter.org/blog/</id><updated>2026-09-11T08:54:00+00:00</updated><subtitle>The Project Jupyter blog: news, releases, and community stories, archived from blog.jupyter.org.</subtitle><entry><title>JupyterHub 6.0</title><link href="https://jupyter.org/blog/posts/2026/jupyterhub-6-0/" rel="alternate"/><published>2026-09-11T08:54:00+00:00</published><updated>2026-09-11T08:54:00+00:00</updated><author><name>Serena Bonaretti</name></author><id>tag:jupyter.org,2026-09-11:/blog/posts/2026/jupyterhub-6-0/</id><summary type="html">&lt;p&gt;JupyterHub is a collection of tools that provides standardized, user-friendly computing environments for education and research. Today, we’re pleased to announce the release of JupyterHub 6.0. This is a substantial release with several new features.&lt;/p&gt;</summary><content type="html">&lt;p class="standalone-image"&gt;&lt;img src="https://jupyter.org/blog/posts/2026/jupyterhub-6-0/images/001-1_l3xTI5FmlszK3So0jg0-YA.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://jupyterhub.readthedocs.io"&gt;JupyterHub&lt;/a&gt; is a collection of tools that provides standardized, user-friendly computing environments for education and research. Today, we’re pleased to announce the release of &lt;a href="https://jupyterhub.readthedocs.io/en/stable/reference/changelog.html#id2"&gt;&lt;strong&gt;JupyterHub 6.0&lt;/strong&gt;&lt;/a&gt;. This is a substantial release with several new features. The breaking changes ought to be minimal, and should affect very few deployments. There is a small database schema upgrade, so make sure to backup your database prior to upgrading, as usual.&lt;/p&gt;
&lt;p&gt;Some changes to highlight:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Stricter named server rules&lt;/strong&gt;: Named servers now have stricter rules on their names, and a new less-restricted “display name” field. This might be the &lt;strong&gt;most significant change&lt;/strong&gt; affecting you! See the &lt;a href="https://jupyterhub.readthedocs.io/en/6.0.0/howto/upgrading-v6.html#named-server-restrictions"&gt;documentation&lt;/a&gt; for more details.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Python 3.10 and database upgrade&lt;/strong&gt;: JupyterHub 6.0 now requires Python 3.10 and includes a database schema upgrade. Make sure to &lt;strong&gt;back up your database&lt;/strong&gt; before upgrading.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Updates to metrics&lt;/strong&gt;: The old StatsD metrics have been removed, in favor of the Prometheus metrics that most people are already using. For Spawner (and spawn hook) authors, JupyterHub 6.0 also introduces &lt;a href="https://jupyterhub.readthedocs.io/en/6.0.0/reference/spawners.html#exception-handling"&gt;SpawnException&lt;/a&gt;, which provides more granular information for classifying spawn failures in metrics, to help deployments separate &lt;em&gt;errors&lt;/em&gt; from &lt;em&gt;rejections&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Internal communication changes&lt;/strong&gt;: JupyterHub now uses &lt;em&gt;aiohttp&lt;/em&gt; for internal HTTP requests, which may require tuning for large deployments since internal HTTP requests. This update has facilitated using unix sockets for internal communication on single-node deployments.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Improved granularity in API access&lt;/strong&gt;: JupyterHub provides new API endpoints for working with single servers, along with more flexible ways to define permissions, such as &lt;code&gt;start:servers&lt;/code&gt; scope and &lt;code&gt;extra_user_scopes&lt;/code&gt;.&lt;br&gt;
See the &lt;a href="https://jupyterhub.readthedocs.io/en/stable/rbac/roles.html#overriding-default-roles"&gt;documentation&lt;/a&gt; for more information.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Beyond these main changes, there have been lots of other improvements and bugfixes. You can find more details in the &lt;a href="https://jupyterhub.readthedocs.io/en/stable/howto/upgrading-v6.html"&gt;upgrading documentation&lt;/a&gt; and &lt;a href="https://jupyterhub.readthedocs.io/en/stable/reference/changelog.html#changelog"&gt;changelog&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="thank-you-to-our-contributors"&gt;Thank you to our contributors&lt;/h2&gt;
&lt;p&gt;The following people contributed discussions, new ideas, code and documentation contributions, and review: &lt;a href="https://github.com/agoose77"&gt;agoose77&lt;/a&gt;, &lt;a href="https://github.com/anny0811"&gt;anny0811&lt;/a&gt;, &lt;a href="https://github.com/ateucher"&gt;ateucher&lt;/a&gt;, &lt;a href="https://github.com/betatim"&gt;betatim&lt;/a&gt;, &lt;a href="https://github.com/bl-aire"&gt;bl-aire&lt;/a&gt;, &lt;a href="https://github.com/clhedrick"&gt;clhedrick&lt;/a&gt;, &lt;a href="https://github.com/consideRatio"&gt;consideRatio&lt;/a&gt;, &lt;a href="https://github.com/dometto"&gt;dometto&lt;/a&gt;, &lt;a href="https://github.com/dragz"&gt;dragz&lt;/a&gt;, &lt;a href="https://github.com/erawn"&gt;erawn&lt;/a&gt;, &lt;a href="https://github.com/ericvd-ucb"&gt;ericvd-ucb&lt;/a&gt;, &lt;a href="https://github.com/jezwilkinson"&gt;jezwilkinson&lt;/a&gt;, &lt;a href="https://github.com/jnywong"&gt;jnywong&lt;/a&gt;, &lt;a href="https://github.com/jrdnbradford"&gt;jrdnbradford&lt;/a&gt;, &lt;a href="https://github.com/kellyrowland"&gt;kellyrowland&lt;/a&gt;, &lt;a href="https://github.com/kreuzert"&gt;kreuzert&lt;/a&gt;, &lt;a href="https://github.com/LUC4R4T0R"&gt;LUC4R4T0R&lt;/a&gt;, &lt;a href="https://github.com/manics"&gt;manics&lt;/a&gt;, &lt;a href="https://github.com/mayli"&gt;mayli&lt;/a&gt;, &lt;a href="https://github.com/milliams"&gt;milliams&lt;/a&gt;, &lt;a href="https://github.com/minrk"&gt;minrk&lt;/a&gt;, &lt;a href="https://github.com/mynkyu"&gt;mynkyu&lt;/a&gt;, &lt;a href="https://github.com/nsychev"&gt;nsychev&lt;/a&gt;, &lt;a href="https://github.com/Paul2708"&gt;Paul2708&lt;/a&gt;, &lt;a href="https://github.com/Rameshkumar31595"&gt;Rameshkumar31595&lt;/a&gt;, &lt;a href="https://github.com/rgaiacs"&gt;rgaiacs&lt;/a&gt;, &lt;a href="https://github.com/shaneknapp"&gt;shaneknapp&lt;/a&gt;, &lt;a href="https://github.com/sixonenines"&gt;sixonenines&lt;/a&gt;, &lt;a href="https://github.com/tbizouerne"&gt;tbizouerne&lt;/a&gt;, &lt;a href="https://github.com/Tech-Abhang"&gt;Tech-Abhang&lt;/a&gt;, &lt;a href="https://github.com/wilkinvr"&gt;wilkinvr&lt;/a&gt;, &lt;a href="https://github.com/willingc"&gt;willingc&lt;/a&gt;, &lt;a href="https://github.com/yuvipanda"&gt;yuvipanda&lt;/a&gt;&lt;/p&gt;
</content><category term="JupyterHub"/></entry><entry><title>mybinder.org adding request Origin to events archive</title><link href="https://jupyter.org/blog/posts/2026/mybinder-org-adding-request-origin-to-events-archive/" rel="alternate"/><published>2026-02-02T16:27:00+00:00</published><updated>2026-03-02T23:33:00+00:00</updated><author><name>Min RK</name></author><id>tag:jupyter.org,2026-02-02:/blog/posts/2026/mybinder-org-adding-request-origin-to-events-archive/</id><summary type="html">&lt;p&gt;In February, the mybinder.org service will start tracking the “origin” of requests that launch a Binder session. The website that created a request to mybinder.org will be visible in the Binder analytics archive.&lt;/p&gt;</summary><content type="html">&lt;figure&gt;
&lt;img alt="The binder logo" src="https://jupyter.org/blog/posts/2026/mybinder-org-adding-request-origin-to-events-archive/images/001-1_gICbzIC3Qbq6ireueH1FYA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Binder logo&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;In February, the &lt;a href="http://mybinder.org"&gt;mybinder.org&lt;/a&gt; service will start tracking the “origin” of requests that launch a Binder session. The website that created a request to &lt;a href="http://mybinder.org"&gt;mybinder.org&lt;/a&gt; will be visible in the Binder analytics archive. This will help us monitor which websites are embedding Binder sessions for impact or abuse reports. It shouldn’t have any implications for private information but we are sharing here for transparency. The only cases where new information will be recorded is websites which embed mybinder.org kernels via tools like &lt;a href="https://thebe.readthedocs.io"&gt;Thebe&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When folks use &lt;a href="https://mybinder.org"&gt;mybinder.org&lt;/a&gt; to run sessions, each launch is added to a public &lt;a href="https://archive.analytics.mybinder.org"&gt;events archive&lt;/a&gt;. That means that every time anyone launches a session on mybinder.org, the repo and version used and the time of the launch is &lt;strong&gt;public information&lt;/strong&gt;. There is no such thing as a private launch on mybinder.org (this is part of why we don’t support private repos). No information about the user &lt;em&gt;requesting the build&lt;/em&gt; is recorded (ip address, user agent, browser info, etc.), though some of that is sent to Plausible and aggregated and anonymized for &lt;a href="https://plausible.io/mybinder.org"&gt;analytics&lt;/a&gt;. Notably, Plausible only covers regular browser visits, not launches via the API.&lt;/p&gt;
&lt;p&gt;There are 3 main ways that people launch sessions on mybinder.org:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;(the vast majority) — visiting mybinder.org in a browser&lt;/li&gt;
&lt;li&gt;embedding &lt;em&gt;kernels&lt;/em&gt; from mybinder.org on another website to make e.g. interactive documentation with a tool like &lt;a href="https://thebe.readthedocs.io"&gt;Thebe&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;‘headless’ launches via scripts&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Up to now, these events are indistinguishable in the events archive. We’d like to start distinguishing these cases in the events archive to better classify how mybinder.org is used, but that means recording new information we didn’t track before, so we want to communicate clearly about it ahead of time.&lt;/p&gt;
&lt;p&gt;We are planning to add the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Origin"&gt;&lt;strong&gt;Origin&lt;/strong&gt;&lt;/a&gt; of the launch request to the public events archive at the end of February. This still doesn’t track anything about the &lt;em&gt;&lt;strong&gt;user&lt;/strong&gt;&lt;/em&gt; (no ip address, user agent, etc.), but rather the host of the page where the launch request originated. For the vast majority of cases, this is not new information, as the origin is e.g. &lt;code&gt;gesis.mybinder.org&lt;/code&gt; (which we already track confusingly as ‘origin’, though this is not the request Origin, it is the &lt;em&gt;federation member&lt;/em&gt; where the launch event takes place). Notably, case 1 still includes any regular browser links to &lt;code&gt;mybinder.org/v1/gh/repo/name/HEAD&lt;/code&gt;. We don’t record where those come from (that would be Referer).&lt;/p&gt;
&lt;p&gt;Where this &lt;em&gt;changes&lt;/em&gt; is cross-origin requests (case 2), that is, pages which &lt;em&gt;embed&lt;/em&gt; kernels from mybinder.org, such as documentation websites that use mybinder.org to embed kernels from mybinder.org via tools like Thebe to make executable documentation (which is rad!). These requests will now include the Origin of the request, which would be the hostname (e.g. &lt;code&gt;https://course.spacy.io&lt;/code&gt; ), not the full page.&lt;/p&gt;
&lt;p&gt;Script launches (case 3) typically lack this information, so will usually have &lt;code&gt;null&lt;/code&gt; data in the new field. But of course, scripts can do whatever they want, so this isn’t guaranteed to be accurate for malicious or deliberately spoofing scripts.&lt;/p&gt;
&lt;p&gt;If you want to have a private, not-recorded use of Binder, you must run your own BinderHub instance. If you want to suggest that we &lt;em&gt;not&lt;/em&gt; do this, or do it differently, please let us know in the &lt;a href="https://github.com/jupyterhub/binderhub/pull/2053"&gt;proposal discussion&lt;/a&gt;. You can also check the &lt;a href="https://mybinder.readthedocs.io/en/latest/about/user-guidelines.html#how-we-ensure-user-privacy"&gt;privacy policy&lt;/a&gt; for more information. We plan to apply the change at the end of February, 2026.&lt;/p&gt;
</content><category term="Binder"/></entry><entry><title>Mybinder.org reducing capacity</title><link href="https://jupyter.org/blog/posts/2023/mybinder-org-reducing-capacity/" rel="alternate"/><published>2023-04-25T07:28:00+00:00</published><updated>2023-04-27T20:16:00+00:00</updated><author><name>Min RK</name></author><id>tag:jupyter.org,2023-04-25:/blog/posts/2023/mybinder-org-reducing-capacity/</id><summary type="html">&lt;p&gt;Funding is running out for the largest member of mybinder.org, which has been sponsored by the Google Open Source Program Office. gke.mybinder.org will shut down and there will be a reduction in capacity at the end of April, 2023 (end of this week).&lt;/p&gt;</summary><content type="html">&lt;p&gt;Funding is running out for the largest member of mybinder.org, which has been sponsored by the Google Open Source Program Office. gke.mybinder.org will shut down and there will be a reduction in capacity at the end of April, 2023 (end of this week).&lt;/p&gt;
&lt;p&gt;Key points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Our funds on &lt;code&gt;gke.mybinder.org&lt;/code&gt;, the biggest part of the federation, will run out in April. Google has decided not to provide more credits.&lt;/li&gt;
&lt;li&gt;Traffic previously sent to GKE will mostly be delivered to OVHcloud, which has less capacity. We are also working to deploy new federation members to spread the load.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Service reductions to expect:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This may come with an increase in errors and failures, as GKE has been the largest and most stable federation member&lt;/li&gt;
&lt;li&gt;We are currently looking at reducing the total capacity of Binder by 60%, so you may get rate limit or capacity errors&lt;/li&gt;
&lt;li&gt;We will start more aggressively deleting older cached builds, as storage costs have increased.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We are still working out the details of the shutdown and deploying new federation members, but mybinder.org users should expect disruption to the mybinder.org service and to see an increase in messages that the service is at capacity in the short term.&lt;/p&gt;
&lt;p&gt;The plan:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Thursday: gke.mybinder.org will be removed from the federation (user sessions not disrupted, but no new sessions will start there)&lt;/li&gt;
&lt;li&gt;Friday: gke.mybinder.org will be shutdown, and mybinder.org migrated to a smaller cluster&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can help us keep mybinder.org running by &lt;a href="https://numfocus.salsalabs.org/donate-to-binder"&gt;donating to Binder&lt;/a&gt; or encouraging your organization to &lt;a href="https://mybinder.readthedocs.io/en/latest/about/federation.html"&gt;join the BinderHub Federation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We are grateful to the Google Open Source Program for their funding thus far, and appreciate the continued support of continuing federation members, OVHCloud and GESIS, which enable us to keep mybinder.org going without shutting down completely when one funding source goes away.&lt;/p&gt;
</content><category term="Binder"/></entry><entry><title>Updating defaults on mybinder.org</title><link href="https://jupyter.org/blog/posts/2023/updating-defaults-on-mybinder-org/" rel="alternate"/><published>2023-03-09T08:31:00+00:00</published><updated>2023-03-10T15:53:00+00:00</updated><author><name>Min RK</name></author><id>tag:jupyter.org,2023-03-09:/blog/posts/2023/updating-defaults-on-mybinder-org/</id><summary type="html">&lt;p&gt;This week, we are deploying some updates to repo2docker, the image-building tool that underpins mybinder.org. This may affect your repos. Please get in touch if you have problems!&lt;/p&gt;</summary><content type="html">&lt;p class="standalone-image"&gt;&lt;img src="https://jupyter.org/blog/posts/2023/updating-defaults-on-mybinder-org/images/001-1_0dd02RZZwHXDo-Fr3tdB5w.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;This week, we are deploying some updates to &lt;a href="http://repo2docker.readthedocs.io"&gt;repo2docker&lt;/a&gt;, the image-building tool that underpins &lt;a href="https://mybinder.org/"&gt;mybinder.org&lt;/a&gt;. This may affect your repos. Please &lt;a href="https://discourse.jupyter.org/c/binder/12"&gt;get in touch&lt;/a&gt; if you have problems! Hopefully this will make things better, more stable, and &lt;em&gt;less&lt;/em&gt; surprising overall, but change always breaks &lt;em&gt;something&lt;/em&gt;, however hard we work to keep things working.&lt;/p&gt;
&lt;p&gt;To highlight the changes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python 3.10 is now the default Python version&lt;/li&gt;
&lt;li&gt;Python 3.11 is now supported, and new versions of Python should be supported more quickly&lt;/li&gt;
&lt;li&gt;If a repo requests an old version of Python (≤ 3.6), it will be installed in a separate environment, as we’ve been doing with Python 2.7 for a long time.&lt;/li&gt;
&lt;li&gt;The server implementation is switched from &lt;code&gt;jupyter-notebook&lt;/code&gt; to &lt;code&gt;jupyter-server&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;More detail on each change, the reasons behind them, and their consequences below.&lt;/p&gt;
&lt;h2 id="python-311-and-separate-envs-for-legacy-python-support"&gt;Python 3.11 and separate envs for legacy Python support&lt;/h2&gt;
&lt;p&gt;When building an image, repo2docker installs packages in one or two environments — the &lt;strong&gt;kernel&lt;/strong&gt; environment where your code runs, and the &lt;strong&gt;server&lt;/strong&gt; environment, which runs the Jupyter server. If you’re using a reasonably supported version of Python, these are actually the same environment. This is the vast majority of repositories on mybinder.org. In order to function properly, we have certain requirements of what goes in the ‘server’ environment. Eventually, those requirements start making it difficult to setup the server environment with an outdated version of Python, as packages march ahead with their minimum required Python (The widely adopted &lt;a href="https://numpy.org/neps/nep-0029-deprecation_policy.html#support-table"&gt;NEP 29&lt;/a&gt; suggested dropping Python 3.7 support in December, 2021). Our solution for this has been to separate the kernel environment from the server environment, so they don’t create a conflict. Until recently, only Python 2 was handled this way (most packages dropped Python 2 support years ago). As part of adding support for Python 3.11, we have expanded this behavior to include Python ≤3.6, so if you are using a ‘legacy Python’, currently defined to be Python ≤3.6, your requested packages will be installed in the kernel environment instead of the server environment. This &lt;em&gt;should&lt;/em&gt; only have a noticeable effect if you are installing something with server extension components , which includes anything modifying the JupyterLab or notebook interfaces— those will not be available in the server environment. To address this, you will have to upgrade Python &lt;em&gt;or&lt;/em&gt; handle the server extension installation in &lt;code&gt;postBuild&lt;/code&gt;. &lt;a href="https://github.com/jupyterhub/repo2docker/pull/1239"&gt;PR adding Python 3.11 and adding Python ≤3.6 to legacy Python versions&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="default-python-is-now-310"&gt;Default Python is now 3.10&lt;/h2&gt;
&lt;p&gt;The default Python for repo2docker and thereby mybinder.org is now Python 3.10, upgraded from 3.7. 3.10 will be used when a repository does not specify which version of Python to use. Following Python’s own annual release cadence, we plan to update this more regularly in the future, lagging behind Python itself by a version or two. This has no effect on your repos if they specify a Python version, which is why we &lt;a href="https://repo2docker.readthedocs.io/en/latest/howto/languages.html#specifying-a-version-of-python"&gt;&lt;strong&gt;encourage you to specify your Python version&lt;/strong&gt;&lt;/a&gt;! If you don’t, then your Python version &lt;em&gt;will&lt;/em&gt; change over time, probably about once a year. &lt;a href="https://github.com/jupyterhub/repo2docker/pull/1219"&gt;PR switching the default Python&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="now-launching-jupyter-server"&gt;Now launching Jupyter Server&lt;/h2&gt;
&lt;p&gt;The default UI for mybinder.org has been JupyterLab for some time, with the classic notebook still available if you specify a url of &lt;code&gt;/tree/&lt;/code&gt; or &lt;code&gt;/notebooks/mynb.ipynb&lt;/code&gt;. When we rolled out that change, we kept the default &lt;em&gt;server&lt;/em&gt; as the &lt;code&gt;jupyter-notebook&lt;/code&gt; server, for improved compatibility with existing server extensions. As the newer Jupyter Server has taken over, the situation is becoming reversed — using the older &lt;code&gt;notebook&lt;/code&gt; server is now the &lt;em&gt;cause&lt;/em&gt; of compatibility issues with server extensions, so we are switching the server to launch &lt;code&gt;jupyter-server&lt;/code&gt; instead of &lt;code&gt;jupyter-notebook&lt;/code&gt;. The classic UI is still available at the same URLs, thanks to &lt;a href="https://nbclassic.readthedocs.io"&gt;nbclassic&lt;/a&gt;. This &lt;em&gt;should&lt;/em&gt; have no visible effects, as &lt;code&gt;jupyter-server&lt;/code&gt; and &lt;code&gt;nbclassic&lt;/code&gt; make a great deal of effort to keep classic notebook extensions working, but some extensions may need an update for jupyter-server compatibility if they haven’t been updated already. Because of where the server is specified, there is no supported way for images to express a preference for the deprecated &lt;code&gt;notebook&lt;/code&gt; server (&lt;a href="https://github.com/jupyterhub/binderhub/pull/1635"&gt;PR switching the default server command&lt;/a&gt;.&lt;/p&gt;
</content><category term="Binder"/><category term="Docker"/><category term="JupyterHub"/><category term="reproducibility"/></entry><entry><title>Please don’t disable authentication in Jupyter servers</title><link href="https://jupyter.org/blog/posts/2022/please-dont-disable-authentication-in-jupyter-servers/" rel="alternate"/><published>2022-04-05T09:50:00+00:00</published><updated>2022-04-05T09:50:00+00:00</updated><author><name>Min RK</name></author><id>tag:jupyter.org,2022-04-05:/blog/posts/2022/please-dont-disable-authentication-in-jupyter-servers/</id><summary type="html">&lt;p&gt;We are aware of ransomware attacks specifically targeting Jupyter servers.&lt;/p&gt;</summary><content type="html">&lt;p&gt;We are aware of ransomware attacks &lt;a href="https://blog.aquasec.com/python-ransomware-jupyter-notebook"&gt;specifically targeting Jupyter servers&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;First off: we have no evidence of a vulnerability in Jupyter associated with the attack. The attacks appear to focus on Jupyter servers &lt;em&gt;with authentication disabled.&lt;/em&gt; That’s what this post is about: &lt;strong&gt;please don’t do that&lt;/strong&gt;!&lt;/p&gt;
&lt;p&gt;Jupyter servers are authenticated with a random token &lt;em&gt;by default,&lt;/em&gt; so you should be fine with the defaults. That default random token is regenerated on each server launch. If that random token is &lt;em&gt;inconvenient&lt;/em&gt; for you (there are plenty of situations where this is the case), &lt;strong&gt;the answer is not to disable auth&lt;/strong&gt;! More on that below.&lt;/p&gt;
&lt;p&gt;We wrote about this &lt;a href="https://jupyter.org/blog/posts/2017/public-notebooks-and-security/"&gt;before&lt;/a&gt;, but it bears repeating:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Jupyter exposes arbitrary code execution and a shell. If folks gain access, they can do anything you can do. You aren’t just protecting your notebooks, you are protecting your &lt;em&gt;whole computer&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;People can find your notebook server&lt;/strong&gt; if it’s accessible on the Internet. If they locate your server and it has auth disabled, that’s game over, and appears to be what happened in the above ransomware event. Jupyter’s popular enough (yay) that folks are always looking (boo).&lt;/li&gt;
&lt;li&gt;If the generated token pattern is inconvenient for how you work, you can set a persistent token or password in configuration instead of retrieving the generated token on each server launch. Save this token or password in your password manager for safe keeping, and accessing your server will be just as convenient as auth being disabled.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you have an interactive prompt, you can run &lt;code&gt;jupyter server password&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;jupyter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;
&lt;span class="n"&gt;Enter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="err"&gt;🔐&lt;/span&gt;
&lt;span class="n"&gt;Verify&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="err"&gt;🔐&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;JupyterPasswordApp&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Wrote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hashed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jupyter&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;jupyter_server_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;which generates and stores config that looks like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;{
  &amp;quot;ServerApp&amp;quot;: {
    &amp;quot;password&amp;quot;: &amp;quot;argon2:$argon2id$v=19$m=10240,t=10,p=8$aFE/DuLj//6oGF2PHWy2DQ$eXHn6AbJe8Lryl4z9oGvMtZhX7iEdt41m+mhvyDvw88&amp;quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is a &lt;em&gt;hashed, salted&lt;/em&gt; form of your password. We can’t get the password out of this, but we can use it to check if you typed it correctly on the login page. If an interactive prompt isn’t available to you (e.g. a cloud vm that starts the notebook server automatically), you can run this command on any machine, and copy the resulting config file to the destination machine as part of setup.&lt;/p&gt;
&lt;p&gt;If it works better for you, you can also generate &lt;em&gt;just&lt;/em&gt; the hashed password:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;jupyter_server.auth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;passwd&lt;/span&gt;
&lt;span class="n"&gt;hashed_password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;passwd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;my-great-passphrase&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# just the &amp;quot;argon2:...&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;and get that into your config files, as best suits your needs. For example, in &lt;code&gt;~/.jupyter/jupyter_server_config.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;c.ServerApp.password = &amp;quot;argon2:...&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;or the same config in &lt;code&gt;~/.jupyter/jupyter_server_config.json&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;{
  &amp;quot;ServerApp&amp;quot;: {
    &amp;quot;password&amp;quot;: &amp;quot;argon2:...&amp;quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Read more details in &lt;a href="https://jupyter.org/blog/posts/2017/public-notebooks-and-security/"&gt;our earlier post&lt;/a&gt;, and &lt;a href="https://jupyter-server.readthedocs.io/en/latest/operators/security.html"&gt;security documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Please contact &lt;a href="mailto:security@ipython.org"&gt;security@ipython.org&lt;/a&gt; if you have a vulnerability to report.&lt;/p&gt;
</content><category term="security"/></entry><entry><title>JupyterHub 2.0</title><link href="https://jupyter.org/blog/posts/2021/jupyterhub-2-0/" rel="alternate"/><published>2021-12-01T14:15:00+00:00</published><updated>2021-12-01T14:15:00+00:00</updated><author><name>Min RK</name></author><id>tag:jupyter.org,2021-12-01:/blog/posts/2021/jupyterhub-2-0/</id><summary type="html">&lt;p&gt;Hubs and roles and scopes, oh my!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;We’ve just published JupyterHub 2.0, our biggest release in a while.&lt;/p&gt;
&lt;h2 id="roles-and-scopes-rbac"&gt;Roles and scopes (RBAC)&lt;/h2&gt;
&lt;p&gt;Thanks to the work of Omar Richardson and Ivana Huskova and more contributors along the way, JupyterHub now has fine-grained control of permissions, via &lt;strong&gt;scopes&lt;/strong&gt; (a &lt;strong&gt;role&lt;/strong&gt; is a named collection of scopes).&lt;/p&gt;
&lt;p&gt;This makes JupyterHub more secure because you no longer need to give every user or service full admin permissions if they need to take one elevated action (such as a cull-idle service). Now, you can give users and services exactly the permissions they actually need. &lt;code&gt;admin&lt;/code&gt; still exists, but nothing should need to be an admin anymore. Fine-grained scopes also allow new configurations that weren’t previously available, such as granting instructors permission to access their students’ servers, which is useful with JupyterLab’s new real-time collaboration features, without needing to grant them full admin permissions, or even access to servers of users not in their specific class.&lt;/p&gt;
&lt;p&gt;The significant change in how permissions work means that upgrading to 2.0 requires some downtime. You will need to shut down all services and single-user servers across the upgrade, and make sure you upgrade JupyterHub to 2.0 in both the user environment and the Hub environment at the same time.&lt;/p&gt;
&lt;p&gt;All tokens issued prior to the 2.0 upgrade will be revoked.&lt;/p&gt;
&lt;p&gt;As always, &lt;strong&gt;&lt;a href="https://jupyterhub.readthedocs.io/en/2.0.0/admin/upgrading.html"&gt;back up your database&lt;/a&gt; before upgrading&lt;/strong&gt;!&lt;/p&gt;
&lt;p&gt;Read more about roles and scopes &lt;a href="https://jupyterhub.readthedocs.io/en/2.0.0/rbac/"&gt;in the docs&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="jupyterlab-by-default"&gt;JupyterLab by default&lt;/h2&gt;
&lt;p&gt;2.0 also marks JupyterHub’s transition to using JupyterLab as the default UI. You can stick with the classic notebook server with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;c.Spawner.environment = {
    &amp;quot;JUPYTERHUB_SINGLEUSER_APP&amp;quot;: &amp;quot;notebook.notebookapp.NotebookApp&amp;quot;,
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;or try &lt;strong&gt;RetroLab&lt;/strong&gt;, a classic-style UI built on the newer Jupyter Server:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;c.Spawner.default_url = &amp;quot;/retro/&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;(assuming you’ve installed it in the user environment, with e.g. &lt;code&gt;pip install retrolab&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id="and-more"&gt;And more&lt;/h2&gt;
&lt;p&gt;There are various other changes and improvements in 2.0, including a new admin UI, API pagination, and no longer relying on any command-line arguments for single-user servers. Check out the &lt;a href="https://jupyterhub.readthedocs.io/en/2.0.0/changelog.html"&gt;changelog&lt;/a&gt; for more details.&lt;/p&gt;
&lt;p&gt;We encourage you to take 2.0 for a spin, try out the roles and scopes, and &lt;a href="https://discourse.jupyter.org/c/jupyterhub/10"&gt;let us know&lt;/a&gt; how it goes.&lt;/p&gt;
&lt;p&gt;Thanks to all the contributors who helped us with 2.0, especially prerelease testers!&lt;/p&gt;
</content><category term="JupyterHub"/></entry><entry><title>IPython Parallel in 2021</title><link href="https://jupyter.org/blog/posts/2021/ipython-parallel-in-2021/" rel="alternate"/><published>2021-11-30T07:55:00+00:00</published><updated>2021-11-30T07:55:00+00:00</updated><author><name>Min RK</name></author><id>tag:jupyter.org,2021-11-30:/blog/posts/2021/ipython-parallel-in-2021/</id><summary type="html">&lt;p&gt;Updates on IPython Parallel; new features, future direction&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;Updates on IPython Parallel; new features, future direction&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This post describes work funded by Bodo, Inc.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://ipyparallel.readthedocs.io/"&gt;IPython Parallel&lt;/a&gt;’s a bit of an odd duck in the parallel computing space. In a world with &lt;a href="https://dask.org/"&gt;dask&lt;/a&gt;, &lt;a href="https://ray.io/"&gt;ray&lt;/a&gt;, &lt;a href="https://docs.bodo.ai/latest/source/getting_started.html"&gt;bodo&lt;/a&gt;, &lt;a href="https://spark.apache.org/docs/latest/api/python/index.html"&gt;pyspark&lt;/a&gt;, and other parallel computing tools, what is IPython Parallel’s role in 2021?&lt;/p&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;IPython Parallel began (in 2006!) as a natural extension of the &lt;a href="https://jupyter-client.readthedocs.io/en/stable/messaging.html#general-message-format"&gt;Jupyter messaging protocol&lt;/a&gt;(&lt;em&gt;though it predates the Jupyter name by a few years&lt;/em&gt;): when you have a protocol for &lt;a href="https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop"&gt;REPL&lt;/a&gt;-style remote code execution, what can you do with &lt;em&gt;multiple&lt;/em&gt; remote execution environments? This question led to the development two basic models for parallel execution in IPython Parallel:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;“multiplexed execution” — where you send code explicitly to different workers, and&lt;/li&gt;
&lt;li&gt;“load-balanced execution” — where you tell IPython Parallel what to run, and a scheduler takes care of assigning each task to an available worker.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="what-ipython-parallel-doesnt-do"&gt;What IPython Parallel doesn’t do&lt;/h2&gt;
&lt;p&gt;To IPython, your tasks are black boxes (either Python functions or blocks of Python code as text) and you are in complete control of where and when your tasks run, as well as any dependencies or side effects they may have.&lt;/p&gt;
&lt;p&gt;IPython Parallel specifically doesn’t and won’t do several things that other tools might:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;infer dependencies or relationships between tasks (execution graph)&lt;/li&gt;
&lt;li&gt;manage data distribution or locality&lt;/li&gt;
&lt;li&gt;construct parallel algorithms or represent ‘natively’ parallel distributed data structures, such as distributed arrays or data frames&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Everything is very explicit and implemented at the client-level in IPython Parallel, meaning that while &lt;em&gt;it&lt;/em&gt; doesn’t do these things, &lt;em&gt;you&lt;/em&gt; can. It also means that many of the things that have overhead costs associated with parallel computing (communication, data movement) have particularly high costs in IPython Parallel.&lt;/p&gt;
&lt;p&gt;IPython Parallel doesn’t hide anything from you, for better &lt;em&gt;and&lt;/em&gt; worse.&lt;/p&gt;
&lt;h2 id="what-ipython-parallel-does"&gt;What IPython Parallel does&lt;/h2&gt;
&lt;p&gt;IPython Parallel &lt;strong&gt;makes &lt;em&gt;explicit&lt;/em&gt; parallel computations interactive&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;IPython Parallel is not just parallel Python, it’s parallel &lt;em&gt;IPython&lt;/em&gt;. That means you have all the power of IPython’s inspection, interactivity, magics, and debugging available on all of your distributed workers. This makes it especially well-suited to prototyping and experimentation.&lt;/p&gt;
&lt;p&gt;IPython Parallel also presents standard APIs such as &lt;a href="https://ipyparallel.readthedocs.io/en/8.0.0/examples/Futures.html#Executors"&gt;Python Executors&lt;/a&gt;, compatible with many other implementations, to make it easy to migrate to &lt;em&gt;and from&lt;/em&gt; IPython Parallel, enabling developers to write code that uses a single multicore laptop or a thousand cores on an HPC cluster or cloud.&lt;/p&gt;
&lt;h2 id="ipython-parallel-in-2021"&gt;IPython Parallel in 2021&lt;/h2&gt;
&lt;figure&gt;
&lt;img alt="Interactive progress across parallel engines. Those progress bars are interactive widgets running locally, and on each remote engine!" src="https://jupyter.org/blog/posts/2021/ipython-parallel-in-2021/images/001-1_5W4lCfV_HPwSmFP88niiJg.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Interactive progress across parallel engines. Those progress bars are interactive widgets running locally, &lt;em&gt;and on each remote engine!&lt;/em&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;For a lot of today’s workloads, my default recommendation is: use &lt;a href="https://dask.org/"&gt;dask&lt;/a&gt; or bodo or another modern tool. IPython even makes this easier if you already happen to have an IPython Parallel cluster, you can tell it to “&lt;a href="https://ipyparallel.readthedocs.io/en/8.0.0/examples/dask.html"&gt;become dask&lt;/a&gt;,” and off you go:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;dask_client = rc.become_dask(ncores=1)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;However, where IPython Parallel can shine, is making traditional &lt;a href="https://en.wikipedia.org/wiki/SPMD"&gt;SPMD&lt;/a&gt; (e.g., MPI) workloads interactive, especially for prototyping and debugging.&lt;/p&gt;
&lt;p&gt;If you have an MPI simulation and you wish you could pause it in the middle, poke around and make plots and interact with just one node or all of them with all the interactive tools available to you in Jupyter, IPython Parallel may be the tool for you.&lt;/p&gt;
&lt;h2 id="recent-developments"&gt;Recent developments&lt;/h2&gt;
&lt;p&gt;Because I see the main problem IPython Parallel solves well is adding interactivity to direct parallel execution, the focus of recent developments has been on improving that story, based on feedback from users who are often in traditional HPC environments like SLURM or PBS, or using MPI in the cloud.&lt;/p&gt;
&lt;p&gt;Much of the feedback on challenges over the years have been around:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Scaling to larger numbers of engines (1,–10,000)&lt;/li&gt;
&lt;li&gt;Better feedback and recovery when things go wrong&lt;/li&gt;
&lt;li&gt;Security requirements&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thanks to &lt;a href="https://bodo.ai/"&gt;Bodo&lt;/a&gt;, I’ve been able to spend a lot of time this year addressing several of those issues, now available as IPython Parallel 8.0.&lt;/p&gt;
&lt;p&gt;Last year, Tom-Olav Bøyum developed a broadcast scheduler as part of his &lt;a href="https://www.duo.uio.no/handle/10852/81515"&gt;Master’s thesis&lt;/a&gt;, which vastly improves the efficiency of sending the same task to all engines (the main thing we do in IPP+MPI). This work is now mostly done and landed in IPython Parallel 7. We’ve also addressed longstanding issues with registering large numbers of engines (5–10,000) at once, a common source of failure on HPC clusters.&lt;/p&gt;
&lt;p&gt;One of the most frustrating / challenging things when working with MPI or other parallel computations is dealing with hangs and restarting tasks. Through the new &lt;a href="https://ipyparallel.readthedocs.io/en/8.0.0/examples/Cluster%20API.html"&gt;Cluster API&lt;/a&gt;, IPython Parallel now has one of its most requested features: the ability to send signals to one or all engines, and forcefully restart the cluster if it’s stuck.&lt;/p&gt;
&lt;p&gt;We’ve also improved interactive feedback with progress bars, live streaming output, a new JupyterLab plugin based on dask-labextension, and support for the Jupyter widget protocol, so you can instantiate widgets on engines and interact with them directly from a notebook.&lt;/p&gt;
&lt;p&gt;Security questions have also been raised, because IPython (and Jupyter’s) use of ZeroMQ assumes a ‘trusted network’, typically localhost or BSD sockets for notebooks. Since IPython Parallel usually runs across a network, security can be more of a concern (whereas notebooks typically only use standard HTTPS connections over a network), and the level of security in the Jupyter protocol alone may not be sufficient. In the past, we’ve resorted to tunneling TCP over SSH for more secure network traffic, but still always implicitly trusting localhost.&lt;/p&gt;
&lt;p&gt;IPython Parallel 7.1 enables &lt;a href="https://rfc.zeromq.org/spec/26/"&gt;CurveZMQ&lt;/a&gt; for full authentication, encryption, and &lt;a href="https://en.wikipedia.org/wiki/Forward_secrecy"&gt;forward-secrecy&lt;/a&gt; at the transport level, solving a longstanding security shortcoming of IPython Parallel.&lt;/p&gt;
&lt;h2 id="ipython-parallel-in-the-future"&gt;IPython Parallel in the future&lt;/h2&gt;
&lt;p&gt;Scaling is the biggest challenge for IPython Parallel due to its communication model. However, that scaling is on the order of IPython &lt;em&gt;engines&lt;/em&gt;, which is not the same as the number of cores. You can benefit from this today if your tasks are already multi-threaded, e.g., through OpenMP threads in numpy, or your own Python threads or multiprocess breakdown of tasks.&lt;/p&gt;
&lt;p&gt;Better support of multi-level parallelism, where each “engine” may represent a multicore node, as individual nodes get bigger and bigger would mean moving the bar where IPython Parallel communication is the bottleneck back two orders of magnitude on large machines, because one ‘engine’ could represent 128 cores or more. Good support for 1000 128-core nodes would give IPython Parallel some pretty comfortable headroom for our target use cases, scale-wise.&lt;/p&gt;
&lt;p&gt;Also related to scaling, bringing the BroadcastView to maturity should allow us to completely replace the DirectView scheduler, as it should be able to match or beat it in almost every scenario with some further development. There are some features lacking, especially when it comes to error handling, but those can certainly be addressed in time.&lt;/p&gt;
&lt;p&gt;Finally, I’ll invite you to get involved. Especially if you are interested in prototyping parallel code, or making traditional MPI-style code interactive, check out IPython Parallel 8 and &lt;a href="https://github.com/ipython/ipyparallel/issues"&gt;let us know&lt;/a&gt; how it goes, or contribute your use case as an example.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install --upgrade ipyparallel
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
</content><category term="IPython"/></entry><entry><title>nbviewer has a new host: OVHcloud</title><link href="https://jupyter.org/blog/posts/2020/nbviewer-has-a-new-host-ovhcloud/" rel="alternate"/><published>2020-03-06T19:46:00+00:00</published><updated>2020-03-06T19:46:00+00:00</updated><author><name>Min RK</name></author><id>tag:jupyter.org,2020-03-06:/blog/posts/2020/nbviewer-has-a-new-host-ovhcloud/</id><summary type="html">&lt;p&gt;nbviewer has moved from Rackspace to OVHcloud. That means moving from Docker to Kubernetes+Helm.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;nbviewer has moved from Rackspace to OVHcloud. That means moving from Docker to Kubernetes+Helm.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For several years, &lt;a href="https://nbviewer.jupyter.org"&gt;nbviewer&lt;/a&gt; has been generously hosted by Rackspace. That sponsorship program appears to be ending, so nbviewer needed a new home; it has found one in &lt;a href="https://ovhcloud.com"&gt;OVHcloud&lt;/a&gt;. We are extremely grateful to OVHcloud for their support in keeping nbviewer running, building on their existing participation in the &lt;a href="https://jupyter.org/blog/posts/2019/the-international-binder-federation/"&gt;Binder Federation&lt;/a&gt; (literally—nbviewer is now running on the same kubernetes cluster as ovh.mybinder.org).&lt;/p&gt;
&lt;h2 id="how-we-moved"&gt;How we moved&lt;/h2&gt;
&lt;h3 id="where-were-we-before"&gt;Where were we before?&lt;/h3&gt;
&lt;p&gt;nbviewer was previously deployed using a private repo (because it contained credentials) and various commands using &lt;a href="http://www.pyinvoke.org"&gt;invoke&lt;/a&gt;. It was a mixture of custom steps, using the openstack Python API and docker machine to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;allocate two VMs&lt;/li&gt;
&lt;li&gt;build docker images&lt;/li&gt;
&lt;li&gt;deploy two nbviewer instances per node&lt;/li&gt;
&lt;li&gt;deploy memcached via nbcache on each node&lt;/li&gt;
&lt;li&gt;deploy statuspage publisher as a separate step&lt;/li&gt;
&lt;li&gt;update fastly to point to the running nbviewer instances&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The upside was that we had a repo that could immediately deploy nbviewer to anywhere with docker. With this repo, we moved our deployment strategy from a CoreOS cluster &lt;a href="https://github.com/jupyter/nbviewer.org-deploy/commit/2bf42cdfd54552d2669549bda002a8bf5f3df60d"&gt;to&lt;/a&gt; Rackspace’s short-lived &lt;a href="https://www.rackspace.com/newsroom/carina-by-rackspace-simplifies-containers-with-easy-to-use-instant-on-native-container-environment"&gt;Carina service&lt;/a&gt; to &lt;a href="https://github.com/jupyter/nbviewer.org-deploy/commit/22f54fc971941764ec3e14a68174823397a56163"&gt;deploying VMs ourselves&lt;/a&gt; with docker-machine. Migrating to a new source of VMs would not have been hard, but it wouldn’t have solved any of our challenges.&lt;/p&gt;
&lt;p&gt;Known downsides of this deployment that we’ve experienced over the years:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;only a few folks ever knew how to use it and could thus deploy updates to nbviewer&lt;/li&gt;
&lt;li&gt;it was private, contributing to above. It’s hard to onboard folks in an open community to a private repo!&lt;/li&gt;
&lt;li&gt;independent machines meant cache was not shared (minor, but contributes to our consumption of the GitHub API rate limit)&lt;/li&gt;
&lt;li&gt;no automatic recovery based on health monitoring, so when a container had issues, some humans got automated emails but no action was automatically taken. Extra frustrating because the fix was ~always to restart the container.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The credentials used by this repo have been revoked and an archived version of the repo is &lt;a href="https://github.com/jupyter/nbviewer.org-deploy/tree/archive-rackspace"&gt;available&lt;/a&gt; (with credentials redacted from history) in the new, &lt;a href="https://github.com/jupyter/nbviewer.org-deploy"&gt;public nbviewer.org-deploy repo&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="what-have-we-learned"&gt;What have we learned?&lt;/h3&gt;
&lt;p&gt;We’ve learned a lot about open, automatic, and sustainable deployments, and can now comfortably address all of the downsides above. Most of this has been learned from the communities participating in the JupyterHub and Binder projects, as seen in the &lt;a href="https://github.com/jupyterhub/mybinder.org-deploy"&gt;mybinder.org-deploy&lt;/a&gt; repo. mybinder.org-deploy is a public repo that automatically deploys and tests updates to at least four different Kubernetes clusters at the push of a button (the Big Green Merge Button, to be precise). Some things we have learned in the years since we set up our nbviewer deployment:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;kubernetes and helm are great :)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/AGWA/git-crypt"&gt;git-crypt&lt;/a&gt; allows us to have public repos with some secret contents, so deployment repos don’t need to be fully private just to protect a couple api keys.&lt;/li&gt;
&lt;li&gt;Continuous Deployment via services like Travis or Circle lowers the bar to adding maintainers on a given deployment since all that’s needed is to press the Big Green Button.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The OVH sponsorship came in the form of a slice of a kubernetes cluster; that meant we had to migrate the nbviewer deployment tools from using docker-machine to kubernetes, which for us means helm.&lt;/p&gt;
&lt;h3 id="step-1-helm-chart-for-nbviewer"&gt;Step 1: helm chart for nbviewer&lt;/h3&gt;
&lt;p&gt;The first step was to create a helm chart for nbviewer, which is done &lt;a href="https://github.com/jupyter/nbviewer/pull/905"&gt;here&lt;/a&gt;. Before, nbviewer was two docker containers running nbviewer and one running memcached per server. To turn this into a helm chart we need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;dependency on &lt;a href="https://github.com/helm/charts/tree/master/stable/memcached"&gt;memcached helm chart&lt;/a&gt; for easy deployment of the cache. All the nbviewer instances will talk to this memcache, which should improve our GitHub rate limit consumption, since the cache will be shared. This means the deprecation of our own &lt;a href="https://github.com/jupyter/nbcache"&gt;nbcache repo&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Deployment&lt;/code&gt; for nbviewer. This is (one of) the kubernetes wrappers around containers. It has a nice ‘replicas’ field to easily scale nbviewer up and down. We are currently running with 3 replicas.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Service&lt;/code&gt; to expose nbviewer to the Internet (likely also need an ingress in the future)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Deployment&lt;/code&gt; for the statuspage publisher, which updates &lt;a href="https://status.jupyter.org"&gt;https://status.jupyter.org&lt;/a&gt; with the remaining github rate limit available&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We also needed helm charts and docker images for some other components, such as cdn.jupyter.org and the nbviewer statuspage data source.&lt;/p&gt;
&lt;h3 id="step-1b-helm-chart-for-cdnjupyterorg"&gt;Step 1b: helm chart for cdn.jupyter.org&lt;/h3&gt;
&lt;p&gt;nbviewer and some other services interact with cdn.jupyter.org, which is a lightweight nginx configuration to serve the classic notebook’s javascript and css as static files(this is no longer needed for npm-based jupyterlab, which can use &lt;a href="https://unpkg.com"&gt;unpkg&lt;/a&gt; as a CDN). This used to run on a multipurpose server VM, but that is also being retired for the same reason. The result was creating a docker image and helm chart for serving the contents of cdn.jupyter.org. The scripts used to run the existing CDN were already in &lt;a href="http://github.com/jupyter/cdn.jupyter.org/"&gt;a repo&lt;/a&gt;, so it was a small amount of work to adapt this to run in a container instead of on a server. We may retire cdn.jupyter.org in the future, so please don’t rely on it :)&lt;/p&gt;
&lt;p&gt;This is where we are right now — nbviewer.jupyter.org and cdn.jupyter.org are being served by OVH and the Rackspace machines are being retired.&lt;/p&gt;
&lt;h3 id="step-2-automatic-deployment"&gt;Step 2: automatic deployment&lt;/h3&gt;
&lt;p&gt;It would have been ideal for this to be step 1, but that’s now how it happened. Sometimes you need to get it done quickly before you get it done right. The task here will be to make a new, public nbviewer-deploy repo following the patterns we have learned in mybinder.org-deploy. That will mean:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;create public nbviewer.org-deploy repo with configuration and git-crypt encrypted secrets for deploying nbviewer on the ovh cluster (&lt;a href="https://github.com/jupyter/nbviewer.org-deploy"&gt;done&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;adopting &lt;a href="https://github.com/jupyterhub/chartpress"&gt;chartpress&lt;/a&gt; to publish our helm charts for nbviewer and version-tagged images from the nbviewer repo&lt;/li&gt;
&lt;li&gt;configure Travis-CI or other CI service to automatically deploy updates with helm, so merging a PR is all we need to do to deploy updates to nbviewer&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thanks to OVHCloud for their continued support of Jupyter and Binder, and the JupyterHub and Binder communities for teaching me how to operate services in the open with kubernetes and helm.&lt;/p&gt;
</content><category term="Kubernetes"/><category term="nbviewer"/></entry><entry><title>Announcing JupyterHub 1.0</title><link href="https://jupyter.org/blog/posts/2019/announcing-jupyterhub-1-0/" rel="alternate"/><published>2019-05-04T03:58:00+00:00</published><updated>2019-05-04T03:58:00+00:00</updated><author><name>Min RK</name></author><id>tag:jupyter.org,2019-05-04:/blog/posts/2019/announcing-jupyterhub-1-0/</id><summary type="html">&lt;p&gt;Today, we are pleased to announce the 1.0 release of JupyterHub. We’ve come a long way since our first release in March, 2015. There are loads of new features and improvements covered in the changelog, but we’ll cover a few of the highlights here.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Today, we are pleased to announce the 1.0 release of JupyterHub. We’ve come a long way since our first release in March, 2015. There are loads of new features and improvements covered in the &lt;a href="https://jupyterhub.readthedocs.io/en/latest/changelog.html"&gt;changelog&lt;/a&gt;, but we’ll cover a few of the highlights here.&lt;/p&gt;
&lt;p&gt;You can upgrade jupyterhub with conda or pip:&lt;/p&gt;
&lt;p&gt;(Before upgrading, always make sure to backup your database!)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;conda install -c conda-forge jupyterhub==1.0.*
pip install --upgrade jupyterhub==1.0.*
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="ui-for-named-servers"&gt;UI for named servers&lt;/h2&gt;
&lt;p&gt;Named servers are a concept in JupyterHub that allows each user to have more than one server (e.g. a ‘gpu’ server with access to gpus, or a ‘cs284’ server with the necessary resources for a given class). JupyterHub 1.0 introduces UI for managing these servers, so users can create/start/stop/delete their servers from the hub home page:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="UI for managing named servers" src="https://jupyter.org/blog/posts/2019/announcing-jupyterhub-1-0/images/001-1_VozbyUHN9qTICXzQSKQvfw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;UI for managing named servers&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="improved-pending-spawn-handling-and-less-implicit-spawn-behavior"&gt;Improved pending spawn handling and less implicit spawn behavior&lt;/h2&gt;
&lt;p&gt;When users launch their server, they will be faced with a progress bar showing the progress. Spawners can emit custom messages to indicate the stages of launch, which are especially useful when it can take a while.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="a spawn taking a bit" src="https://jupyter.org/blog/posts/2019/announcing-jupyterhub-1-0/images/002-1_e5FwpbvGl7SFbl9vGevBwA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;a spawn taking a bit&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="support-for-total-internal-encryption-with-ssl"&gt;Support for total internal encryption with SSL&lt;/h2&gt;
&lt;p&gt;If your JupyterHub is publicly accessible, we hope you are using HTTPS (it’s never been easier, thanks to &lt;a href="https://letsencrypt.org"&gt;letsencrypt&lt;/a&gt;)! However, JupyterHub exclusively used HTTP for internal communication. This is usually fine, especially for single-machines, but for deployments on distributed or shared infrastructure it’s a good idea to encrypt communication between your components. With 1.0, you can &lt;a href="https://jupyterhub.readthedocs.io/en/latest/reference/websecurity.html#encrypt-internal-connections-with-ssl-tls"&gt;enable SSL encryption&lt;/a&gt; and authentication of all internal communication as well. Spawners must support this feature by defining &lt;a href="https://jupyterhub.readthedocs.io/en/latest/api/spawner.html?highlight=move_certs#jupyterhub.spawner.Spawner.move_certs"&gt;Spawner.move_certs&lt;/a&gt;. Currently, local spawners and DockerSpawner support internal ssl.&lt;/p&gt;
&lt;h2 id="support-for-checking-and-refreshing-authentication"&gt;Support for checking and refreshing authentication&lt;/h2&gt;
&lt;p&gt;JupyterHub authentication is most often managed by an external authority, e.g. GitHub OAuth. &lt;a href="https://jupyterhub.readthedocs.io/en/latest/reference/authenticators.html#authentication-state"&gt;Auth state&lt;/a&gt; can be used to persist credentials, such as enabling push access to repositories or access to resources. These credentials can sometimes expire or need refreshing. Until now, expiring or refreshing authentication was not well supported by JupyterHub. 1.0 introduces new configuration to refresh or expire authentication info:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;c.Authenticator.auth_refresh_age&lt;/code&gt; allows authentication to expire after a number of seconds.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;c.Authenticator.refresh_pre_spawn&lt;/code&gt; forces a refresh of authentication prior to spawning a server, effectively requiring a user to have up-to-date authentication when they start their server, which can be important when the user environment should have credentials with access to external resources.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Authenticator.refresh_auth&lt;/code&gt; defines what it means to refresh authentication (default is nothing, but can check with external authorities to re-load info such as tokens or group membership) and can be customized by Authenticator implementations.&lt;/p&gt;
&lt;h2 id="thanks"&gt;Thanks!&lt;/h2&gt;
&lt;p&gt;Huge thanks to the many people who have contributed to this release, whether it was through discussion, testing, documentation, or development.&lt;/p&gt;
</content><category term="JupyterHub"/></entry><entry><title>Open Redirect Vulnerability in Jupyter notebook, JupyterHub</title><link href="https://jupyter.org/blog/posts/2019/open-redirect-vulnerability-in-jupyter-jupyterhub/" rel="alternate"/><published>2019-03-28T14:03:00+00:00</published><updated>2019-04-01T11:20:00+00:00</updated><author><name>Min RK</name></author><id>tag:jupyter.org,2019-03-28:/blog/posts/2019/open-redirect-vulnerability-in-jupyter-jupyterhub/</id><summary type="html">&lt;p&gt;Update: notebook 5.7.7 and JupyterHub 0.9.5 contained incomplete fixes for this issue. 5.7.8 and 0.9.6 are released with more complete fixes.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;Update: notebook 5.7.7 and JupyterHub 0.9.5 contained incomplete fixes for this issue. 5.7.8 and 0.9.6 are released with more complete fixes.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;JupyterHub ≤ 0.9.5 and Notebook ≤ 5.7.7 are affected. Versions 0.9.6 and 5.7.8 are released with fixes. Upgrade with pip or conda:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install --upgrade &amp;#39;notebook&amp;gt;=5.7.8&amp;#39;
pip install --upgrade &amp;#39;jupyterhub&amp;gt;=0.9.6&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;conda install -c conda-forge &amp;#39;notebook&amp;gt;=5.7.8&amp;#39;
conda install -c conda-forge &amp;#39;jupyterhub&amp;gt;=0.9.6&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;notebook 5.7.8 also fixes some compatibility issues and regressions introduced in 5.7.5 and 5.7.6, especially on some Windows systems. If you use the &lt;a href="https://zero-to-jupyterhub.readthedocs.io"&gt;jupyterhub helm chart&lt;/a&gt;, version 0.8.2 upgrades JupyterHub to 0.9.6.&lt;/p&gt;
&lt;h2 id="what-is-an-open-redirect-vulnerability"&gt;What is an Open Redirect Vulnerability?&lt;/h2&gt;
&lt;p&gt;Login pages tend to take a parameter for redirecting back to a page after successful login, e.g. &lt;code&gt;/login?next=/notebooks/mynotebook.ipynb&lt;/code&gt;, so that you aren’t disrupted too much if you try to visit a page, but have to authenticate first. An &lt;a href="https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md"&gt;Open Redirect Vulnerability&lt;/a&gt; is when a malicious person crafts a link pointing to the login page of a trusted site, but setting the “redirect after successful login” parameter to send the user to their own site, instead of a page on the authenticated site (the notebook or JupyterHub server), e.g. &lt;code&gt;/login?next=http://badwebsite.biz&lt;/code&gt;. This doesn’t necessarily compromise anything immediately, but it enables phishing if users don’t notice that the domain has changed, e.g. by showing a fake “re-enter your password” page. Servers generally have to validate the redirect URL to avoid this. Both JupyterHub and Notebook already do this, but the validation didn’t take into account all possible ways to redirect to other sites, so some malicious URLs could still be crafted to redirect away from the server (the above example does not work in any recent version of either package). Only certain browsers (Chrome and Firefox, not Safari) could be redirected from the JupyterHub login page, but all browsers could be redirected away from a standalone notebook server.&lt;/p&gt;
&lt;p&gt;This vulnerability has been assigned CVE-2019-10255. This post will be updated when it has been assigned. Thanks to Ronak Odhaviya for reporting the issue. You can report security issues by emailing &lt;a href="mailto:ipython-security@googlegroups.com"&gt;ipython-security@googlegroups.com&lt;/a&gt;.&lt;/p&gt;
</content><category term="Jupyter Notebook"/><category term="JupyterHub"/><category term="security"/></entry><entry><title>Jupyter notebook XSSI security fix</title><link href="https://jupyter.org/blog/posts/2019/jupyter-notebook-xssi-security-fix/" rel="alternate"/><published>2019-03-09T20:46:00+00:00</published><updated>2019-03-09T20:46:00+00:00</updated><author><name>Min RK</name></author><id>tag:jupyter.org,2019-03-09:/blog/posts/2019/jupyter-notebook-xssi-security-fix/</id><summary type="html">&lt;p&gt;We have just released Jupyter notebook 5.7.6 with a security fix for a cross-site inclusion (XSSI) vulnerability, where content from a Jupyter server could be included in another page if the visitor is logged in to the Jupyter server and the author of the page knows the URL of the server and the…&lt;/p&gt;</summary><content type="html">&lt;p&gt;We have just released Jupyter notebook 5.7.6 with a security fix for a cross-site inclusion (&lt;a href="https://www.scip.ch/en/?labs.20160414"&gt;XSSI&lt;/a&gt;) vulnerability, where content from a Jupyter server could be included in another page if the visitor is logged in to the Jupyter server and the author of the page knows the URL of the server and the path within the server’s notebook directory that they would like to include. Further, it has been demonstrated with the Internet Explorer browser that some content from the accessed file can be retrieved by the attacking page. This has not yet been demonstrated with other browsers, however.&lt;/p&gt;
&lt;p&gt;To upgrade:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install --upgrade &amp;#39;notebook&amp;gt;=5.7.6&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;or conda:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;conda install &amp;#39;notebook&amp;gt;=5.7.6&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This vulnerability was reported by the HackerOne hacker Abhishek Bundela, via Jonathan Kamens at Quantopian. The patch was developed by Min Ragan-Kelley with help from Devdatta Akhawe. Jupyter is grateful to Quantopian’s bug bounty program, which has found and reported this and other security flaws in Jupyter.&lt;/p&gt;
&lt;p&gt;This vulnerability has been assigned CVE-2019-9644.&lt;/p&gt;
</content><category term="Jupyter Notebook"/><category term="security"/></entry><entry><title>Zero to JupyterHub helm chart 0.8</title><link href="https://jupyter.org/blog/posts/2019/zero-to-jupyterhub-helm-chart-0-8/" rel="alternate"/><published>2019-02-21T18:34:00+00:00</published><updated>2019-02-21T18:34:00+00:00</updated><author><name>Min RK</name></author><id>tag:jupyter.org,2019-02-21:/blog/posts/2019/zero-to-jupyterhub-helm-chart-0-8/</id><summary type="html">&lt;p&gt;We’ve just released version 0.8 of the jupyterhub helm chart.&lt;/p&gt;</summary><content type="html">&lt;p&gt;We’ve just released version 0.8 of the jupyterhub helm chart.&lt;/p&gt;
&lt;p&gt;For those who may not know, &lt;a href="https://zero-to-jupyterhub.readthedocs.io"&gt;Zero to JupyterHub&lt;/a&gt; is a guide and &lt;a href="https://helm.sh"&gt;helm chart&lt;/a&gt; for deploying &lt;a href="https://jupyterhub.readthedocs.io"&gt;JupyterHub&lt;/a&gt; on &lt;a href="https://kubernetes.io"&gt;Kubernetes&lt;/a&gt;. Helm is like a package manager for Kubernetes, which aims to make it easy to install and manage applications on your cluster.&lt;/p&gt;
&lt;p&gt;There are loads of bugfixes and improvements to this release, and we encourage you to check out the &lt;a href="https://github.com/jupyterhub/zero-to-jupyterhub-k8s/blob/master/CHANGELOG.md"&gt;change log&lt;/a&gt;. Here, we’ll discuss two highlights from the new features: profiles and autoscaling.&lt;/p&gt;
&lt;h2 id="profiles"&gt;Profiles&lt;/h2&gt;
&lt;p&gt;By adopting the profiles pattern established in &lt;a href="https://github.com/jupyterhub/wrapspawner"&gt;WrapSpawner&lt;/a&gt;, Kubernetes users can now pick from a selection of “profiles” or pre-set configurations, defined by the operator of a cluster. For example, defining small, medium, and large resource requests, or special profiles for GPUs.&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://jupyter.org/blog/posts/2019/zero-to-jupyterhub-helm-chart-0-8/images/001-1_XRxmQ4Mmd48iE7ST-zdcUg.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;The above sample is created from the following snippet in your values.yaml:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;singleuser&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;profileList&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;display_name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Small: default&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;small&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CPU&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;no&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GPU&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;This&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;True&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;kubespawner_override&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;cpu_limit&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;cpu_guarantee&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;mem_limit&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;1G&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;mem_guarantee&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;512M&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;extra_resource_limits&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;display_name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Big: 8 CPUs&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;big&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CPUs&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;no&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GPU&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="n"&gt;GB&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RAM&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;kubespawner_override&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;cpu_limit&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;cpu_guarantee&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;mem_limit&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;64G&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;mem_guaranttee&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;64G&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;extra_resource_limits&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;display_name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GPU job&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;This&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gives&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CPUs&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="n"&gt;GB&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RAM&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GPU&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;kubespawner_override&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;consideratio&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;singleuser&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;gpu&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;v0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;cpu_limit&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;cpu_guarantee&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;mem_limit&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;16G&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;mem_guarantee&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;16G&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;extra_resource_limits&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="n"&gt;nvidia&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gpu&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;1&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="autoscaling"&gt;Autoscaling&lt;/h2&gt;
&lt;p&gt;Autoscaling is a big focus of this release, and many of the new features in 0.8 of the chart relate to scaling in some way. At the center is a new ‘user scheduler’ a custom kubernetes scheduler responsible for assigning user pods to nodes. To enable the user scheduler:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;scheduling&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;userScheduler&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;enabled&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There are two major pain-points for autoscaling with JupyterHub. First, is &lt;strong&gt;scaling up&lt;/strong&gt;. Kubernetes scale-up is very basic: If scheduling a pod fails due insufficient resources, a new node is requested. This isn’t a great fit for JupyterHub. If you happen to be the unlucky user who is the first to need a new node, launching your server can take several minutes as the new node is requested and warming up. To prevent this, the chart adds the notion of “placeholder pods,” which are pods that request the same resources as user pods, but have lower priority and will thus be evicted immediately if a user pod needs their resources. This allows a cluster to always have a certain amount of “headroom” of vacant slots available for users so that requesting a new node occurs not when there are 0 slots available for users, but instead when there are 5 or 10 (you choose) slots available. See &lt;a href="https://discourse.jupyter.org/t/planning-placeholders-with-jupyterhub-helm-chart-0-8-tested-on-mybinder-org/213"&gt;this exploration&lt;/a&gt; of how mybinder.org arrived at 25 placeholders for its deployment. To enable 10 user placeholders:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;scheduling&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;podPriority&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;enabled&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;userPlaceholder&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;enabled&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;replicas&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The second major improvement to autoscaling that comes with the user scheduler is in &lt;strong&gt;scaling down&lt;/strong&gt;. If your cluster is automatically scaling up, you probably want it to scale down as well when you no longer need the added capacity. Unfortunately, because user pods cannot be kicked out, Kubernetes’ default scheduler doesn’t manage to drain nodes that are no longer needed without some manual intervention. The user scheduler adds this feature, ensuring that, over time, your newest nodes eventually drain when they are no longer needed. It accomplishes this by prioritizing the node with the most other user pods when picking a node to start new users. If there is an extra node, over time eventually all the users on that node will stop and the node will become idle and culled by the cluster autoscaler. This is the default behavior . There’s loads more scheduling optimizations available &lt;a href="https://zero-to-jupyterhub.readthedocs.io/en/latest/optimization.html#optimizations"&gt;in the updated documentation&lt;/a&gt;, including dedicating certain nodes to users so that user pods never run on the same node as others, etc.&lt;/p&gt;
&lt;p&gt;And finally, a huge thanks to the numerous contributors who helped make this release, be it via documentation contributions, testing, discussion, or code.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://zero-to-jupyterhub.readthedocs.io/"&gt;Check it out&lt;/a&gt;&lt;/p&gt;
</content><category term="JupyterHub"/><category term="Kubernetes"/></entry><entry><title>Jupyter Notebook security fixes</title><link href="https://jupyter.org/blog/posts/2018/jupyter-notebook-security-fixes/" rel="alternate"/><published>2018-11-19T22:20:00+00:00</published><updated>2018-11-19T22:20:00+00:00</updated><author><name>Min RK</name></author><id>tag:jupyter.org,2018-11-19:/blog/posts/2018/jupyter-notebook-security-fixes/</id><summary type="html">&lt;p&gt;Two security issues have been found and fixed this week, where untrusted javascript could be executed if malicious files could be delivered to the users system and the user takes specific actions with those malicious files.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Two security issues have been found and fixed this week, where untrusted javascript could be executed if malicious files could be delivered to the users system and the user takes specific actions with those malicious files.&lt;/p&gt;
&lt;p&gt;The first allowed nbconvert endpoints (such as Print Preview) to render untrusted HTML and javascript with access to the notebook server. This is fixed in notebook 5.7.1. All notebook versions prior to 5.7.1 are affected. Thanks to Jonathan Kamens of Quantopian for reporting. This issue has been assigned CVE-2018-19351.&lt;/p&gt;
&lt;p&gt;The second issue allowed maliciously crafted directory names to execute javascript when opened in the tree view. This is fixed in notebook 5.7.2. All versions of notebook from 5.3.0 to 5.7.1 are affected. Thanks to Marvin Solano Quesada for reporting. This issue has been assigned CVE-2018-19352.&lt;/p&gt;
&lt;p&gt;You can check your version of the notebook package by issuing the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;jupyter&lt;span class="w"&gt; &lt;/span&gt;notebook&lt;span class="w"&gt; &lt;/span&gt;--version
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Whether you are using classic notebook, JupyterLab or any other notebook server extensions, we recommend that you update the &lt;code&gt;notebook&lt;/code&gt; package with :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;--upgrade&lt;span class="w"&gt; &lt;/span&gt;notebook
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;or if you are using conda-forge&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;conda&lt;span class="w"&gt; &lt;/span&gt;upgrade&lt;span class="w"&gt; &lt;/span&gt;notebook
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Thanks especially to Jonathan and Marvin for reporting these issues! If you find a security issue in a Jupyter project, please report it to &lt;code&gt;security@ipython.org&lt;/code&gt;.&lt;/p&gt;
</content><category term="Jupyter Notebook"/><category term="security"/></entry><entry><title>Security fix for Jupyter Notebook</title><link href="https://jupyter.org/blog/posts/2018/security-fix-for-jupyter-notebook/" rel="alternate"/><published>2018-07-18T04:31:00+00:00</published><updated>2018-08-16T17:33:00+00:00</updated><author><name>Min RK</name></author><id>tag:jupyter.org,2018-07-18:/blog/posts/2018/security-fix-for-jupyter-notebook/</id><summary type="html">&lt;p&gt;We have just released Jupyter Notebook 5.6.0. This release fixes a vulnerability that could allow a maliciously crafted notebook to execute JavaScript when it is opened, bypassing the trusted-notebook mechanism.&lt;/p&gt;</summary><content type="html">&lt;p&gt;We have just released Jupyter Notebook 5.6.0. This release fixes a vulnerability that could allow a maliciously crafted notebook to execute JavaScript when it is opened, bypassing the trusted-notebook mechanism.&lt;/p&gt;
&lt;p&gt;We recommend updating the notebook immediately, via pip:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;notebook&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;.6.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;or conda:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;conda&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;notebook&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;.6.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Affected versions&lt;/strong&gt;: all releases prior to 5.6.0&lt;/p&gt;
&lt;p&gt;JupyterLab users are affected, independent of the version of JupyterLab itself. Upgrading the notebook package to 5.6.0 resolves the issue for users of both JupyterLab and the classic notebook.&lt;/p&gt;
&lt;p&gt;A CVE has been requested for the vulnerability. Release notes for 5.6.0 and this post will be updated as the CVE is assigned. More details of the vulnerability will be released in 30 days, on August 16, 2018.&lt;/p&gt;
&lt;p&gt;Security reports for Jupyter are greatly appreciated. You can &lt;a href="http://jupyter-notebook.readthedocs.io/en/stable/security.html#reporting-security-issues"&gt;report security issues&lt;/a&gt; to &lt;a href="mailto:security@ipython.org"&gt;security@ipython.org&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks to Jonathan Kamens for reporting this issue to the security list.&lt;/p&gt;
&lt;p&gt;[Update July 28] The vulnerability have been assigned number CVE-2018-1999024.&lt;/p&gt;
&lt;p&gt;[Update August 16] MathJax versions prior to version 2.7.4 contains a Cross Site Scripting (XSS) vulnerability in the \unicode{} macro that can result in potentially untrusted Javascript running within a web browser, for example at notebook load. Notebook 5.6.0 ships with an updated mathjax version 2.7.4 which fixes this vulnerability.&lt;/p&gt;
</content><category term="Jupyter Notebook"/><category term="security"/></entry><entry><title>JupyterHub 0.9</title><link href="https://jupyter.org/blog/posts/2018/jupyterhub-0-9/" rel="alternate"/><published>2018-06-18T13:18:00+00:00</published><updated>2018-06-18T15:15:00+00:00</updated><author><name>Min RK</name></author><id>tag:jupyter.org,2018-06-18:/blog/posts/2018/jupyterhub-0-9/</id><summary type="html">&lt;p&gt;We are pleased to announce the latest release of JupyterHub. JupyterHub is the multi-user server for Jupyter notebooks, allowing students or researchers to have their own workspace. This release has lots of improvements, especially for stability and performance with large numbers of users.&lt;/p&gt;</summary><content type="html">&lt;p&gt;We are pleased to announce the latest release of JupyterHub. JupyterHub is the multi-user server for Jupyter notebooks, allowing students or researchers to have their own workspace. This release has lots of improvements, especially for stability and performance with large numbers of users.&lt;/p&gt;
&lt;p&gt;The biggest change is probably the adoption of asyncio coroutines throughout instead of tornado coroutines and improved support for asyncio coroutines in general, adopting &lt;code&gt;async def&lt;/code&gt; coroutine syntax. This means that JupyterHub 0.9 requires Python ≥ 3.5 and tornado ≥ 5.0.&lt;/p&gt;
&lt;p&gt;There are lots of improvements to the &lt;a href="https://jupyterhub.readthedocs.io/en/0.9.0/_static/rest-api/index.html"&gt;REST API&lt;/a&gt;, including more detailed time information and token management.&lt;/p&gt;
&lt;p&gt;There are also several improvements to the ability to customize JupyterHub’s HTML templates.&lt;/p&gt;
&lt;p&gt;See the &lt;a href="http://jupyterhub.readthedocs.io/en/0.9.0/changelog.html"&gt;changelog&lt;/a&gt; for a more detailed list of changes.&lt;/p&gt;
&lt;p&gt;Make sure to backup your jupyterhub database prior to upgrade, and then upgrade with pip:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python3 -m pip install --upgrade &amp;#39;jupyterhub==0.9.*&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;or conda:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;conda install -c conda-forge jupyterhub=0.9
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For Kubernetes users, we will have an 0.7 release of the &lt;a href="https://zero-to-jupyterhub.readthedocs.io/en/latest/"&gt;helm chart&lt;/a&gt; before too long.&lt;/p&gt;
&lt;p&gt;Thanks to everyone who has contributed to JupyterHub!&lt;/p&gt;
</content><category term="JupyterHub"/></entry><entry><title>Security fix for JupyterHub GitLab OAuthenticator Group Whitelists</title><link href="https://jupyter.org/blog/posts/2018/security-fix-for-jupyterhub-gitlab-oauthenticator/" rel="alternate"/><published>2018-02-16T22:12:00+00:00</published><updated>2018-03-16T15:15:00+00:00</updated><author><name>Min RK</name></author><id>tag:jupyter.org,2018-02-16:/blog/posts/2018/security-fix-for-jupyterhub-gitlab-oauthenticator/</id><summary type="html">&lt;p&gt;This vulnerability has been assigned CVE-2018-7206&lt;/p&gt;</summary><content type="html">&lt;p&gt;This vulnerability has been assigned CVE-2018-7206&lt;/p&gt;
&lt;p&gt;If you are using JupyterHub with the GitLab OAuthenticator and its gitlab_group_whitelist support, there is a security issue where the authenticator will allow users outside your intended group whitelist to create accounts. A fix has been released as OAuthenticator 0.6.2 and 0.7.3. No other authentication mechanism, including GitLabOAuthenticator without using the group whitelist feature, is affected. If you are using GitLab authentication with group whitelist support, upgrade oauthenticator immediately:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python3 -m pip install --upgrade oauthenticator
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Thanks to Joseph Weston for reporting the issue and providing the fix.&lt;/p&gt;
&lt;p&gt;Timeline (all times UTC):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2018-02-16 09:51 Joseph Weston reports security issue to the Jupyter security list&lt;/li&gt;
&lt;li&gt;2018-02-16 16:08 Fix is verified and applied to oauthenticator master&lt;/li&gt;
&lt;li&gt;2018-02-16 21:52 oauthenticator 0.7.3 and 0.6.2 are released with the fix&lt;/li&gt;
&lt;li&gt;2018-02-18 03:02 CVE-2018-7206 assigned&lt;/li&gt;
&lt;/ul&gt;
</content><category term="JupyterHub"/><category term="security"/></entry><entry><title>Public Notebooks and Security</title><link href="https://jupyter.org/blog/posts/2017/public-notebooks-and-security/" rel="alternate"/><published>2017-11-28T19:33:00+00:00</published><updated>2017-11-28T19:33:00+00:00</updated><author><name>Min RK</name></author><id>tag:jupyter.org,2017-11-28:/blog/posts/2017/public-notebooks-and-security/</id><summary type="html">&lt;p&gt;tl;dr: don’t disable notebook authentication!&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;tl;dr: don’t disable notebook authentication!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Let’s chat a little bit about public Jupyter notebook servers and security. A lot of folks run notebook servers that are publicly accessible, whether they are the access point to a cloud server or remote access to their home or office computer. This is great! It is important to keep in mind that, since arbitrary code execution is the main point of IPython and Jupyter, running a publicly accessible server without authentication or encryption is a very bad idea indeed. Prior to notebook 4.3, this was the &lt;em&gt;default&lt;/em&gt; behavior of Jupyter, which has led to a lot of insecure notebook servers running on cloud services. One aspect that may not be apparent is that &lt;strong&gt;people can find your public notebook server&lt;/strong&gt;, especially if it is on one of the popular cloud providers. Systematic port scans will turn up notebook servers, especially for those running on the default port of 8888. And Jupyter has become popular enough (🎉) that we know people are doing this (😠). &lt;strong&gt;Running a publicly accessible notebook server without authentication is making your computing resources free for the world to use.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The highlights to keep in mind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Always enable a password or token for publicly accessible notebook servers (this includes shared computers)&lt;/li&gt;
&lt;li&gt;Use HTTPS for authenticated notebooks over the Internet&lt;/li&gt;
&lt;li&gt;Unsecured public notebook servers are discoverable&lt;/li&gt;
&lt;li&gt;Running on a cloud service should be treated as if it’s publicly accessible&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first two points shouldn’t be new information, and anyone &lt;a href="https://jupyter-notebook.readthedocs.io/en/latest/public_server.html#running-a-public-notebook-server"&gt;following the documentation&lt;/a&gt; should be adding basic authentication when exposing a server to the public. If you are already accessing a notebook server over https with a password or token, you have nothing to worry about. However, if you are running unsecured notebook servers on the Internet, please stop and &lt;a href="http://jupyter-notebook.readthedocs.io/en/latest/public_server.html#securing-a-notebook-server"&gt;enable a password&lt;/a&gt;. The latter points are important and may not be obvious, but the gist is: don’t disable notebook authentication, even if you think your bases are covered via other means.&lt;/p&gt;
&lt;p&gt;In notebook 4.3, a token is enabled by default, making it a little bit more inconvenient to run a totally insecure notebook server. We can’t feasibly require HTTPS by default, but free, trusted HTTPS has never been easier, thanks to &lt;a href="https://letsencrypt.org/"&gt;LetsEncrypt&lt;/a&gt;. If you can’t assign a domain name to your server, self-signed certificates &lt;a href="http://jupyter-notebook.readthedocs.io/en/latest/public_server.html#using-ssl-for-encrypted-communication"&gt;can be used&lt;/a&gt;, with some caveats (notably: Safari requires self-signed certificates be trusted at the system level before it will allow websocket connections). For shared deployments, you can also use &lt;a href="https://jupyterhub.readthedocs.io"&gt;JupyterHub&lt;/a&gt;, which allows you to authenticate notebooks via the system (PAM) or OAuth providers such as GitHub, Google, etc.&lt;/p&gt;
&lt;p&gt;Be safe out there.&lt;/p&gt;
</content><category term="Jupyter Notebook"/><category term="security"/></entry><entry><title>JupyterHub 0.8</title><link href="https://jupyter.org/blog/posts/2017/jupyterhub-0-8/" rel="alternate"/><published>2017-10-03T19:42:00+00:00</published><updated>2017-10-03T19:42:00+00:00</updated><author><name>Min RK</name></author><id>tag:jupyter.org,2017-10-03:/blog/posts/2017/jupyterhub-0-8/</id><summary type="html">&lt;p&gt;We are pleased to announce the release of JupyterHub 0.8. This is a big release with many fixes and improvements, and some major changes.&lt;/p&gt;</summary><content type="html">&lt;p&gt;We are pleased to announce the release of JupyterHub 0.8. This is a big release with many fixes and improvements, and some major changes.&lt;/p&gt;
&lt;p&gt;To upgrade from jupyterhub 0.7:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Stop jupyterhub&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pip3 install --upgrade jupyterhub==0.8&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Backup your database: e.g.&lt;code&gt;cp -v jupyterhub.sqlite jupyterhub-backup-$(date +%Y-%m-%d).sqlite&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Upgrade your database: &lt;code&gt;jupyterhub upgrade-db&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;See &lt;a href="http://jupyterhub.readthedocs.io/en/latest/changelog.html#changelog"&gt;the changelog&lt;/a&gt; for more details. The “Notes on Upgrading” below also provides more specifics.&lt;/p&gt;
&lt;h2 id="oauth"&gt;OAuth&lt;/h2&gt;
&lt;p&gt;Perhaps the biggest change is the switch to using OAuth 2.0 for the internal authentication mechanism in JupyterHub. This shouldn’t have much of a visible effect on your deployments other than hopefully a reduction in redirect-loops when users login.&lt;/p&gt;
&lt;p&gt;If you are writing a JupyterHub Service, then you can switch to OAuth by using the &lt;code&gt;HubOAuthenticated&lt;/code&gt; or &lt;code&gt;HubOAuth&lt;/code&gt; classes if you were previously using &lt;code&gt;HubAuthenticated&lt;/code&gt; or &lt;code&gt;HubAuth&lt;/code&gt;. All of these classes now support token-based access via the &lt;code&gt;Authorization&lt;/code&gt; header, just like the Hub itself.&lt;/p&gt;
&lt;h2 id="stability-scalability-and-performance"&gt;Stability, Scalability, and Performance&lt;/h2&gt;
&lt;p&gt;As part of supporting larger deployments, we have done some scalability stress testing of the Hub, finding and fixing several race conditions that could manifest as redirect loops, bugs, and performance improvements when the Hub is under load. This has been led largely by the Berkeley Data Science Education Program team, which has over one thousand students using a JupyterHub instance this Fall. We now know that one JupyterHub 0.8 instance can support a few thousand users logging in and attempting to spawn their servers at the same time. Once servers are active, the Hub is minimally involved, and the proxy becomes the next bottleneck.&lt;/p&gt;
&lt;h2 id="custom-proxy-implementations"&gt;Custom Proxy Implementations&lt;/h2&gt;
&lt;p&gt;Another major new feature targeted at scalability is custom proxy implementations. JupyterHub has always used Configurable-HTTP-Proxy (CHP), a single-process Node.js HTTP proxy. This is a single process bottleneck and potential single point of failure for Hub deployments. CHP can handle a lot of concurrent active users (at least several thousand), but for larger scale applications, Hub deployments may want to use a more scalable and/or robust proxy implementation.&lt;/p&gt;
&lt;p&gt;To achieve this, JupyterHub 0.8 introduces a Python API abstracting the proxy needs of JupyterHub and developers can provide their own proxy implementations as alternatives to CHP. The first such implementation uses a Kubernetes Traefik-based Ingress for the proxy, as part of &lt;a href="https://github.com/jupyterhub/kubespawner"&gt;KubeSpawner&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="persisting-authentication-state"&gt;Persisting Authentication State&lt;/h2&gt;
&lt;p&gt;Another new feature available to Authenticators is the encrypted persistence of authentication state. This is aimed at preserving and passing authenticator-related state, such as client certificates or GitHub API tokens. Authenticators provided by &lt;a href="https://github.com/jupyterhub/oauthenticator"&gt;oauthenticator&lt;/a&gt; 0.7 support persisting auth state from upstream authentication services.&lt;/p&gt;
&lt;h2 id="other-highlights"&gt;Other Highlights&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Preliminary (API-only, no GUI) support for multiple named servers per user&lt;/li&gt;
&lt;li&gt;Token-based access to the single-user server API&lt;/li&gt;
&lt;li&gt;A page for users to request new API tokens from the Hub&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="notes-on-upgrading"&gt;Notes on upgrading&lt;/h2&gt;
&lt;p&gt;As with every update to JupyterHub, make sure to back-up your database and run &lt;code&gt;upgrade-db&lt;/code&gt;. For example, if using JupyterHub with SQLite:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Backup:&lt;code&gt;cp -v jupyterhub.sqlite jupyterhub-backup-$(date +%Y-%m-%d).sqlite&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Upgrade: &lt;code&gt;jupyterhub upgrade-db&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Due to the change in internal authentication, it is &lt;strong&gt;important&lt;/strong&gt; that you make sure that single-user servers and the Hub itself are upgraded at the same time. If you are running everything in one env (e.g. the default Spawner, SudoSpawner, or similar), there is nothing to do. However, if you are using a container-based setup to launch single-user server (e.g. DockerSpawner or KubeSpawner), make sure that your user image and your Hub both get the upgrade at the same time. In most cases, this means adding to your Dockerfile:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;RUN pip3 install jupyterhub==0.8.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;which ensures the correct version of JupyterHub is installed.&lt;/p&gt;
&lt;p&gt;Thanks to everyone who has contributed to this release, especially users who have helped out with testing JupyterHub during the beta process, which helps make this a great release!&lt;/p&gt;
</content><category term="JupyterHub"/></entry><entry><title>nbviewer and GitHub’s rate limit</title><link href="https://jupyter.org/blog/posts/2017/nbviewer-and-githubs-rate-limit/" rel="alternate"/><published>2017-03-01T07:45:00+00:00</published><updated>2017-09-25T09:25:00+00:00</updated><author><name>Min RK</name></author><id>tag:jupyter.org,2017-03-01:/blog/posts/2017/nbviewer-and-githubs-rate-limit/</id><summary type="html">&lt;p&gt;It turns out that some overzealous SEO bots were eating up all of our resources. Last week, we discovered this cause of the API rate limit exhaustion and took a few actions to resolve it.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;If you’ve been using &lt;a href="https://nbviewer.jupyter.org"&gt;nbviewer&lt;/a&gt; recently, you may have seen this message more often than you would like:&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://jupyter.org/blog/posts/2017/nbviewer-and-githubs-rate-limit/images/g001-Screen-Shot-2017-02-26-at-21.31.09.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;It turns out that some overzealous SEO bots were eating up all of our resources. Last week, we discovered this cause of the API rate limit exhaustion and took a few actions to resolve it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;tl;dr;&lt;/strong&gt; nbviewer shouldn’t hit the rate limit so often anymore. This is what our API usage availability has looked like since we deployed the changes I’ll describe here:&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://jupyter.org/blog/posts/2017/nbviewer-and-githubs-rate-limit/images/g002-rate-limit-copy.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;h2 id="nbviewers-architecture"&gt;nbviewer’s architecture&lt;/h2&gt;
&lt;p&gt;I’d like to take this opportunity to give an overview of how we deploy nbviewer, so we can see how each piece comes into play.&lt;/p&gt;
&lt;p&gt;To start off, nbviewer is two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;the &lt;a href="https://github.com/jupyter/nbviewer"&gt;web application&lt;/a&gt; that finds notebooks on the internet and renders them, and&lt;/li&gt;
&lt;li&gt;the specific deployment of nbviewer serving &lt;a href="https://nbviewer.jupyter.org"&gt;https://nbviewer.jupyter.org&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can even roll your own nbviewer by grabbing the public docker image and running:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker pull jupyter/nbviewer
docker run -p 5000:5000 -it jupyter/nbviewer
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can then start rendering notebooks left and right. However, there’s a limit to how scalable such a basic deployment can be.&lt;/p&gt;
&lt;h2 id="rendering-a-notebook"&gt;Rendering a notebook&lt;/h2&gt;
&lt;p&gt;Rendering a notebook involves two major steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;download the notebook from somewhere on the web&lt;/li&gt;
&lt;li&gt;render it to HTML with &lt;a href="https://github.com/jupyter/nbconvert"&gt;nbconvert&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each step can take a bit of time. Fetching and rendering a notebook start to finish can take as much few seconds if the notebook is big and/or the upstream server is busy or slow. We use tornado’s async APIs to handle concurrent requests. Fetching notebooks from the web is handled via a subclass of &lt;a href="http://www.tornadoweb.org/en/stable/httpclient.html#tornado.httpclient.AsyncHTTPClient"&gt;AsyncHTTPClient&lt;/a&gt; and rendering is done in the background via &lt;code&gt;concurrent.futures&lt;/code&gt;. A single nbviewer instance can handle quite a few concurrent visitors, depending on how many rendering threads are available.&lt;/p&gt;
&lt;h2 id="caching"&gt;Caching&lt;/h2&gt;
&lt;p&gt;Most notebooks on the web don’t change very often, so going through the whole process when you just downloaded and rendered the same notebook a minute ago is a bit of a waste. &lt;strong&gt;This is where caching comes in.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;nbviewer supports caching via a simple in-memory cache or &lt;a href="https://memcached.org"&gt;memcache&lt;/a&gt; at two levels:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Every time nbviewer completes a render, it caches the result for a period of time (usually 10 minutes to an hour, depending on render time), so that a very popular notebook doesn’t consume too many resources. This is unconditional, and doesn’t take into account whether the original notebook has been updated. If it’s in the cache, it gets reused.&lt;/li&gt;
&lt;li&gt;nbviewer &lt;em&gt;separately&lt;/em&gt; caches upstream requests. When nbviewer asks an external service for anything, whether it’s a regular web server or GitHub’s API, the response is saved for a time. The next time the same resource is requested, nbviewer checks for a cached response. Unlike the full render cache, this cache doesn’t prevent a new upstream request. Instead, we check the headers of the cached reply (&lt;code&gt;ETag&lt;/code&gt; and &lt;code&gt;Last-Modified&lt;/code&gt;). nbviewer then takes the content of those headers to populate &lt;code&gt;If-None-Match&lt;/code&gt; and &lt;code&gt;If-Modified-Since&lt;/code&gt; headers before sending a new request. The server can then check these headers and return an empty reply with status 304, telling nbviewer that it should reuse the cached response instead of running the full process of handling the request again. Critically for nbviewer, when the GitHub API responds with 304, the rate limit &lt;a href="https://developer.github.com/v3/#conditional-requests"&gt;is not consumed&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="our-nbviewer-deployment"&gt;Our nbviewer deployment&lt;/h2&gt;
&lt;p&gt;To deal with the traffic our public nbviewer instance gets, we have deployed nbviewer with a few extra elements. The moving parts:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;we have multiple nbviewer instances on multiple servers deployed. Right now, there are two nbviewer instances (running in docker) on each of two servers running on &lt;a href="https://rackspace.com"&gt;rackspace&lt;/a&gt;, making four total instances.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://memcached.org"&gt;memcache&lt;/a&gt;, also running in docker, with separate instances on each server&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fastly.com"&gt;fastly&lt;/a&gt; sits in front of our nbviewer instances and load-balances requests across them, performing health checks, etc. It also has its own cache layer.&lt;/li&gt;
&lt;li&gt;Finally, &lt;a href="https://cloudflare.com"&gt;cloudflare&lt;/a&gt; is out front, handles SSL termination, and a small amount of caching&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Four instances is quite a bit more than we need for the load nbviewer gets, but having it spread out lets us do things like reboot or upgrade servers without any downtime.&lt;/p&gt;
&lt;h2 id="what-happened"&gt;What happened?&lt;/h2&gt;
&lt;p&gt;So, why were we seeing the GitHub rate limit hit so often? Did nbviewer just get too popular? Sadly, no. How did we figure out what happened? &lt;em&gt;To the logs!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Fun fact: until now, we were using docker’s default json logging, and nbviewer was producing 50GB of logs each month. &lt;code&gt;docker logs --tail&lt;/code&gt; does not behave super well with a 30GB log file. I started digging through the logs to see what was going on, but quickly realized that this wasn’t working very well. To try to get a better sense of things, I started by hooking up our logs to &lt;a href="https://loggly.com"&gt;Loggly&lt;/a&gt; via syslog. Loggly lets me start to extract some metrics, such as status, URL, IP address, and user-agent of failing requests.&lt;/p&gt;
&lt;h2 id="what-did-we-learn"&gt;What did we learn?&lt;/h2&gt;
&lt;p&gt;One of the first things I did was group incoming requests by IP address, to see if anything stood out. This is what I saw:&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://jupyter.org/blog/posts/2017/nbviewer-and-githubs-rate-limit/images/g003-Screen-Shot-2017-02-24-at-19.30.41.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;So about two thirds of requests were coming from just two ip addresses! That’s not right. Checking for user-agent showed something else:&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://jupyter.org/blog/posts/2017/nbviewer-and-githubs-rate-limit/images/g004-Screen-Shot-2017-02-24-at-19.30.14.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;Two bots, &lt;a href="http://ahrefs.com/robot/"&gt;Ahrefs&lt;/a&gt; and &lt;a href="https://moz.com/researchtools/ose/dotbot"&gt;DotBot&lt;/a&gt; were accounting for almost three quarters of requests. That’s too much.&lt;/p&gt;
&lt;h2 id="what-we-did-about-it"&gt;What we did about it&lt;/h2&gt;
&lt;p&gt;The very first thing we did was to block the two IP addresses that were driving way too much traffic. This was done in the fastly layer, sending a custom &lt;a href="https://httpstatuses.com/429"&gt;429 error&lt;/a&gt; for any request from these IPs:&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://jupyter.org/blog/posts/2017/nbviewer-and-githubs-rate-limit/images/001-1_lHjj_gdpvFSHMQD1Y0GETQ.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;This improved availability a bunch, and would probably have been plenty on its own to resolve the current situation. Of course, it wouldn’t be enough to prevent similar situations in the future. Since the problem was caused by robots, the next place to look was &lt;a href="http://www.robotstxt.org"&gt;robots.txt&lt;/a&gt;. Our robots.txt had been the most permissive possible:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;User-agent: *
Disallow:
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;explicitly allowing any bot. Turns out we were being too nice. We’ve now updated our robots.txt to tell all robots to slow way down, and instructed the two overzealous bots that they are not welcome:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;User-agent: *
Crawl-delay: 10

User-agent: dotbot
Disallow: /
User-agent: AhrefsBot
Disallow: /
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Again, this should be enough to resolve the issue &lt;strong&gt;if these and all other bots are honorable&lt;/strong&gt; and respect the robots.txt.&lt;/p&gt;
&lt;p&gt;However, we would prefer to not rely on the kindness of strange robots.&lt;/p&gt;
&lt;h2 id="better-caching-and-better-use-of-the-cache"&gt;Better caching and better use of the cache&lt;/h2&gt;
&lt;p&gt;As mentioned earlier, we cache the responses we get from upstream sources and use those to reduce the load on the GitHub API and other sources of notebooks. However, we were not making the best use of that cache. First of all, the responses were only being cached for two hours, which means this caching only help us for relatively short bursts of activity (e.g. a popular notebook being shared on social media). This certainly happens, but does not represent a large fraction of typical traffic. Since the way we check for updates with &lt;code&gt;If-Not-Modified&lt;/code&gt; has no risk of stale results, we &lt;a href="https://github.com/jupyter/nbviewer/pull/674/files"&gt;changed this&lt;/a&gt; to cache upstream responses forever, until memcache pushes them out when it gets full. Since most public notebooks don’t change often, we should get a cache hit almost every time.&lt;/p&gt;
&lt;p&gt;The next change we made was an improvement to behavior when the rate limit is hit. Previously, if we had a cached upstream response and GitHub told us there was an update that we couldn’t fetch due to the rate limit, we would serve the 503 error page. But we had valid data right there! Now, if we hit the rate limit but happen to have an old version of the data, we &lt;a href="https://github.com/jupyter/nbviewer/pull/674/files#diff-a9fe8780e6ab9856bafdf9c997a70c56R82"&gt;work with the stale version&lt;/a&gt; rather than serving an error page. Something’s better than nothing, right? This applies to any kind of error fetching from an upstream server, not just failures due to hitting the GitHub rate limit.&lt;/p&gt;
&lt;p&gt;So we are now much less likely to hit the rate limit in the first place, and much more likely to be able to serve &lt;em&gt;something&lt;/em&gt; if and when we hit it.&lt;/p&gt;
&lt;h2 id="our-own-rate-limit"&gt;Our own rate limit&lt;/h2&gt;
&lt;p&gt;The last thing we did to help limit the consumption of resources by a small number of actors was to &lt;a href="https://github.com/jupyter/nbviewer/pull/675"&gt;implement our own rate limit&lt;/a&gt; in nbviewer. This is a pretty simple rate limit, where we identify a source by the combination of ip address and user-agent. Currently, this limit is set at 60 requests in ten minutes. We only count non-cached requests against the limit, so refreshing the same few pages a bunch won’t consume the limit.&lt;/p&gt;
&lt;p&gt;Like the cache, the rate limit is actually implemented with memcache. When we see a request from a given source, we initialize a counter to memcache with an expiry of our rate limit window:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;key = cache_key(request)  # combines ip and user-agent
new_visitor = memcache.add(key, 1, rate_limit_window)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If it’s not a new visitor, we increment the counter with memcache’s atomic increment operation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;if not new_visitor:
    count = memcache.incr(key)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The last thing to do is to check if the rate limit is exceeded, and return an HTTP 429 error instead of proceeding with the render:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;if count &amp;gt; rate_limit:
    raise HTTPError(429, &amp;quot;Rate limit exceeded...&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If you hit the rate limit you will see:&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://jupyter.org/blog/posts/2017/nbviewer-and-githubs-rate-limit/images/g006-Screen-Shot-2017-02-27-at-12.01.08.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;From looking at our logs, we can see that this rate limit has blocked ~800 requests since deploying it a few days ago. More than 600 of these were from &lt;a href="https://domainreanimator.com"&gt;a bot&lt;/a&gt; that apparently doesn’t respect robots.txt. A few dozen were made from phantomjs, presumably some manual scraping that made a few too many fetches. That leaves a few probably-real humans who hit the rate limit, but no IP has hit it often or for a sustained period of time.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/jupyter/nbviewer/issues"&gt;Let us know&lt;/a&gt; if the rate limiting is causing an issue for you. We will keep an eye on the limits, and continue to tune both the limits and limiting conditions to find the best balance.&lt;/p&gt;
&lt;h2 id="monitoring"&gt;Monitoring&lt;/h2&gt;
&lt;p&gt;We run our instances with the &lt;a href="https://docs.newrelic.com/docs/agents/python-agent/getting-started/introduction-new-relic-python"&gt;New Relic&lt;/a&gt; Python agent, which gets us some basic health monitoring. We also have some monitoring and alerts in our Rackspace account to notify us when things go down.&lt;/p&gt;
&lt;p&gt;We’ve added the error rate tracked by New Relic and a &lt;a href="https://gist.github.com/179f92a45e18e865ac4b95578dce949c"&gt;new metric&lt;/a&gt; that tracks how much of our GitHub API limit is available to our &lt;a href="http://status.jupyter.org"&gt;public status page&lt;/a&gt;, so you can see how healthy nbviewer is at any given time.&lt;/p&gt;
&lt;h2 id="one-last-thing"&gt;One last thing&lt;/h2&gt;
&lt;p&gt;After confirming that everything was working well, I did a final deploy on a Friday evening, and moved on. Then this came in:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://twitter.com/williamrthomas/status/835255588116447232"&gt;https://twitter.com/williamrthomas/status/835255588116447232&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Turns out&lt;/em&gt; there had been an update to the New Relic Python agent since the last deploy, which introduced this tiny off-by-one error:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;if len(args) &amp;gt; 0:
    args = list(args)
    args[1] = wrapped_callback
    # what if len(args) == 1 ?!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;(that &lt;code&gt;&amp;gt; 0&lt;/code&gt; should be &lt;code&gt;&amp;gt; 1&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;This little bug just so happens to prevent nbviewer from making any outgoing requests :shrug:. This served as a friendly reminder that it is a good idea to pin your dependencies when deploying applications so that updates only happen when you decide they should.&lt;/p&gt;
</content><category term="nbviewer"/></entry><entry><title>Security release: Jupyter Notebook 4.3.1</title><link href="https://jupyter.org/blog/posts/2016/security-release-jupyter-notebook-4-3-1/" rel="alternate"/><published>2016-12-22T02:25:00+00:00</published><updated>2017-08-28T18:20:00+00:00</updated><author><name>Min RK</name></author><id>tag:jupyter.org,2016-12-22:/blog/posts/2016/security-release-jupyter-notebook-4-3-1/</id><summary type="html">&lt;p&gt;We have just released Jupyter Notebook 4.3.0 and 4.3.1 with some important security fixes.&lt;/p&gt;</summary><content type="html">&lt;p&gt;We have just released Jupyter Notebook 4.3.0 and 4.3.1 with some important security fixes.&lt;/p&gt;
&lt;p&gt;You can check out &lt;a href="https://jupyter-notebook.readthedocs.io/en/stable/changelog.html#release-4-3-1"&gt;the changelog&lt;/a&gt; for more details on the many fixes and improvements. I’m going to focus on the security changes in this post.&lt;/p&gt;
&lt;h2 id="430-token-authentication"&gt;4.3.0: Token authentication&lt;/h2&gt;
&lt;p&gt;The biggest change in 4.3 is the addition of token-based authentication, which is &lt;strong&gt;enabled by default&lt;/strong&gt;. Several of the security issues the notebook has had over the years would have been avoided if the notebook ran with authentication by default. We did not do this for a long time, because the process of enabling and entering a password on a localhost-only web application was deemed too cumbersome, and the risks too small, given that it listened only on localhost. With automatic token authentication, I think we have a solution that should be convenient enough for most users that we can now switch to enabling authentication by default.&lt;/p&gt;
&lt;p&gt;If you use Jupyter in its default configuration, which is to open your browser automatically when you type &lt;code&gt;jupyter notebook&lt;/code&gt;, this shouldn’t have much impact on your daily work. In this case, a one-time token is generated, and your browser opens with this in the URL. That token is immediately consumed, and your browser will be authenticated, storing the information in a cookie for future requests.&lt;/p&gt;
&lt;p&gt;If you access the notebook without authentication, you will see this page:&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://jupyter.org/blog/posts/2016/security-release-jupyter-notebook-4-3-1/images/g001-Screen-Shot-2016-12-20-at-16-13-37.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;This may happen if you switch browsers, or use the notebook with the &lt;code&gt;--no-browser&lt;/code&gt; option, in which you will need to enter the token manually the first time you connect to a server with a given browser. This can be done at the login page above, or via a URL containing the token. The notebook server logs this URL when it starts, so you can paste the URL into your browser:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[C 16:11:17.581 NotebookApp]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;Copy/paste this URL into your browser when you connect for the first time,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;to login with a token&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;//localhost:8888/?token=20abd368...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After you have logged in with this token just once, a cookie is set and used thereafter, even across restarts of the notebook server. So you shouldn’t need to deal with the token after the first time you have used the notebook on a given machine, until/unless you switch browsers or launch new notebook servers &lt;em&gt;on different ports&lt;/em&gt;. This does mean that cookies are required for authenticated access to notebooks, and clearing cookies means needing to login again.&lt;/p&gt;
&lt;p&gt;At any time, you can see all of your current notebook servers with their token-authenticated URLs by running the &lt;code&gt;jupyter notebook list&lt;/code&gt; command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;jupyter&lt;span class="w"&gt; &lt;/span&gt;notebook&lt;span class="w"&gt; &lt;/span&gt;list
Currently&lt;span class="w"&gt; &lt;/span&gt;running&lt;span class="w"&gt; &lt;/span&gt;servers:
http://localhost:8888/?token&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1234&lt;/span&gt;...&lt;span class="w"&gt; &lt;/span&gt;::&lt;span class="w"&gt; &lt;/span&gt;/Users/you/notebooks
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We didn’t communicate the information about token authentication very well in 4.3.0, and I apologize for that. 4.3.1 should make the token behavior much clearer, with more information in both the login form and the notebook log output about how to use the tokens and when you need them. You can see &lt;a href="http://jupyter-notebook.readthedocs.io/en/latest/security.html"&gt;the docs&lt;/a&gt; for more details. We’re always open to improvements, so please &lt;a href="https://github.com/jupyter/notebook/issues"&gt;open an Issue&lt;/a&gt; if you are having trouble.&lt;/p&gt;
&lt;h2 id="431"&gt;4.3.1&lt;/h2&gt;
&lt;p&gt;4.3.1 follows shortly after 4.3.0, with a fix for a CSRF vulnerability (&lt;strong&gt;CVE-2016-9971&lt;/strong&gt;), affecting users of the Firefox or Microsoft (IE, Edge) browsers, and any other browsers that do not set the Origin header on cross-site forms. WebKit and Blink based browsers like Safari and Chrome are not affected. The effect of this vulnerability is the ability of forms on malicious websites visited by the user to spawn new kernels and create empty, untitled files on the user’s notebook server. CORS protections already in place prevent further access to these kernels and editing the content of any files.&lt;/p&gt;
&lt;p&gt;Summary:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CVE-2016-9971&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Affected notebook versions:&lt;/strong&gt; all notebook releases &amp;lt; 4.3.1&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Affected users:&lt;/strong&gt; Users of Firefox or Microsoft browsers, or other browsers that do not set the Origin header on forms.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Results:&lt;/strong&gt; Visiting malicious pages with these browsers could result in unauthorized launch of new kernels or creation of empty, untitled files on the user’s notebook server&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Upgrade to notebook-4.3.1, which uses an &lt;code&gt;xsrf&lt;/code&gt; token via cookies to validate the origin of forms. See &lt;a href="http://www.tornadoweb.org/en/stable/guide/security.html#cross-site-request-forgery-protection"&gt;tornado docs&lt;/a&gt; for details.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="update-practical-effects-of-xsrf-for-extensions"&gt;Update: practical effects of xsrf for extensions&lt;/h2&gt;
&lt;p&gt;This note was omitted when first posted.&lt;/p&gt;
&lt;p&gt;The use of the xsrf token affects existing extensions / etc. that work directly with the notebook server via REST API. To make requests to the rest API, the &lt;code&gt;_xsrf&lt;/code&gt; cookie must be sent back to the server in the &lt;code&gt;X-XSRF-Token&lt;/code&gt; header of API requests, for example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_get_cookie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tornado&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;www&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tornadoweb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;en&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;stable&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;guide&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;security&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cookie&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;b&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;=([^;]*)&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_add_xsrf_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Adds&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;xsrf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;jquery&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ajax&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;xsrf_token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_get_cookie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;_xsrf&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xsrf_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;X-XSRFToken&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;xsrf_token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ajax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;like&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$.&lt;/span&gt;&lt;span class="n"&gt;ajax&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ensure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;xsrf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;added&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_add_xsrf_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$.&lt;/span&gt;&lt;span class="n"&gt;ajax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This code will also work for any previous version of the notebook, where the xsrf cookie will be undefined and ignored.&lt;/p&gt;
&lt;p&gt;If you’re using &lt;code&gt;fetch&lt;/code&gt;, you’ll need to set &lt;code&gt;credentials: true&lt;/code&gt; as well so that the &lt;code&gt;Cookie&lt;/code&gt; header is set.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;xsrfToken&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_get_cookie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;_xsrf&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://127.0.0.1:8888/api/contents/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;POST&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;include&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;X-XSRFToken&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;xsrfToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Content-Type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;application/json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Test.py&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
</content><category term="Jupyter Notebook"/><category term="releases"/><category term="security"/></entry></feed>