Skip to content

Support ZMQ Curve for transport encryption#1515

Open
krassowski wants to merge 23 commits into
ipython:mainfrom
krassowski:curve-encryption
Open

Support ZMQ Curve for transport encryption#1515
krassowski wants to merge 23 commits into
ipython:mainfrom
krassowski:curve-encryption

Conversation

@krassowski
Copy link
Copy Markdown
Member

@krassowski krassowski commented May 6, 2026

This PR adds support for ZMQ Curve cryptography to encrypt kernel communications over TCP, addressing the current limitation of all code and outputs being transmitted in plaintext.

What's changed

  • Curve key support in IPKernelApp: Loads curve_publickey / curve_secretkey from connection files and applies them to all ZMQ sockets (iopub, shell, control, stdin, heartbeat) via new _apply_curve_server_options() / _apply_curve_client_options() helpers.
  • Kernelspec metadata: Advertises "supported_encryption": "curve" so frontends can discover and negotiate encryption capability.
  • Emits a warning when TCP transport is used without encryption, guiding users toward IPC or CurveZMQ.
  • Heartbeat thread: Extended to accept and apply curve keys to its ROUTER socket.

References

Code changes

  • tests
  • additions

Comment thread ipykernel/kernelapp.py Outdated
Comment thread ipykernel/kernelapp.py Outdated
Comment thread ipykernel/kernelapp.py
Comment on lines +363 to +369
self.log.warning(
"Kernel is running over TCP without encryption."
" All communication (including code and outputs) is sent in plain text"
" and is susceptible to eavesdropping."
" Use IPC transport or set IPKernelApp.enable_curve=True to enable"
" CurveZMQ encryption."
)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nbclient downstream tests are failing due to addition of this warning, see:

  E       AssertionError: assert '[IPKernelApp] WARNING | Kernel is running over TCP without encryption. All communication (including code and outputs) is sent in plain text and is susceptible to eavesdropping. Use IPC transport or set IPKernelApp.enable_curve=True to enable CurveZMQ encryption.\n[IPKernelApp] WARNING | Kernel is running over TCP without encryption. All communication (including code and outputs) is sent in plain text and is susceptible to eavesdropping. Use IPC transport or set IPKernelApp.enable_curve=True to enable CurveZMQ encryption.' == ''

I believe we should keep it and update nbclient tests, any objections?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to fix nbclient.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, nbclient tests shouldn't fail if warnings are logged from another package, that's a problem in the test suite

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Carreau Carreau self-requested a review May 7, 2026 10:20
Comment thread ipykernel/heartbeat.py Outdated
Comment thread ipykernel/heartbeat.py Outdated
Comment thread ipykernel/kernelapp.py Outdated
).tag(config=True)

enable_curve = Bool(
bool(int(os.environ.get("JUPYTER_ENABLE_CURVE", "0"))),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the name it's non obvious, i think it's ok to have "curve" in inner variable names, maybe not in env-var and options. ; should we also trim(), the value of environ.get, or be stricter on it's format ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, I think we could hide "curve" here as an implementation detail; an argument for keeping it as-is would be alignment with ipyparallel (ipython/ipyparallel#553).

I think an obvious choice would be enable_transport_encryption?

Same change would need to follow in jupyter-server/jupyter_server#1638.

CC @minrk in case if you have an opinion here

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enable_transport_encryption / JUPYTER_ENABLE_TRANSPORT_ENCRYPTION seems fine to me, if a bit verbose. I can update ipyparallel to match, no big deal.

Comment thread tests/test_curve.py Outdated
@krassowski krassowski marked this pull request as ready for review May 9, 2026 08:20
Comment thread pyproject.toml Outdated
Comment thread pyproject.toml Outdated
Comment thread ipykernel/kernelapp.py Outdated
@Carreau
Copy link
Copy Markdown
Member

Carreau commented Jun 5, 2026

I kicked test; everything passing. Do you want to update the PR description extensively before merge ?

Copy link
Copy Markdown
Member Author

@krassowski krassowski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description is fine, but we should release jupyter-client and update pyproject.toml before we merge

Comment thread pyproject.toml Outdated
Comment thread pyproject.toml Outdated
Carreau pushed a commit to jupyter/jupyter_client that referenced this pull request Jun 5, 2026
This commit adds CurveZMQ transport encryption to jupyter_client, providing the foundational layer for encrypted kernel communication. It is the upstream dependency for ipykernel#1515 and jupyter-server#1638.

What's changed

- local_provisioner.py: Generates a CurveZMQ keypair and writes curve_publickey / curve_secretkey into the connection file. The client owns key generation.
- connect.py: Reads curve keys from connection files and applies them when setting up ZMQ sockets.
- manager.py: Introduces a transport_encryption traitlet with three modes:

  - 'disabled' (default) — no encryption, existing behavior unchanged
  - 'auto' — enables encryption only for kernelspecs that advertise metadata.supported_encryption: 'curve'
  - 'required' — encryption is mandatory; kernel startup fails if the kernelspec doesn't declare support
- channels.py / client.py: Heartbeat channel configured as a CurveZMQ client using the server's public key.
- Validation: Checks for libsodium support in pyzmq at startup and raises a clear error if missing.

Design notes

- Named `transport_encryption` (not a boolean) to leave room for future encryption backends beyond CurveZMQ.
- Fully backward-compatible: 'disabled' is the default, so nothing changes for existing kernels.
- Kernel capability discovery is via kernelspec metadata (supported_encryption: 'curve'), handled on the kernel side in ipykernel#1515.
- Guards against pyzmq builds without libsodium, with graceful degradation or a clear error depending on the configured mode.



### References

- Closes #808
- PoC for jupyter/enhancement-proposals#75
- Sibling PR ipython/ipykernel#1515
- Toggle on server-level: jupyter-server/jupyter_server#1638
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants