Skip to main content
A plugin runs third-party code next to a flying vehicle. Every plugin is sandboxed, every privileged call is gated by a capability the operator granted, and every archive is signed and checked before a file lands on disk. This page is the threat-model summary: what each half can reach, what it cannot, and the worst case if a plugin turns out to be malicious or buggy. The mechanics live in Concepts, Permissions, the agent plugin deep dive and the GCS plugin deep dive.

The two sandboxes at a glance

Agent half: what it can and cannot do

Each agent half runs as a generated systemd unit inside ados-plugins.slice, under its own system user ados-plg-<hash8> in the ados-plugins group. There is no in-process mode. Every unit carries these directives:
The capability-dependent part of the unit is re-rendered on every grant and revoke. Without network.outbound the unit gets RestrictAddressFamilies=AF_UNIX and IPAddressDeny=any; a hardware grant such as hardware.i2c adds the matching DeviceAllow= line; filesystem.host adds /srv /mnt /media /boot to ReadWritePaths and relaxes ProtectHome to read-only. A Python plugin’s ExecPaths also includes the agent’s Python environment.

The loopback guard

The agent’s own services treat a caller on the node as on-box or as a lifeline peer. A plugin with network.outbound must not inherit that trust. An nftables rule drops every TCP and UDP packet a plugin process (any user in the ados-plugins group) sends to an address local to the node: loopback, the access-point and USB addresses, link-local, and every interface address. The exceptions are DNS (port 53) and the plugin’s own declared listen_ports. Every plugin unit reloads the rule before it starts. If the rule cannot load (for example nft is missing), the agent fails closed: a network.outbound grant is refused, a unit that already holds one is rendered with the no-network sandbox, and /api/status/full reports "plugin_loopback_guard": "unavailable". When the rule is in place the field reads "active".

Blast radius if the agent half is malicious

The worst a compromised agent half can do is bounded by its granted capabilities. With only low-risk grants (telemetry.read, event.publish) it can read telemetry and waste its own CPU budget. A plugin granted the vehicle-moving capabilities is a different matter: mavlink.write, msp.write, flight.guided_setpoint, flight.rate_setpoint (critical) and estimator.pose.inject can each move the vehicle. That is why they carry a high or critical risk badge and the operator approves them explicitly. The sandbox keeps a buggy or hostile plugin from reaching the rest of the node; it does not make a granted flight-control capability harmless.

GCS half: what it can and cannot do

The GCS half runs in an iframe with the strictest sandbox that still allows JavaScript, on a blob: document the host builds:
The frame document carries a CSP with connect-src 'none', and a guard script removes WebRTC and nested frames, so the iframe has no network egress at all.

Blast radius if the GCS half is malicious

A compromised GCS half is trapped in a null-origin iframe that cannot read the host session, cannot reach any network, and cannot navigate the parent page. Its only output is postMessage envelopes, and the bridge checks the capability each one actually needs. The damage ceiling is the set of GCS capabilities the operator granted (for example command.send to its drone, or mission.write to edit a plan). Route any network I/O through the agent half.

Capability least privilege

Declare the smallest permission set that makes your plugin work, mark only the load-bearing ones required: true, and leave the rest optional so an operator can run a reduced version. The catalog, the risk levels and which capabilities are enforced where are on Permissions; tokens are described in Concepts.

The trust boundary: first-party and third-party signers

Every .adosplug is an Ed25519-signed zip, verified before anything is unpacked.
A revoked signer or a revoked archive digest is refused in every mode, including with signature enforcement relaxed. First-party status is granted only to the ids on the in-code allowlist, and only when the archive’s signature verifies against the key pinned in the agent for that id. A key file planted under a first-party name in /etc/ados/plugin-keys/ verifies only as a third party. Write access to /etc/ados/plugin-keys/ is still root-equivalent trust for third-party code: whoever can add a key file can make the node accept archives that key signs, so protect that directory like root.
isolation: inprocess (agent) parses but is refused at install for every plugin, first-party included. gcs.isolation: inline runs the GCS half in Mission Control’s own page, outside the iframe sandbox, so it is first-party only. A third-party archive that declares it is refused at install.
Signature enforcement can only be relaxed on the agent itself, for a bench node. The procedure and its limits are in Bench install and signing.

The operator install gate

No plugin installs silently. The install is two-stage: the host parses the archive in memory, verifies the signature and returns a summary (the requested permissions, the signer and the risk); only after the operator approves does it unpack, generate the unit and write the install record. A plugin that never gets past review leaves no trace. The dialog is described in Permissions. Auto-update does not apply a version that asks for new permissions; the node reports update_available with the new permissions, and the operator reinstalls to approve them. An operator can revoke any granted capability at any time from Mission Control or with ados plugin perms <id> --revoke <cap>; the host re-mints the plugin’s token at once without it.

Why each layer exists

The layers are independent. If one fails, the next still holds.
  • Signing stops a tampered or unknown-origin archive from installing. It is the supply-chain wall.
  • The sandbox (hardened unit plus cgroup, or null-origin iframe plus CSP) bounds a plugin that installed but turns out to be buggy or hostile. It is the runtime wall.
  • Capabilities make the plugin’s reach explicit and operator-approved. They are the authority wall.
  • The install gate keeps a person in the loop for every grant.

Next steps

Permissions

The full capability catalog with risk levels.

Signing keys

Generate, enrol and rotate a publisher key.

Revocation and incidents

Pull a signer, an archive or a release from the field.

Agent plugin deep dive

How the plugin host runs an agent half.
Last modified on October 11, 2026