🔒 Security#

Most connection types (SSH, SSH Command, Docker over SSH, and Docker over SSH Command) reach the solver over a network, so the add-on’s security story is mostly the SSH endpoint’s security story. This page collects the things you need to know before pointing it at a host you do not fully control, or a host that is reachable from the wider internet.

If you use Local or Windows Native the network-attacker sections below do not apply: the solver is a child process on your own machine and no TCP socket leaves it. Docker (Local) sits in between: there is no SSH hop, but the server runs inside a container that has to publish its port on your workstation, and the README’s -p 9090:9090 publishes it on every interface. Read Network exposure of the solver port before skipping ahead. Otherwise, skip to Code execution risk: MCP and Credential files on disk.

Where the Trust Boundaries Are#

The diagram on the Connections page shows the transport topology. The trust boundaries that matter for security are:

Boundary

What crosses it

Protection

Blender workstation -> remote solver host

SSH session (paramiko)

SSH key auth + SSH encryption

SSH session -> ppf-cts-server on remote

direct-tcpip channel to the remote’s localhost:<server_port>

Stays inside the SSH tunnel; not a separate network hop

Blender workstation <-> local solver

UNIX loopback TCP socket

No encryption; no encryption needed (never leaves the host)

Blender workstation <-> local MCP server

UNIX loopback TCP socket on port 9633

Bound to localhost only; a non-loopback Origin header is refused with 403

Blender workstation <-> local reload/debug server

UNIX loopback TCP socket on port 8765

Bound to localhost only. Off in a plain Blender session unless you start it from Debug Options, but blender_addon/launch.sh starts it automatically; its execute command runs arbitrary Python inside Blender with no authentication, so treat it exactly like the MCP port

The important consequence: when you run Docker-over-SSH or plain SSH, the solver’s TCP socket never crosses an untrusted network directly. paramiko opens a direct-tcpip channel to localhost:<server_port> on the remote. By default ppf-cts-server binds to 127.0.0.1 (loopback only) on the remote in plain SSH, Local, and Windows Native modes, so the SSH tunnel is the only way in. In Docker-family modes the add-on launches ppf-cts-server with --host 0.0.0.0 inside the container because docker -p HOST:CONTAINER forwards traffic to the container’s external interface (eth0), not loopback; external reachability of the container is then controlled by the host-side -p mapping (see below).

SSH Authentication#

The add-on only supports public-key authentication. There is no password field in the UI, and paramiko is never asked to try password auth. Concretely:

  • Provide a private key in SSH Key (Custom mode) or via -i (Command mode). Ed25519 and RSA keys are supported; PuTTY .ppk keys are not.

  • The matching public key must be in the remote account’s ~/.ssh/authorized_keys.

  • Encrypted (passphrase-protected) keys are not supported by the add-on’s paramiko integration: paramiko is invoked without a passphrase argument or prompt callback, so an encrypted key file raises an exception at connect time. Use ssh-agent (or keychain, pageant, gnome-keyring) to unlock the key once per login session and let paramiko pick it up through the agent, or decrypt the key file before pointing the add-on at it.

  • Use a dedicated key for the solver host where practical, so revoking access to the solver does not require revoking access everywhere else that key is trusted.

On the server side, the usual SSH hardening rules apply and are not the add-on’s responsibility: disable password auth in sshd_config (PasswordAuthentication no), keep PermitRootLogin tight, and limit authorized_keys with from=, command=, or restrict options if the account is dedicated to the solver.

Host-Key Verification (Trust-on-First-Use)#

Warning

The add-on accepts unknown host keys silently. It does not consult ~/.ssh/known_hosts, and it does not honor StrictHostKeyChecking from ~/.ssh/config. Internally, paramiko is configured with AutoAddPolicy.

This means the very first connection to a host has no out-of-band verification: a man-in-the-middle on that first handshake can present their own key and the add-on will trust it. Mitigations:

  • Do the first connect from a trusted network. A home or office LAN with known routing is safer than a hotel or airport Wi-Fi for the initial handshake.

  • Prefer a jump host you already trust. Bring up ssh -L from a terminal (which does honor known_hosts), then point the add-on at localhost:<forwarded-port>. The add-on’s weak policy now only applies to a loopback handshake, which a network attacker cannot influence. The panel’s own Proxy Jump is a convenience rather than a substitute here: it dials the jump host with paramiko, so the weak policy applies to that handshake as well.

  • Avoid public networks for first-run on production hosts. If you have to, verify the SSH fingerprint out of band after connecting (for example, by logging in from a known-good machine and checking the remote’s /etc/ssh/ssh_host_*_key.pub).

