> ## Documentation Index
> Fetch the complete documentation index at: https://docs.altnautica.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Concepts

> Manifest, signing, sandboxing, and capability tokens. The four primitives every plugin author works with.

A plugin has up to two halves: an **agent half** that runs on the drone
or ground 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 in ADOS: a **manifest**, a
**signature**, a **sandbox**, and a set of **capability tokens**. If
you understand those four, the rest of the system follows.

## 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 slots the GCS panels mount into. 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 (see below).

The host validates the manifest against
`schemas/plugin-manifest.schema.json` before unpacking anything. A
malformed manifest is rejected before any file is written to disk.

See [manifest reference](/developers/manifest) 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
   at this step.
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 itself), sign that 32-byte digest with an
   Ed25519 private key, and write a separate `SIGNATURE` file into the
   archive. The `SIGNATURE` file has two lines: the signer key id, then
   the base64 signature.

On install the agent recomputes the payload hash and verifies the
signature against its trust list. First-party publishers (e.g.
`altnautica-2026-A`) are pre-trusted. Third-party keys must be added
explicitly. Unsigned archives install only when the agent is in
**developer mode** (red banner, manual toggle).

## Sandbox

Each half runs in a separate sandbox.

* **Agent half**: a subprocess under `ados-supervisor`. A Python plugin
  runs through the plugin runner; a Rust plugin (`runtime: rust` in the
  manifest) runs its own compiled binary directly. Either way the
  subprocess sits in the shared `ados-plugins.slice` cgroup, which caps
  CPU percent, memory, and PIDs. The generated systemd unit adds
  privilege hardening: `NoNewPrivileges=yes`,
  `ProtectSystem=strict`, `ProtectHome=yes`, `PrivateTmp=yes`,
  `LockPersonality=yes`, `RestrictRealtime=yes`, and
  `RestrictSUIDSGID=yes`. The plugin reaches the host only through a
  Unix domain socket; it cannot open arbitrary network sockets unless it
  declared `network.outbound`.
* **GCS half**: an `<iframe sandbox="allow-scripts">` with no
  `allow-same-origin`. Null origin means no shared cookies, no shared
  storage, no DOM access to the host. The host serves the plugin bundle
  with a Content-Security-Policy that constrains what the frame can
  load. The plugin reaches the host only through `postMessage`, and the
  host validates every envelope.

The sandbox is the boundary that turns a manifest's permissions into
real authority. The plugin cannot lie about what it can do.

## Capability tokens

Permissions in the manifest become **capabilities** at runtime. When
the operator approves a permission, the host mints a capability
token: a short string bound to the plugin id, the granted capability
set, the plugin's session, and an expiry, signed with HMAC-SHA256
under a per-session secret. The token carries a TTL (10 minutes by
default). The plugin presents the token (or an automatically attached
one) on every privileged call, and the host re-checks the HMAC and the
expiry on the critical path before the call runs.

Why tokens and not just a permission flag? Because they expire.
Because they bind to a session, so a stolen token does not work in a
later session. Because they let the host audit which call exercised
which permission.

For the wire shape and the validation pipeline, see
[event hooks](/developers/event-hooks) and the
[permissions reference](/developers/permissions).
