🔒 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 -> |
|
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 |
Bound to |
Blender workstation <-> local reload/debug server |
UNIX loopback TCP socket on port |
Bound to |
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.ppkkeys 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(orkeychain,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 -Lfrom a terminal (which does honorknown_hosts), then point the add-on atlocalhost:<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-serverruns in the remote user’s shell and the add-on launches it with no--hostflag at all, so the binary falls back to its own default bind address of127.0.0.1and the socket is loopback-only on the remote. (Do not read the missing--hostinpsas a failure to set it; check the bind withssinstead.) The add-on reaches it through the SSH session’sdirect-tcpipchannel; 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 relaunchingppf-cts-server --host 0.0.0.0outside the add-on).Docker / Docker over SSH. Inside the container the add-on launches
ppf-cts-server --host 0.0.0.0because docker-p HOST:CONTAINERforwards traffic to the container’s external interface (eth0), not loopback. External reachability of the host is then controlled by the-pmapping you choose: the container must publish the server port (-p 9090:9090or 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.0binds every interface of the Docker host directly and no-pmapping 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:9090binds 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:9090so 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 |
|---|---|
|
Ignored. See Host-key verification. |
|
Ignored. |
|
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. |
|
Ignored. Bring up the jump yourself with |
|
Ignored. paramiko will try the configured key, then every key the agent offers, then any |
|
Ignored. paramiko picks the auth method. |
|
Ignored as an |
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.
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
localhostonly (see Port usage at a glance). Do not forward the MCP port overssh -R,ngrok,gh codespaces, or any reverse proxy unless the machine is disposable.The HTTP transport refuses any request whose
Originheader is notlocalhost,127.0.0.1, or::1with a403 Origin not allowed. That is DNS-rebinding protection: it stops a web page you happen to visit from drivingrun_python_scriptthrough your browser. A request with noOriginat 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
8765is 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, butblender_addon/launch.shstarts 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 noOrigincheck and no authentication. Stop it when you are done debugging.LLM outputs wired into
run_python_scriptare 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.tomlon Unix). The add-on does not enforce this.When sharing a profile with teammates (check-in, chat, paste), strip the
username,key_path, andproxy_jumpfields first. The host + container + port fields are usually fine; the identity fields are not, andproxy_jumpis both: it names your bastion host and can carry auser@of its own.The private key file itself should also be mode
600and 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#
Use an Ed25519 key dedicated to the solver host.
Make sure the key is either unencrypted-but-safeguarded or loaded into
ssh-agent.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.Point the add-on at
localhost:2222, leaving the weak host-key policy applied only to loopback.On the remote, verify the server port is not reachable from the outside. With plain SSH the add-on now binds
ppf-cts-serverto127.0.0.1so loopback-only is the default; confirm withss -tlnp | grep 9090(you should see127.0.0.1:9090, not0.0.0.0:9090).If you use Docker-over-SSH, publish the host-side port as
127.0.0.1:9090:9090(the in-container bind is0.0.0.0by necessity, but the host mapping can still be loopback-only).Keep the MCP port on
localhost. Do notssh -Rit, do not proxy it, do not expose it to an LLM that executes arbitrary network tools without review.