The add-on cannot be configured to reject unknown keys from the panel; the tunnel-through-your-own-ssh pattern is the supported escape hatch.

Network Exposure of the Solver Port#

ppf-cts-server listens on TCP. Where that TCP socket is reachable from depends on how the backend is deployed, not on the add-on:

  • Plain SSH / SSH Command. ppf-cts-server runs in the remote user’s shell and the add-on launches it with no --host flag at all, so the binary falls back to its own default bind address of 127.0.0.1 and the socket is loopback-only on the remote. (Do not read the missing --host in ps as a failure to set it; check the bind with ss instead.) The add-on reaches it through the SSH session’s direct-tcpip channel; nothing else on the network can connect, regardless of host firewall. If you need the port reachable from the remote’s wider network for some other tool, you must rebind it yourself (e.g. by relaunching ppf-cts-server --host 0.0.0.0 outside the add-on).

  • Docker / Docker over SSH. Inside the container the add-on launches ppf-cts-server --host 0.0.0.0 because docker -p HOST:CONTAINER forwards traffic to the container’s external interface (eth0), not loopback. External reachability of the host is then controlled by the -p mapping you choose: the container must publish the server port (-p 9090:9090 or the equivalent in your compose file), otherwise the add-on refuses to start. The one exception is a Docker (Local) container on --network host, which publishes nothing and is accepted as is. There the in-container --host 0.0.0.0 binds every interface of the Docker host directly and no -p mapping exists to restrict it, so the mitigation below does not apply; use a bridge network if you need the loopback publish. The default -p 9090:9090 binds to all interfaces on the Docker host, which can expose the solver to any network that reaches that host. Prefer -p 127.0.0.1:9090:9090 so the publish is loopback-only; the add-on will still reach it over the SSH tunnel.

  • Windows Native / Local. The server binds to the local machine only (loopback). No container and no host firewall change to worry about.

A quick check on the remote (or inside the container):

# On the solver host (plain SSH) or inside the container (Docker)
ss -tlnp | grep 9090
# LISTEN 0 ... 127.0.0.1:9090 ...  → loopback-only (default for plain SSH / Local / Windows)
# LISTEN 0 ... 0.0.0.0:9090   ...  → all interfaces (default inside Docker; host -p mapping controls external exposure)

Plain SSH now binds loopback-only by default. For Docker-over-SSH the in-container bind is 0.0.0.0 (required for port publishing); restrict the host-side mapping with -p 127.0.0.1:9090:9090 if you do not want the port exposed beyond the Docker host’s loopback.

What the Add-on Does Not Support from ~/.ssh/config#

The bundled SSH parser is intentionally minimal; several options that you might rely on for security elsewhere are silently ignored. See Supported ssh_config options for the authoritative list. The ones worth flagging here:

Option

Consequence

StrictHostKeyChecking

Ignored. See Host-key verification.

UserKnownHostsFile

Ignored. known_hosts is never consulted.

ProxyJump

Honored. Each hop is dialed by paramiko under the same weak host-key policy as the destination, so a chain widens the trust-on-first-use surface to every jump host in it.

ProxyCommand

Ignored. Bring up the jump yourself with ssh -L.

IdentitiesOnly

Ignored. paramiko will try the configured key, then every key the agent offers, then any id_rsa / id_dsa / id_ecdsa / id_ed25519 key it finds in ~/.ssh/.

PreferredAuthentications

Ignored. paramiko picks the auth method.

CertificateFile

Ignored as an ssh_config directive. However, if a matching <key>-cert.pub file exists alongside a private key (either the configured key_path or a discoverable ~/.ssh/id_* key), paramiko loads and uses it automatically. Explicit CertificateFile entries in ~/.ssh/config are not read.

If your environment depends on any of these for its security posture (certificate-gated auth, pinned host keys), bring up ssh -L yourself from a terminal so that the system ssh binary handles the sensitive part of the connection and the add-on only talks to localhost. That holds for bastion-only access too whenever the bastion is the part you need verified: the add-on can route through it, but it does so under the same weak host-key policy it applies everywhere else.

Shared Solver Hosts#

