Security model#

This page explains the security assumptions behind notebook embedding + hooks, and how to run Cellucid safely in local and shared environments.

If you primarily need HTTPS/mixed-content details, see Security: CORS, origins, and mixed content.

Threat model (what Cellucid assumes)#

Default assumption for notebook usage:

  • You are running a notebook you trust.

  • The Python kernel and the browser are under your control.

  • The Cellucid server is bound to 127.0.0.1 (localhost) and is not exposed to the network.

Within that model, it’s reasonable to allow:

  • the iframe to POST events back to the server (/_cellucid/events)

  • Python to send commands into the iframe (postMessage)

What is “exposed” and to whom?#

The data server#

The Cellucid server exposes dataset endpoints (exported or AnnData-backed) plus:

  • /_cellucid/health

  • /_cellucid/info

  • /_cellucid/datasets

  • /_cellucid/events (POST)

  • /_cellucid/session_bundle (POST)

In notebook mode, the server is started with host="127.0.0.1" by default, so it should only be reachable from the same machine.

The viewer UI assets#

In notebook mode, each viewer startup downloads the declared web generation from its configured source, verifies it completely, and publishes it locally before the server binds.

Security implications:

  • Your dataset is not uploaded to cellucid.com by this mechanism.

  • Every viewer-serving startup needs outbound access to its configured web_source_url; an existing generation directory is not an offline substitute.

Authentication and integrity#

Python → iframe commands (postMessage)#

  • Commands are authenticated using a per-viewer token (viewerToken).

  • The iframe ignores commands that don’t include the expected token.

This avoids relying on strict postMessage origin allowlisting, which is brittle in notebook frontends that proxy/transform origins.

Iframe → Python events (/_cellucid/events)#

  • Events are HTTP POSTs to the local server.

  • Every event body must contain the exact viewerId, viewerToken, and event type.

  • The server validates the per-viewer token before delivering the event once to the registered callback.

  • Missing or incorrect credentials are rejected; see Security: CORS, origins, and mixed content for the separate origin policy.

High-risk configurations (avoid these unless you know what you’re doing)#

Binding the server to 0.0.0.0 (LAN exposure)#

If you run cellucid serve --host 0.0.0.0 (or equivalent), anyone on the network who can reach your machine can potentially access:

  • your dataset endpoints,

  • hooks endpoints,

  • and session bundle upload endpoints.

If you need remote access, prefer:

  • SSH tunneling, or

  • a properly secured reverse proxy with authentication.

Running on shared JupyterHub without proxy hardening#

On shared infrastructure, ensure:

  • notebook server access is authenticated,

  • ports are not exposed directly,

  • jupyter-server-proxy is configured appropriately (if used).

Data privacy notes#

  • Hooks events can contain:

  • Session bundles can contain user-defined labels and highlight membership.

Treat session bundles as sensitive data:

  • store them like you would store curated annotations,

  • avoid committing private identifiers into public repos.

Next steps#