Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-jupyter-server for
openSUSE:Factory checked in at 2026-06-30 15:13:35
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-jupyter-server (Old)
and /work/SRC/openSUSE:Factory/.python-jupyter-server.new.11887 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-jupyter-server"
Tue Jun 30 15:13:35 2026 rev:48 rq:1362580 version:2.20.0
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-jupyter-server/python-jupyter-server.changes
2026-06-08 14:28:54.385984049 +0200
+++
/work/SRC/openSUSE:Factory/.python-jupyter-server.new.11887/python-jupyter-server.changes
2026-06-30 15:13:53.864984712 +0200
@@ -1,0 +2,23 @@
+Tue Jun 30 06:53:27 UTC 2026 - Daniel Garcia <[email protected]>
+
+- Update to 2.20.0 (CVE-2026-44727, bsc#1269630):
+ # Security fixes
+ - CVE-2026-44727 GHSA-fcw5-x6j4-ccmp
+ # Enhancements made
+ - Fix confusing terminal output when using ServerApp.ip=0.0.0.0
+ #1643
+ - Add a toggle to enable curve encryption for all kernels that
+ support it #1638
+ # Bugs fixed
+ - Grab the port from bind_sockets in case its different #1651
+ # Maintenance and upkeep improvements
+ - Fix test_authorizer having a spurious comma in params #1664
+ - Add a reminder to merge GHSA before release #1659
+ - Exclude problematic pywinpty 3.0.4 version #1658
+ - ci: explicitly pass base-setup inputs to fix strict validation
+ failures #1626
+ # Documentation improvements
+ - Align docs for curve encryption with latest JEP version #1660
+ - Remove PGP key from docs #1653
+
+-------------------------------------------------------------------
Old:
----
jupyter_server-2.19.0.tar.gz
New:
----
jupyter_server-2.20.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-jupyter-server.spec ++++++
--- /var/tmp/diff_new_pack.Zxlqwo/_old 2026-06-30 15:13:55.165028732 +0200
+++ /var/tmp/diff_new_pack.Zxlqwo/_new 2026-06-30 15:13:55.177029139 +0200
@@ -33,7 +33,7 @@
%{?sle15_python_module_pythons}
Name: python-jupyter-server%{psuffix}
-Version: 2.19.0
+Version: 2.20.0
Release: 0
Summary: The backend to Jupyter web applications
License: BSD-3-Clause
++++++ jupyter_server-2.19.0.tar.gz -> jupyter_server-2.20.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server-2.19.0/.github/workflows/prep-release.yml
new/jupyter_server-2.20.0/.github/workflows/prep-release.yml
--- old/jupyter_server-2.19.0/.github/workflows/prep-release.yml
2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server-2.20.0/.github/workflows/prep-release.yml
2020-02-02 01:00:00.000000000 +0100
@@ -23,6 +23,10 @@
description: "Use PRs with activity since the last stable git tag"
required: false
type: boolean
+ security_reminder:
+ description: "Recommended: check with a security maintainer if a patch
is waiting to be merged.
https://github.com/jupyter-server/jupyter_server/security/advisories?state=draft"
+ required: false
+ type: boolean
jobs:
prep_release:
runs-on: ubuntu-latest
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server-2.19.0/.github/workflows/python-tests.yml
new/jupyter_server-2.20.0/.github/workflows/python-tests.yml
--- old/jupyter_server-2.19.0/.github/workflows/python-tests.yml
2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server-2.20.0/.github/workflows/python-tests.yml
2020-02-02 01:00:00.000000000 +0100
@@ -29,6 +29,10 @@
uses: actions/checkout@v6
- name: Base Setup
uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
+ with:
+ python_package_manager: pip
+ dependency_type: standard
+ python_version: ${{ matrix.python-version }}
- name: Install nbconvert dependencies on Linux
if: startsWith(runner.os, 'Linux')
run: |
@@ -49,6 +53,9 @@
steps:
- uses: actions/checkout@v6
- uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
+ with:
+ python_package_manager: pip
+ dependency_type: standard
- name: Install Dependencies
run: |
sudo apt-get update
@@ -70,6 +77,9 @@
steps:
- uses: actions/checkout@v6
- uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
+ with:
+ python_package_manager: pip
+ dependency_type: standard
- name: Run Linters
run: |
pipx run pre-commit run --all-files
@@ -86,6 +96,9 @@
steps:
- uses: actions/checkout@v6
- uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
+ with:
+ python_package_manager: pip
+ dependency_type: standard
- name: Install the Python dependencies for the examples
run: |
pip install -e ".[test]"
@@ -103,6 +116,7 @@
- uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
with:
dependency_type: minimum
+ python_package_manager: pip
- name: Run the unit tests
run: |
hatch -vv run test:nowarn || hatch -v run test:nowarn --lf
@@ -116,6 +130,7 @@
- uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
with:
dependency_type: pre
+ python_package_manager: pip
# pyO3 dependencies can't build on dev python
python_version: "3.14"
- name: Run the tests
@@ -129,6 +144,9 @@
steps:
- uses: actions/checkout@v6
- uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
+ with:
+ python_package_manager: pip
+ dependency_type: standard
- name: Create NFS file system
run: |
sudo apt-get install -y nfs-kernel-server
@@ -147,6 +165,9 @@
steps:
- uses: actions/checkout@v6
- uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
+ with:
+ python_package_manager: pip
+ dependency_type: standard
- uses: jupyterlab/maintainer-tools/.github/actions/make-sdist@v1
test_sdist:
@@ -156,6 +177,9 @@
timeout-minutes: 20
steps:
- uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
+ with:
+ python_package_manager: pip
+ dependency_type: standard
- uses: jupyterlab/maintainer-tools/.github/actions/test-sdist@v1
with:
package_spec: -vv .
@@ -168,6 +192,9 @@
uses: actions/checkout@v6
- name: Base Setup
uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
+ with:
+ python_package_manager: pip
+ dependency_type: standard
- name: Install Dependencies
run: |
pip install -e .
@@ -183,6 +210,9 @@
steps:
- uses: actions/checkout@v6
- uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
+ with:
+ python_package_manager: pip
+ dependency_type: standard
- uses: jupyterlab/maintainer-tools/.github/actions/check-links@v1
with:
ignore_links: "https://www.npmjs.com"
@@ -197,6 +227,10 @@
steps:
- uses: actions/checkout@v6
- uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
+ with:
+ python_package_manager: pip
+ dependency_type: standard
+ python_version: ${{ matrix.python-version }}
- name: Run the tests
run: hatch -v run cov:integration
- uses: jupyterlab/maintainer-tools/.github/actions/upload-coverage@v1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server-2.19.0/CHANGELOG.md
new/jupyter_server-2.20.0/CHANGELOG.md
--- old/jupyter_server-2.19.0/CHANGELOG.md 2020-02-02 01:00:00.000000000
+0100
+++ new/jupyter_server-2.20.0/CHANGELOG.md 2020-02-02 01:00:00.000000000
+0100
@@ -4,6 +4,42 @@
<!-- <START NEW CHANGELOG ENTRY> -->
+## 2.20.0
+
+([Full
Changelog](https://github.com/jupyter-server/jupyter_server/compare/v2.19.0...333e700119ee0bcc0b5fcd4c158213d7c275c778))
+
+### Enhancements made
+
+- Fix confusing terminal output when using ServerApp.ip=0.0.0.0
[#1643](https://github.com/jupyter-server/jupyter_server/pull/1643)
([@Yann-P](https://github.com/Yann-P), [@minrk](https://github.com/minrk))
+- Add a toggle to enable curve encryption for all kernels that support it
[#1638](https://github.com/jupyter-server/jupyter_server/pull/1638)
([@krassowski](https://github.com/krassowski),
[@Carreau](https://github.com/Carreau),
[@ianthomas23](https://github.com/ianthomas23),
[@minrk](https://github.com/minrk))
+
+### Bugs fixed
+
+- Grab the port from `bind_sockets` in case its different
[#1651](https://github.com/jupyter-server/jupyter_server/pull/1651)
([@choldgraf](https://github.com/choldgraf),
[@krassowski](https://github.com/krassowski))
+
+### Maintenance and upkeep improvements
+
+- Fix `test_authorizer` having a spurious comma in params
[#1664](https://github.com/jupyter-server/jupyter_server/pull/1664)
([@krassowski](https://github.com/krassowski))
+- Add a reminder to merge GHSA before release
[#1659](https://github.com/jupyter-server/jupyter_server/pull/1659)
([@Yann-P](https://github.com/Yann-P), [@Carreau](https://github.com/Carreau))
+- Exclude problematic `pywinpty` 3.0.4 version
[#1658](https://github.com/jupyter-server/jupyter_server/pull/1658)
([@krassowski](https://github.com/krassowski))
+- ci: explicitly pass base-setup inputs to fix strict validation failures
[#1626](https://github.com/jupyter-server/jupyter_server/pull/1626)
([@Carreau](https://github.com/Carreau), [@Copilot](https://github.com/Copilot))
+
+### Documentation improvements
+
+- Align docs for curve encryption with latest JEP version
[#1660](https://github.com/jupyter-server/jupyter_server/pull/1660)
([@krassowski](https://github.com/krassowski),
[@Carreau](https://github.com/Carreau))
+- Remove PGP key from docs
[#1653](https://github.com/jupyter-server/jupyter_server/pull/1653)
([@Yann-P](https://github.com/Yann-P),
[@krassowski](https://github.com/krassowski))
+
+### Contributors to this release
+
+The following people contributed discussions, new ideas, code and
documentation contributions, and review.
+See [our definition of
contributors](https://github-activity.readthedocs.io/en/latest/use/#how-does-this-tool-define-contributions-in-the-reports).
+
+([GitHub contributors page for this
release](https://github.com/jupyter-server/jupyter_server/graphs/contributors?from=2026-05-29&to=2026-06-17&type=c))
+
+@Carreau
([activity](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3ACarreau+updated%3A2026-05-29..2026-06-17&type=Issues))
| @choldgraf
([activity](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Acholdgraf+updated%3A2026-05-29..2026-06-17&type=Issues))
| @Copilot
([activity](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3ACopilot+updated%3A2026-05-29..2026-06-17&type=Issues))
| @ianthomas23
([activity](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Aianthomas23+updated%3A2026-05-29..2026-06-17&type=Issues))
| @krassowski
([activity](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Akrassowski+updated%3A2026-05-29..2026-06-17&type=Issues))
| @minrk
([activity](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Aminrk+updated%3A2026-05-29..2026-06-17&type=Issues))
| @Yann-P ([activity](https://github.com/search?q
=repo%3Ajupyter-server%2Fjupyter_server+involves%3AYann-P+updated%3A2026-05-29..2026-06-17&type=Issues))
+
+<!-- <END NEW CHANGELOG ENTRY> -->
+
## 2.19.0
([Full
Changelog](https://github.com/jupyter-server/jupyter_server/compare/v2.18.2...664e2255c71efe963f397b9f803dbcf503b5a920))
@@ -36,8 +72,6 @@
@Carreau
([activity](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3ACarreau+updated%3A2026-05-06..2026-05-29&type=Issues))
| @ianthomas23
([activity](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Aianthomas23+updated%3A2026-05-06..2026-05-29&type=Issues))
| @krassowski
([activity](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Akrassowski+updated%3A2026-05-06..2026-05-29&type=Issues))
| @minrk
([activity](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Aminrk+updated%3A2026-05-06..2026-05-29&type=Issues))
| @MUFFANUJ
([activity](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3AMUFFANUJ+updated%3A2026-05-06..2026-05-29&type=Issues))
| @terminalchai
([activity](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Aterminalchai+updated%3A2026-05-06..2026-05-29&type=Issues))
| @Zsailer ([activity](https://github.com
/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3AZsailer+updated%3A2026-05-06..2026-05-29&type=Issues))
-<!-- <END NEW CHANGELOG ENTRY> -->
-
## 2.18.2
([Full
Changelog](https://github.com/jupyter-server/jupyter_server/compare/v2.18.1...0d829f2c35a481c3b24ecbe1e25a6f79954e88f2))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server-2.19.0/PKG-INFO
new/jupyter_server-2.20.0/PKG-INFO
--- old/jupyter_server-2.19.0/PKG-INFO 2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server-2.20.0/PKG-INFO 2020-02-02 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: jupyter_server
-Version: 2.19.0
+Version: 2.20.0
Summary: The backend—i.e. core services, APIs, and REST endpoints—to Jupyter
web applications.
Project-URL: Homepage, https://jupyter-server.readthedocs.io
Project-URL: Documentation, https://jupyter-server.readthedocs.io
@@ -63,7 +63,7 @@
Requires-Dist: overrides>=5.0; python_version < '3.12'
Requires-Dist: packaging>=22.0
Requires-Dist: prometheus-client>=0.9
-Requires-Dist: pywinpty>=2.0.1; os_name == 'nt'
+Requires-Dist: pywinpty!=3.0.4,>=2.0.1; os_name == 'nt'
Requires-Dist: pyzmq>=24
Requires-Dist: send2trash>=1.8.2
Requires-Dist: terminado>=0.8.3
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server-2.19.0/RELEASE.md
new/jupyter_server-2.20.0/RELEASE.md
--- old/jupyter_server-2.19.0/RELEASE.md 2020-02-02 01:00:00.000000000
+0100
+++ new/jupyter_server-2.20.0/RELEASE.md 2020-02-02 01:00:00.000000000
+0100
@@ -7,6 +7,8 @@
Note that we must use manual versions since Jupyter Releaser does not
yet support "next" or "patch" when dev versions are used.
+Note about Security Advisories: some patches are developed in private hidden
forks that only the [security team](https://github.com/jupyter/security) can
access; please check with them that no patch is waiting be be merged before
releasing.
+
## Manual Release
To create a manual release, perform the following steps:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server-2.19.0/docs/source/operators/ipython_security.asc
new/jupyter_server-2.20.0/docs/source/operators/ipython_security.asc
--- old/jupyter_server-2.19.0/docs/source/operators/ipython_security.asc
2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server-2.20.0/docs/source/operators/ipython_security.asc
1970-01-01 01:00:00.000000000 +0100
@@ -1,52 +0,0 @@
------BEGIN PGP PUBLIC KEY BLOCK-----
-Version: GnuPG v2.0.22 (GNU/Linux)
-
-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
-5vmLc+Ibz+eT+rM=
-=XVDe
------END PGP PUBLIC KEY BLOCK-----
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server-2.19.0/docs/source/operators/security.rst
new/jupyter_server-2.20.0/docs/source/operators/security.rst
--- old/jupyter_server-2.19.0/docs/source/operators/security.rst
2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server-2.20.0/docs/source/operators/security.rst
2020-02-02 01:00:00.000000000 +0100
@@ -366,6 +366,107 @@
``@authenticated`` decorator for authentication (from ``tornado.web``).
+Kernel transport encryption
+---------------------------
+
+.. versionadded:: 2.20
+
+By default, ZMQ sockets used to communicate with kernels (shell, IOPub, stdin,
+control, heartbeat) are bound to TCP ports with no transport-level encryption.
+Any process on the same host that can reach those ports can connect and read
+messages, including all IOPub output.
+
+`CurveZMQ <https://rfc.zeromq.org/spec/26/>`__ adds elliptic-curve
+encryption and authentication directly at the ZMQ transport layer.
+When enabled, the ``KernelManager`` generates a keypair, writes the keys into
+the kernel's connection file, and configures all sockets as a CurveZMQ server.
+Clients must present the correct server public key to connect; unauthenticated
+connections are silently dropped before any data is delivered.
+
+.. note::
+
+ CurveZMQ is only available when pyzmq was compiled against a libzmq that
+ includes libsodium. You can verify this with::
+
+ python -c "import zmq; print(zmq.has('curve'))"
+
+ If this prints ``False``, the ``transport_encryption`` setting has no
+ effect and attempts to set it to ``'auto'`` or ``'required'`` will raise
+ a configuration error.
+
+The ``transport_encryption`` setting
+*************************************
+
+Transport encryption is controlled by the
``KernelManager.transport_encryption``
+traitlet, which accepts three values:
+
+``'disabled'`` (default)
+ No CurveZMQ keys are generated. All kernel sockets are unencrypted.
+
+``'auto'``
+ Keys are generated **only when the kernelspec declares support** via
+ ``metadata.supported_encryption: ['curve']``. Kernelspecs that do not
+ declare this field are started without encryption, so the setting is safe
+ to enable globally without breaking existing kernels.
+
+``'required'``
+ Keys are always generated. Startup fails with a ``RuntimeError`` if the
+ kernelspec does not declare ``metadata.supported_encryption: ['curve']``,
+ so kernels that have not been updated to handle the connection-file keys
+ are never started unencrypted.
+
+.. note::
+
+ ``transport_encryption`` applies to both the ``tcp`` and ``ipc``
+ transports. IPC sockets also rely on filesystem permissions for access
+ control, but CurveZMQ can be layered on top for defense in depth.
+
+To enable encryption globally for all kernels that support it, add the
+following to your :file:`jupyter_server_config.py`:
+
+.. sourcecode:: python
+
+ c.KernelManager.transport_encryption = "auto"
+
+To enforce encryption and prevent unencrypted kernels from starting:
+
+.. sourcecode:: python
+
+ c.KernelManager.transport_encryption = "required"
+
+Kernelspec requirements
+***********************
+
+A kernel must declare CurveZMQ support in its :file:`kernel.json` before the
+``KernelManager`` will provision keys for it:
+
+.. sourcecode:: json
+
+ {
+ "argv": ["python", "-m", "ipykernel_launcher", "-f",
"{connection_file}"],
+ "display_name": "Python 3",
+ "language": "python",
+ "metadata": {
+ "supported_encryption": ["curve"]
+ }
+ }
+
+When ``transport_encryption`` is ``'auto'``, kernelspecs without this field are
+started normally without encryption. When it is ``'required'``, their startup
+is refused.
+
+The kernel itself must read ``curve_publickey`` and ``curve_secretkey`` from
+the connection file and apply them to its ZMQ sockets. Kernels based on
+ipykernel 7.3 do this automatically when the fields are present.
+
+.. note::
+
+ When updating a previously installed kernel to a version that supports
encryption
+ you may need to re-install the kernelspec or manually add the
``supported_encryption``
+ metadata field. If you subsequently decide to downgrade, you will need to
+ remove this field as otherwise the kernel will silently fail to connect.
+
+
Security in notebook documents
==============================
@@ -453,6 +554,22 @@
These two methods simply load the notebook, compute a new signature, and add
that signature to the user's database.
+Content-Security-Policy for nbconvert
+--------------------------------------
+
+When notebooks are rendered via nbconvert (``/nbconvert/`` endpoints),
+the server adds a ``sandbox allow-scripts`` directive to the
+``Content-Security-Policy`` header by default. This confines any
+JavaScript in the rendered output to a unique origin, preventing it
+from interacting with the Jupyter server.
+
+This behavior is controlled by
:attr:`~jupyter_server.serverapp.ServerApp.nbconvert_csp_sandbox`:
+
+.. sourcecode:: python
+
+ # jupyter_server_config.py
+ c.ServerApp.nbconvert_csp_sandbox = True # default
+
Reporting security issues
-------------------------
@@ -460,9 +577,6 @@
code to properly implement the model described here, or a failure of the
model itself, please report it to [email protected].
-If you prefer to encrypt your security reports,
-you can use :download:`this PGP public key <ipython_security.asc>`.
-
Affected use cases
------------------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server-2.19.0/jupyter_server/_version.py
new/jupyter_server-2.20.0/jupyter_server/_version.py
--- old/jupyter_server-2.19.0/jupyter_server/_version.py 2020-02-02
01:00:00.000000000 +0100
+++ new/jupyter_server-2.20.0/jupyter_server/_version.py 2020-02-02
01:00:00.000000000 +0100
@@ -6,7 +6,7 @@
import re
# Version string must appear intact for automatic versioning
-__version__ = "2.19.0"
+__version__ = "2.20.0"
# Build up version_info tuple for backwards compatibility
pattern = r"(?P<major>\d+).(?P<minor>\d+).(?P<patch>\d+)(?P<rest>.*)"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server-2.19.0/jupyter_server/nbconvert/handlers.py
new/jupyter_server-2.20.0/jupyter_server/nbconvert/handlers.py
--- old/jupyter_server-2.19.0/jupyter_server/nbconvert/handlers.py
2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server-2.20.0/jupyter_server/nbconvert/handlers.py
2020-02-02 01:00:00.000000000 +0100
@@ -92,6 +92,14 @@
auth_resource = AUTH_RESOURCE
SUPPORTED_METHODS = ("GET",)
+ @property
+ def content_security_policy(self):
+ # In case we're serving HTML, confine any Javascript to a unique
+ # origin so it can't interact with the Jupyter server.
+ if self.settings.get("nbconvert_csp_sandbox", True):
+ return super().content_security_policy + "; sandbox allow-scripts"
+ return super().content_security_policy
+
@web.authenticated
@authorized
async def get(self, format, path):
@@ -173,6 +181,14 @@
SUPPORTED_METHODS = ("POST",)
auth_resource = AUTH_RESOURCE
+ @property
+ def content_security_policy(self):
+ # In case we're serving HTML, confine any Javascript to a unique
+ # origin so it can't interact with the Jupyter server.
+ if self.settings.get("nbconvert_csp_sandbox", True):
+ return super().content_security_policy + "; sandbox allow-scripts"
+ return super().content_security_policy
+
@web.authenticated
@authorized
async def post(self, format):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server-2.19.0/jupyter_server/serverapp.py
new/jupyter_server-2.20.0/jupyter_server/serverapp.py
--- old/jupyter_server-2.19.0/jupyter_server/serverapp.py 2020-02-02
01:00:00.000000000 +0100
+++ new/jupyter_server-2.20.0/jupyter_server/serverapp.py 2020-02-02
01:00:00.000000000 +0100
@@ -457,6 +457,7 @@
"websocket_ping_timeout": websocket_ping_timeout,
# handlers
"extra_services": extra_services,
+ "nbconvert_csp_sandbox": jupyter_app.nbconvert_csp_sandbox,
# Jupyter stuff
"started": now,
# place for extensions to register activity
@@ -1604,6 +1605,15 @@
help="""If True, display controls to shut down the Jupyter server,
such as menu items or buttons.""",
)
+ nbconvert_csp_sandbox = Bool(
+ True,
+ config=True,
+ help=_i18n(
+ "If True, add a 'sandbox' directive to the Content-Security-Policy
header for nbconvert-served pages, "
+ "confining any JavaScript to a unique origin so it cannot interact
with the Jupyter server."
+ ),
+ )
+
contents_manager_class = Type(
default_value=AsyncLargeFileManager,
klass=ContentsManager,
@@ -2341,23 +2351,27 @@
resource.setrlimit(resource.RLIMIT_NOFILE, (soft, hard))
def _get_urlparts(
- self, path: str | None = None, include_token: bool = False
+ self,
+ path: str | None = None,
+ include_token: bool = False,
+ ip: str | None = None,
) -> urllib.parse.ParseResult:
"""Constructs a urllib named tuple, ParseResult,
with default values set by server config.
The returned tuple can be manipulated using the `_replace` method.
"""
+ if ip is None:
+ ip = self.ip
if self.sock:
scheme = "http+unix"
netloc = urlencode_unix_socket_path(self.sock)
else:
- if not self.ip:
+ # Empty string means "unset", fallback to localhost so the url
+ # is valid e.g. in k8s where gethostname() != localhost #743
+ if not ip:
ip = "localhost"
- # Handle nonexplicit hostname.
- elif self.ip in ("0.0.0.0", "::"): # noqa: S104
- ip = "%s" % socket.gethostname()
else:
- ip = f"[{self.ip}]" if ":" in self.ip else self.ip
+ ip = f"[{ip}]" if ":" in ip else ip
netloc = f"{ip}:{self.port}"
scheme = "https" if self.certfile else "http"
if not path:
@@ -2412,6 +2426,23 @@
urlparts = self._get_urlparts(path=self.base_url)
return urlparts.geturl()
+ @property
+ def connect_url(self) -> str:
+ """Human readable string with URLs for connecting to the running
+ Jupyter Server.
+
+ If `ip` is the wildcard address, add a text hint but keep
+ the machine hostname in the link as 0.0.0.0 is not connectable.
+ """
+ if self.ip not in ("0.0.0.0", "::"): # noqa: S104
+ return self.display_url
+ public = self._get_urlparts(include_token=True,
ip=socket.gethostname()).geturl()
+ hint = _i18n(
+ "The server is listening on all interfaces, "
+ "so any hostname or IP of this machine will work."
+ )
+ return f"{public}\n {self.local_url}\n{hint}"
+
def init_signal(self) -> None:
"""Initialize signal handlers."""
if not sys.platform.startswith("win") and sys.stdin and
sys.stdin.isatty():
@@ -2691,6 +2722,9 @@
for port in random_ports(self.port, self.port_retries + 1):
try:
sockets = bind_sockets(port, self.ip)
+ # When port=0 the OS assigns a free port, so we read it back
here.
+ if port == 0:
+ port = sockets[0].getsockname()[1]
for s in sockets:
s.close()
except OSError as e:
@@ -3138,7 +3172,7 @@
message = [
"\n",
_i18n("To access the server, copy and paste one of
these URLs:"),
- " %s" % self.display_url,
+ " %s" % self.connect_url,
]
else:
message = [
@@ -3150,7 +3184,7 @@
_i18n(
"Or copy and paste one of these URLs:",
),
- " %s" % self.display_url,
+ " %s" % self.connect_url,
]
self.log.critical("\n".join(message))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server-2.19.0/jupyter_server/services/kernels/kernelmanager.py
new/jupyter_server-2.20.0/jupyter_server/services/kernels/kernelmanager.py
--- old/jupyter_server-2.19.0/jupyter_server/services/kernels/kernelmanager.py
2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server-2.20.0/jupyter_server/services/kernels/kernelmanager.py
2020-02-02 01:00:00.000000000 +0100
@@ -37,6 +37,7 @@
from traitlets import (
Any,
Bool,
+ CaselessStrEnum,
Dict,
Float,
Instance,
@@ -67,6 +68,18 @@
kernel_argv = List(Unicode())
+ transport_encryption = CaselessStrEnum(
+ ["disabled", "auto", "required"],
+ default_value="disabled",
+ config=True,
+ help=(
+ "Transport encryption policy for manager-provisioned CurveZMQ keys
for all managed kernels. "
+ "'disabled' (default) does not provision Curve credentials, 'auto'
provisions when the kernelspec "
+ "declares support, and 'required' enforces provisioning and fails
kernel startup if encryption "
+ "cannot be applied."
+ ),
+ )
+
root_dir = Unicode(config=True)
_kernel_connections = Dict()
@@ -204,6 +217,13 @@
os_path = os.path.dirname(os_path)
return os_path
+ def _kernel_start_kwargs(self, **kwargs: t.Any) -> dict[str, t.Any]:
+ """Build kernel launch kwargs with server-level policy applied."""
+ launch_kwargs = dict(kwargs)
+ if self.transport_encryption != "disabled":
+ launch_kwargs["transport_encryption"] = self.transport_encryption
+ return launch_kwargs
+
async def _remove_kernel_when_ready(self, kernel_id, kernel_awaitable):
"""Remove a kernel when it is ready."""
await super()._remove_kernel_when_ready(kernel_id, kernel_awaitable)
@@ -213,7 +233,7 @@
# TODO: DEC 2022: Revise the type-ignore once the signatures have been
changed upstream
# https://github.com/jupyter/jupyter_client/pull/905
async def _async_start_kernel( # type:ignore[override]
- self, *, kernel_id: str | None = None, path: ApiPath | None = None,
**kwargs: str
+ self, *, kernel_id: str | None = None, path: ApiPath | None = None,
**kwargs: t.Any
) -> str:
"""Start a kernel for a session and return its kernel_id.
@@ -231,6 +251,7 @@
an existing kernel is returned, but it may be checked in the
future.
"""
if kernel_id is None or kernel_id not in self:
+ kwargs = self._kernel_start_kwargs(**kwargs)
if path is not None:
kwargs["cwd"] = self.cwd_for_path(path, env=kwargs.get("env",
{}))
if kernel_id is not None:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server-2.19.0/pyproject.toml
new/jupyter_server-2.20.0/pyproject.toml
--- old/jupyter_server-2.19.0/pyproject.toml 2020-02-02 01:00:00.000000000
+0100
+++ new/jupyter_server-2.20.0/pyproject.toml 2020-02-02 01:00:00.000000000
+0100
@@ -33,7 +33,11 @@
"nbformat>=5.3.0",
"packaging>=22.0",
"prometheus_client>=0.9",
- "pywinpty>=2.0.1;os_name=='nt'",
+ # pywinpty 3.0.4 ships broken Windows wheels: winpty.dll (amd64) and all
native
+ # binaries (arm64) are no longer bundled, so the .pyd fails to load ->
import winpty
+ # raises ImportError -> terminado falls back to `object` -> object.spawn
AttributeError.
+ # See https://github.com/andfoy/pywinpty/issues/573 (regressed in 3.0.4,
2026-06-10).
+ "pywinpty>=2.0.1,!=3.0.4;os_name=='nt'",
"pyzmq>=24",
"Send2Trash>=1.8.2",
"terminado>=0.8.3",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server-2.19.0/tests/auth/test_authorizer.py
new/jupyter_server-2.20.0/tests/auth/test_authorizer.py
--- old/jupyter_server-2.19.0/tests/auth/test_authorizer.py 2020-02-02
01:00:00.000000000 +0100
+++ new/jupyter_server-2.20.0/tests/auth/test_authorizer.py 2020-02-02
01:00:00.000000000 +0100
@@ -246,7 +246,7 @@
@pytest.mark.parametrize(
- "jp_server_config,",
+ "jp_server_config",
[
{
"ServerApp": {"authorizer_class": AsyncAuthorizerTest},
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server-2.19.0/tests/nbconvert/test_handlers.py
new/jupyter_server-2.20.0/tests/nbconvert/test_handlers.py
--- old/jupyter_server-2.19.0/tests/nbconvert/test_handlers.py 2020-02-02
01:00:00.000000000 +0100
+++ new/jupyter_server-2.20.0/tests/nbconvert/test_handlers.py 2020-02-02
01:00:00.000000000 +0100
@@ -190,3 +190,38 @@
r = await jp_fetch("nbconvert", "latex", method="POST",
body=json.dumps(nbmodel))
assert "application/zip" in r.headers["Content-Type"]
assert ".zip" in r.headers["Content-Disposition"]
+
+
[email protected](
+ "jp_server_config,expected",
+ [
+ ({"ServerApp": {"nbconvert_csp_sandbox": True}}, "sandbox
allow-scripts"),
+ ({"ServerApp": {"nbconvert_csp_sandbox": False}}, None),
+ ],
+)
+async def test_csp_file_handler(jp_fetch, jp_server_config, notebook,
expected):
+ r = await jp_fetch("nbconvert", "html", "foo", "testnb.ipynb",
method="GET")
+ csp = r.headers["Content-Security-Policy"]
+ if expected:
+ assert expected in csp
+ else:
+ assert "sandbox" not in csp
+
+
[email protected](
+ "jp_server_config,expected",
+ [
+ ({"ServerApp": {"nbconvert_csp_sandbox": True}}, "sandbox
allow-scripts"),
+ ({"ServerApp": {"nbconvert_csp_sandbox": False}}, None),
+ ],
+)
+async def test_csp_post_handler(jp_fetch, jp_server_config, notebook,
expected):
+ r = await jp_fetch("api/contents/foo/testnb.ipynb", method="GET")
+ nbmodel = json.loads(r.body.decode())
+
+ r = await jp_fetch("nbconvert", "html", method="POST",
body=json.dumps(nbmodel))
+ csp = r.headers["Content-Security-Policy"]
+ if expected:
+ assert expected in csp
+ else:
+ assert "sandbox" not in csp
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server-2.19.0/tests/services/kernels/test_config.py
new/jupyter_server-2.20.0/tests/services/kernels/test_config.py
--- old/jupyter_server-2.19.0/tests/services/kernels/test_config.py
2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server-2.20.0/tests/services/kernels/test_config.py
2020-02-02 01:00:00.000000000 +0100
@@ -1,7 +1,10 @@
import pytest
from traitlets.config import Config
-from jupyter_server.services.kernels.kernelmanager import
AsyncMappingKernelManager
+from jupyter_server.services.kernels.kernelmanager import (
+ AsyncMappingKernelManager,
+ MappingKernelManager,
+)
@pytest.fixture
@@ -29,3 +32,21 @@
]
with pytest.warns(FutureWarning, match="is not a subclass of
'ServerKernelManager'"):
jp_configurable_serverapp(argv=argv)
+
+
+def test_kernel_start_kwargs_transport_encryption_sets_flag():
+ km = MappingKernelManager(transport_encryption="auto")
+
+ launch_kwargs = km._kernel_start_kwargs(env={"EXISTING": "1"})
+
+ assert launch_kwargs["transport_encryption"] == "auto"
+ assert launch_kwargs["env"] == {"EXISTING": "1"}
+
+
+def test_kernel_start_kwargs_transport_encryption_required_sets_policy():
+ km = MappingKernelManager(transport_encryption="required")
+
+ launch_kwargs = km._kernel_start_kwargs(env={"EXISTING": "1"})
+
+ assert launch_kwargs["transport_encryption"] == "required"
+ assert launch_kwargs["env"] == {"EXISTING": "1"}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server-2.19.0/tests/services/kernels/test_transport_encryption.py
new/jupyter_server-2.20.0/tests/services/kernels/test_transport_encryption.py
---
old/jupyter_server-2.19.0/tests/services/kernels/test_transport_encryption.py
1970-01-01 01:00:00.000000000 +0100
+++
new/jupyter_server-2.20.0/tests/services/kernels/test_transport_encryption.py
2020-02-02 01:00:00.000000000 +0100
@@ -0,0 +1,111 @@
+"""End-to-end tests for MappingKernelManager.transport_encryption."""
+
+import json
+import sys
+import warnings
+
+import jupyter_client
+import pytest
+import zmq
+from traitlets.config import Config
+
+TEST_TIMEOUT = 60
+
+pytestmark = [
+ pytest.mark.skipif(
+ jupyter_client._version.version_info < (8, 9),
+ reason="transport_encryption requires jupyter-client >= 8.9",
+ ),
+ pytest.mark.skipif(
+ not zmq.has("curve"),
+ reason="CurveZMQ not available in this environment (zmq.has('curve')
is False)",
+ ),
+]
+
+
[email protected](autouse=True)
+def suppress_deprecation_warnings():
+ with warnings.catch_warnings():
+ warnings.filterwarnings(
+ "ignore",
+ message="The synchronous MappingKernelManager",
+ category=DeprecationWarning,
+ )
+ yield
+
+
[email protected]
+def non_curve_kernel_spec(jp_data_dir):
+ """Install a minimal kernel spec that does not declare curve encryption
support."""
+ kernel_dir = jp_data_dir / "kernels" / "no_curve"
+ kernel_dir.mkdir(parents=True)
+ (kernel_dir / "kernel.json").write_text(
+ json.dumps(
+ {
+ "argv": [sys.executable, "-c", "pass"],
+ "display_name": "No Curve",
+ "language": "python",
+ }
+ )
+ )
+ return "no_curve"
+
+
[email protected](TEST_TIMEOUT)
+async def
test_transport_encryption_disabled_no_curve_keys(jp_configurable_serverapp):
+ """Default 'disabled' policy never provisions curve keys, even for kernels
that support them."""
+ app = jp_configurable_serverapp()
+ km = app.kernel_manager
+ kernel_id = await km.start_kernel()
+ try:
+ kernel = km.get_kernel(kernel_id)
+ info = kernel.get_connection_info()
+ assert "curve_publickey" not in info
+ assert "curve_secretkey" not in info
+ finally:
+ await km.shutdown_kernel(kernel_id, now=True)
+
+
[email protected](TEST_TIMEOUT)
[email protected]("policy", ["auto", "required"])
+async def
test_transport_encryption_provisions_curve_keys(jp_configurable_serverapp,
policy):
+ """'auto' and 'required' both provision curve keys for kernelspecs that
declare curve support."""
+ config = Config({"MappingKernelManager": {"transport_encryption": policy}})
+ app = jp_configurable_serverapp(config=config)
+ km = app.kernel_manager
+ kernel_id = await km.start_kernel()
+ try:
+ kernel = km.get_kernel(kernel_id)
+ info = kernel.get_connection_info()
+ assert "curve_publickey" in info
+ assert "curve_secretkey" in info
+ finally:
+ await km.shutdown_kernel(kernel_id, now=True)
+
+
[email protected](TEST_TIMEOUT)
+async def test_transport_encryption_auto_skips_keys_for_non_curve_kernel(
+ non_curve_kernel_spec, jp_configurable_serverapp
+):
+ """'auto' silently skips key provisioning when the kernelspec lacks curve
support metadata."""
+ config = Config({"MappingKernelManager": {"transport_encryption": "auto"}})
+ app = jp_configurable_serverapp(config=config)
+ km = app.kernel_manager
+ kernel_id = await km.start_kernel(kernel_name=non_curve_kernel_spec)
+ try:
+ kernel = km.get_kernel(kernel_id)
+ info = kernel.get_connection_info()
+ assert "curve_publickey" not in info
+ assert "curve_secretkey" not in info
+ finally:
+ await km.shutdown_kernel(kernel_id, now=True)
+
+
+async def test_transport_encryption_required_raises_for_non_curve_kernel(
+ non_curve_kernel_spec, jp_configurable_serverapp
+):
+ """'required' raises RuntimeError at startup when the kernelspec lacks
curve support metadata."""
+ config = Config({"MappingKernelManager": {"transport_encryption":
"required"}})
+ app = jp_configurable_serverapp(config=config)
+ with pytest.raises(RuntimeError, match=r"metadata\.supported_encryption"):
+ await
app.kernel_manager.start_kernel(kernel_name=non_curve_kernel_spec)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server-2.19.0/tests/test_serverapp.py
new/jupyter_server-2.20.0/tests/test_serverapp.py
--- old/jupyter_server-2.19.0/tests/test_serverapp.py 2020-02-02
01:00:00.000000000 +0100
+++ new/jupyter_server-2.20.0/tests/test_serverapp.py 2020-02-02
01:00:00.000000000 +0100
@@ -317,11 +317,26 @@
"http+unix://%2Ftmp%2Fjp-test.sock/",
),
(
+ # https://github.com/jupyter-server/jupyter_server/issues/743
{"ip": ""},
"http://localhost:8888/?token=<generated>",
"http://127.0.0.1:8888/?token=<generated>",
"http://localhost:8888/",
),
+ (
+ # https://github.com/jupyterlab/jupyterlab/issues/15520
+ {"ip": "0.0.0.0"},
+ "http://0.0.0.0:8888/?token=<generated>",
+ "http://127.0.0.1:8888/?token=<generated>",
+ "http://0.0.0.0:8888/",
+ ),
+ (
+ # https://github.com/jupyterlab/jupyterlab/issues/15520
+ {"ip": "::"},
+ "http://[::]:8888/?token=<generated>",
+ "http://[::1]:8888/?token=<generated>",
+ "http://[::]:8888/",
+ ),
],
)
def test_urls(config, public_url, local_url, connection_url):
@@ -343,6 +358,61 @@
ServerApp.clear_instance()
[email protected](
+ "config,connect_url",
+ [
+ # Non-wildcard addresses: mirrors display_url.
+ (
+ {"ip": ""},
+ "http://localhost:8888/?token=<generated>\n
http://127.0.0.1:8888/?token=<generated>",
+ ),
+ (
+ {"ip": "127.0.0.1"},
+ "http://127.0.0.1:8888/?token=<generated>",
+ ),
+ # Sockets: mirrors display_url.
+ (
+ {"sock": "/tmp/jp-test.sock"},
+ "http+unix://%2Ftmp%2Fjp-test.sock/?token=<generated>",
+ ),
+ # Wildcard addresses are not connectable: use the machine's
+ # hostname instead, with a trailing note that any address works.
+ (
+ {"ip": "0.0.0.0"},
+ "http://myhost:8888/?token=<generated>\n"
+ " http://127.0.0.1:8888/?token=<generated>\n"
+ "The server is listening on all interfaces, "
+ "so any hostname or IP of this machine will work.",
+ ),
+ # Wildcard but ipv6
+ (
+ {"ip": "::"},
+ "http://myhost:8888/?token=<generated>\n"
+ " http://[::1]:8888/?token=<generated>\n"
+ "The server is listening on all interfaces, "
+ "so any hostname or IP of this machine will work.",
+ ),
+ ],
+)
+def test_connect_url(config, connect_url):
+ # Verify we're working with a clean instance.
+ ServerApp.clear_instance()
+ serverapp = ServerApp.instance(**config)
+ serverapp.init_configurables()
+ token = serverapp.identity_provider.token
+ if serverapp.identity_provider.token_generated:
+ connect_url = connect_url.replace("<generated>", token)
+
+ with patch("socket.gethostname", return_value="myhost"):
+ assert serverapp.connect_url == connect_url
+
+ # The wildcard address is never advertised as connectable.
+ assert "0.0.0.0" not in serverapp.connect_url
+ assert "[::]" not in serverapp.connect_url
+ # Cleanup singleton after test.
+ ServerApp.clear_instance()
+
+
# Preferred dir tests
# ----------------------------------------------------------------------------
@pytest.mark.filterwarnings("ignore::FutureWarning")
@@ -664,6 +734,14 @@
assert _has_tornado_web_authenticated(method) == expected
+def test_find_http_port_zero_resolves_real_port(jp_configurable_serverapp):
+ """port=0 should resolve to the real OS-assigned port, not stay 0
(#1650)."""
+ app = jp_configurable_serverapp()
+ app.port = 0
+ app._find_http_port()
+ assert app.port != 0
+
+
def test_bind_http_server_tcp_success(jp_configurable_serverapp):
"""Normal case: listen succeeds, returns True."""
app = jp_configurable_serverapp()