The two sandboxes at a glance
Agent half: what it can and cannot do
Each agent half runs as a generated systemd unit insideados-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:
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 withnetwork.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 ablob: document the host builds:
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 ispostMessage 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 onesrequired: 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.
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 reportsupdate_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.