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
Amanifest.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:- Pack. Zip the plugin tree (manifest plus the agent and GCS
halves) into a
.adosplug. No hashes are written into the manifest. - 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 aSIGNATUREfile 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 theados-pluginsgroup, inside theados-plugins.slicecgroup with its CPU, memory and process caps, and under systemd hardening such asNoNewPrivileges=yes,ProtectSystem=strictand an exec allowlist. It reaches the host only through its Unix domain socket, and it cannot open network sockets unless it was grantednetwork.outbound. The security model lists the full unit. - GCS half: an
<iframe sandbox="allow-scripts">with noallow-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 withconnect-src 'none', so the frame has no network egress. The plugin reaches the host only throughpostMessage, and the host validates every envelope.
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.