Skip to main content
A plugin has up to two halves: an agent half that runs on the drone or ground station node, and a GCS half that runs in the Mission Control browser. The agent half is written in Python (the ados.sdk package) or Rust (the ados-sdk crate); the GCS half is written in TypeScript (the @altnautica/plugin-sdk package). One signed archive carries both halves and one manifest.yaml describes them. Four primitives describe every plugin: a manifest, a signature, a sandbox and a set of capability tokens.

Manifest

A manifest.yaml at the root of the archive declares everything the host needs to install the plugin: the id, the version, which halves (agent, gcs, or both), the permissions each half requests, and the contributions the GCS half mounts. The manifest carries no per-asset hash list. Asset integrity is bound by the canonical payload hash that the signing step writes into the separate SIGNATURE file. The agent parses and validates the manifest in memory before unpacking; a malformed manifest is rejected before any file is written. The strict schema at schemas/plugin-manifest.schema.json is what ados plugin lint, ados plugin sign and ados plugin test enforce, so validate with them before you ship. See the manifest reference for the full field list.

Signature

The archive is an Ed25519-signed zip. Two steps produce it:
  1. Pack. Zip the plugin tree (manifest plus the agent and GCS halves) into a .adosplug. No hashes are written into the manifest.
  2. Sign. Compute the canonical payload hash (a SHA-256 over the path-sorted <path>\n<sha256-hex> of every archive entry except the signature file), sign that 32-byte digest with an Ed25519 private key, and write a SIGNATURE file into the archive. It has two lines: the signer id, then the base64 signature.
ados plugin sign does both steps from a plugin directory. On install the agent recomputes the payload hash and verifies the signature against the public key enrolled for that signer at /etc/ados/plugin-keys/<signer-id>.pem. The first-party signers (altnautica-2026-A and altnautica-2026-B) are pinned in the agent itself. A third-party signer is trusted once its public key is enrolled on the node. Unsigned archives install only on an agent whose services run with ADOS_PLUGIN_REQUIRE_SIGNED=0; the agent logs a warning and records the plugin with no signer, shown as UNSIGNED. Use that only on a bench node; see Bench install and signing.

Sandbox

Each half runs in its own sandbox.
  • Agent half: a systemd unit that the plugin host (ados-plugin-host) serves. A Python plugin runs through the plugin runner; a Rust plugin (runtime: rust) runs its own compiled binary. Either way the process runs as the plugin’s own system user in the ados-plugins group, inside the ados-plugins.slice cgroup with its CPU, memory and process caps, and under systemd hardening such as NoNewPrivileges=yes, ProtectSystem=strict and an exec allowlist. It reaches the host only through its Unix domain socket, and it cannot open network sockets unless it was granted network.outbound. The security model lists the full unit.
  • GCS half: an <iframe sandbox="allow-scripts"> with no allow-same-origin. The null origin means no shared cookies, no shared storage and no DOM access to the host. The frame document carries a Content-Security-Policy with connect-src 'none', so the frame has no network egress. The plugin reaches the host only through postMessage, and the host validates every envelope.
The sandbox is what turns a manifest’s permissions into real authority: a plugin can do only what it was granted.

Capability tokens

Permissions in the manifest become capabilities at runtime. When the operator grants a permission, the host mints a capability token bound to the plugin id, the granted capability set and the plugin’s session, with an expiry. It is signed with HMAC-SHA256 under the host’s persisted issuer key, and each token carries a session id, so it does not carry over to a new session. A token lives 10 minutes; the host re-mints it at half that, so a running plugin never needs to refresh it itself. The plugin presents the token on every privileged call, and the host re-checks the HMAC, the expiry and the capability before the call runs. Tokens expire, bind to one session, and let the host audit which call exercised which permission. A revoke takes effect at once: the host re-mints the plugin’s token without the revoked capability.

Next steps

Security model

The threats each boundary defends against.

Permissions

The capability catalog and risk levels.

GCS bridge methods

The methods a GCS half calls and the capability each needs.

Your first plugin

Put the four primitives to work.
Last modified on October 11, 2026