Plugin security¶
A plugin is Python, and runs with the rights of whoever runs noodlelab. Python cannot be sandboxed from inside its own process, so noodlelab does not pretend that a plugin is safe because it was checked. It makes it hard to publish or swap in harmful code, shows what a plugin does before anyone trusts it, and lets a harmful one be revoked quickly.
Trust levels¶
Level |
Meaning |
|---|---|
official |
noodlelab’s own: the packs that come with it, and its official plugins (noodlelab-maths…) installed from PyPI or an index that vouches for them. A package that only borrows the name, from a git repository, is not |
reviewed |
a person checked this exact version (a new version needs a new review) |
listed |
in an index, and passed its automatic checks; nobody has read the code |
unverified |
from a git URL or a folder: nothing has checked it |
By default a single user on their own computer may install anything, after
confirming an unverified plugin; a shared server (with logins) installs only
plugins listed in an index. Admins set both in Admin → Plugins → Policy
(plugin_sources, min_plugin_trust).
Before installing¶
The plan. Every install shows each plugin, where it comes from (the repository and the commit), its trust level, the permissions it asks for, what the index’s checks found, and every plugin it pulls in.
Permissions. A plugin declares what it does besides computing: network, starting programs, native code, files outside the workspace, environment variables. The index and
check-pluginread its code and refuse a version that does more than it declares. Declared permissions are shown, and stay off until an admin grants them.Signed wheels. A plugin from an index installs from the wheel its release workflow built: its SHA-256 must match the index, and its GitHub build attestation is verified when the
ghCLI is installed. No code runs while it is installed.Dependencies. The new Python packages a plan brings in are looked up in OSV; one known to be malicious stops the install.
Pins. The lockfile records each plugin’s commit (and wheel hash). A tag that moves to another commit is refused on upgrade and reported by health, and an index freezes a plugin whose tag moved or whose repository changed owner.
Graphs never install anything. A graph lists the plugins it needs, and the editor and agents say what is missing, but installing is always a person’s decision. An agent over MCP can install only listed plugins that ask for no permissions.
After installing¶
Advisories. An index publishes advisories against harmful versions. Health shows an installed one as revoked (red in the editor), and it is never installed again.
Disable. An admin can disable a plugin at once: its nodes stop being offered, and after a restart its code no longer runs.
Provenance. Every run records each plugin’s version, commit and source, so a result can be traced to the exact code that computed it.
While it runs: the sandbox¶
Plugins trusted less than the in_process_trust setting (by default, every
plugin but noodlelab’s own) never run their nodes’ code in noodlelab’s
process. A run that uses one goes to a runner, a separate process that
confines itself before it imports any plugin:
In the sandbox |
|
|---|---|
Files |
reads Python’s and the system’s own files and the workspace; writes only the workspace and its own folder (Landlock). noodlelab’s home (users, tokens, storage credentials), your home folder and other workspaces are out of reach |
Network |
none: a network namespace with no interface, and Landlock refusing TCP, unless an admin granted a plugin in the run |
Programs |
none can be started, unless a plugin in the run was granted |
Environment |
clean: no tokens, cloud credentials or |
Configuration |
its own: the run’s settings, without users, tokens or storage mounts (a sandboxed run reads only the workspace’s own files) |
An audit hook in the runner stops a plugin doing what it was not granted and names it: the node fails with “The thermo plugin tried to use the network, and it has not been granted the ‘network’ permission”, and the plugin’s health says so. The editor shows sandboxed runs like any other; the provenance of each records how it was isolated, and it has the same memory limit as every run (see Runs and memory). In noodlelab’s own process, a sandboxed plugin’s nodes are known by their specs only: their edit-time previews and checks are off, and calling one fails.
A sandboxed plugin is still imported in noodlelab’s process (to learn its nodes and the types it registers). Before that happens the first time for each version, it is imported in a runner that records what its import does; a plugin whose import reaches for the network, starts a program or loads native code without declaring it is not loaded.
The sandbox needs Linux 5.13 or later (Landlock; 6.7 or later also refuses
TCP by itself), and a network namespace when unshare can make one. It works
in Docker when the kernel has Landlock. Elsewhere (macOS, Windows) runs cannot
be isolated: with sandbox = "auto" plugins then run in noodlelab’s process
(the Plugins page says so), and with sandbox = "required" they do not run at
all.
What the sandbox does not cover:
a plugin’s import-time code, and the types, codecs and previews it registers, run in noodlelab’s process (checked by the import runner, which a determined plugin could detect);
UDP, when no network namespace can be made (Landlock covers only TCP);
sandboxed runs cannot read remote storage mounts;
the audit hook sees Python, not native extensions.
For a shared server, run noodlelab as a user with access to nothing else,
keep in_process_trust at official, and set sandbox to required.