Two users on the same box share more than a port; they share a GPU, a filesystem, and (unless you configure otherwise) a user account. For a safer shared setup:

  • Give each user their own remote Linux account. Key auth keeps them apart at the OS level.

  • Give each user (or project) a distinct Remote Path. That is what prevents one client from overwriting another’s checkpoints or accidentally fetching another run’s frames.

  • Give them distinct server ports too where you can, but note that the panel only draws the port field (Docker Port) in the Docker-family modes. One shared property sits behind that field, so on SSH, SSH Command, Local, and Windows Native the port is whatever it was last set to (9090 by default): set it from a Docker mode and switch back, or give each entry its own with a connection profile’s docker_port key. SSH does not recommend sharing a solver host in the first place.

  • Do not share private keys. One key per user means revocation is one line in authorized_keys.

  • Remember the solver has file-system access to whatever the remote account can read. Do not run it under an account that also owns sensitive data.

Code Execution Risk: MCP#

The bundled MCP server exposes run_python_script and execute_shell_command by design. There is no sandbox, no allowlist, and no authentication. The protections are network scoping plus a loopback-Origin gate:

  • The MCP server binds to localhost only (see Port usage at a glance). Do not forward the MCP port over ssh -R, ngrok, gh codespaces, or any reverse proxy unless the machine is disposable.

  • The HTTP transport refuses any request whose Origin header is not localhost, 127.0.0.1, or ::1 with a 403 Origin not allowed. That is DNS-rebinding protection: it stops a web page you happen to visit from driving run_python_script through your browser. A request with no Origin at all (curl, native MCP clients) is allowed, so it is not an authentication check.

  • Any process on the Blender machine can reach the MCP port. Treat local-user isolation as your security boundary: a malicious local user already owns the Blender session.

  • The add-on’s reload/debug server on port 8765 is a second unauthenticated code-execution socket. It is off in a plain Blender session and starts when you press Start under Debug Options -> Add-on Local Debug Server, but blender_addon/launch.sh starts it (with the MCP server) automatically at launch (Reload Add-on Now and Full Reload bring it back afterwards, but only if it was already running). While it runs, a JSON packet sent to it executes arbitrary Python inside Blender, with no Origin check and no authentication. Stop it when you are done debugging.

  • LLM outputs wired into run_python_script are effectively shell on your machine. Always review the agent’s tool calls before approving them; see MCP Security.

Credential Files on Disk#

The add-on stores connection details in plain-text TOML connection profiles. Profile files contain hostnames, ports, usernames, container names, and paths to private keys; they do not contain the keys themselves or any passphrases.

  • Keep the profile file readable only by your user (chmod 600 connections.toml on Unix). The add-on does not enforce this.

  • When sharing a profile with teammates (check-in, chat, paste), strip the username, key_path, and proxy_jump fields first. The host + container + port fields are usually fine; the identity fields are not, and proxy_jump is both: it names your bastion host and can carry a user@ of its own.

  • The private key file itself should also be mode 600 and owned by your user (chmod 600 ~/.ssh/id_ed25519). paramiko does not load world-readable keys silently, but it does not refuse them either.

Logs and Artifacts#

server.log and progress.log on the remote capture the solver’s stdout and a structured run log. They can contain:

  • Remote file paths under the project directory.

  • Scene metadata (object names, frame counts, parameter values).

  • Tracebacks from the solver, including absolute paths on the host.

They do not contain your SSH key, Blender credentials, or the raw geometry. If the remote is shared, review the project directory’s permissions if these logs are sensitive.

The local reload and MCP servers log to Blender’s console (the terminal that launched Blender, or Blender’s Python console), not to disk.

Quick Checklist Before Connecting to an Untrusted Network#

  1. Use an Ed25519 key dedicated to the solver host.

  2. Make sure the key is either unencrypted-but-safeguarded or loaded into ssh-agent.

  3. Bring up a trusted tunnel from a terminal (ssh -L 2222:gpu01.example.com:22 bastion.example.com) rather than letting the add-on dial the hostile network directly, whether that dialing would be to the host itself or through the panel’s Proxy Jump.

  4. Point the add-on at localhost:2222, leaving the weak host-key policy applied only to loopback.

  5. On the remote, verify the server port is not reachable from the outside. With plain SSH the add-on now binds ppf-cts-server to 127.0.0.1 so loopback-only is the default; confirm with ss -tlnp | grep 9090 (you should see 127.0.0.1:9090, not 0.0.0.0:9090).

  6. If you use Docker-over-SSH, publish the host-side port as 127.0.0.1:9090:9090 (the in-container bind is 0.0.0.0 by necessity, but the host mapping can still be loopback-only).

  7. Keep the MCP port on localhost. Do not ssh -R it, do not proxy it, do not expose it to an LLM that executes arbitrary network tools without review.