Ctrl K
ComunidadGuías…

docs/THREAT_MODEL.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Threat model

**Applies to:** Ferryman as it actually ships — a signed channel carried between
machines by Syncthing, and a worker that runs an agent CLI locally. The HTTP server
that older parts of this document describe is a second, optional mode; it is covered
further down, under [Server mode](#server-mode-optional).

## The shape of the thing

There is no service in the middle. A project's channel is a folder — orders,
results, reviews, heartbeats, the roster, the ledger — replicated between your own
machines by Syncthing. Every file is named after its only writer, so two machines
cannot produce a conflicting edit of one file. Nothing listens on a port for this to
work, and nothing outside your machines holds any of it.

## Who can write into the channel

**Any Syncthing peer you have shared the folder with.** That is the boundary, and it
is worth being blunt about: Syncthing authenticates devices, so nobody who is not an
approved device can put a file in the channel at all — but every approved device can
write anything, including a file claiming to be from another agent.

That is why writing is not what is trusted. **Consumption is.**

## What is enforced when a record is read

- **Signatures are checked, and an unsigned or invalid order is not worked.** The
  worker verifies before it claims (`crates/ferryman-ops/src/agent.rs:2044`) and
  again before it acts on a claimed task (`agent.rs:2609`). `SignatureCheck` has
  five outcomes — `Valid`, `Unsigned`, `Invalid`, `UnknownSigner`, `KeyChanged` —
  and only `Valid` proceeds. Nothing anywhere softens the other four into "probably
  fine".
- **Keys are pinned on first sight (trust on first use).** An agent's key is pinned
  into the operator-local `attachment/agents-pinned/`, and a key later overwritten
  in the shared folder is reverted to the pin and reported as `KeyChanged`. The
  limit is the first sight itself: a machine that has never met an agent pins
  whatever the channel shows it first. **So signatures prove continuity, not
  identity, until a fingerprint is checked out of band.** Out-of-band key
  distribution is the real fix and is not built.
- **Authority is a renewable lease, not a durable capability.** Any authority a
  human or agent holds over another principal's agents, secrets or membership is
  short-lived, signed by its owner, expires on its own, and is renewed by
  publication into the channel — see
  [ADR 0013](adr/0013-agent-access-grants-are-renewable-leases.md). Revocation means
  "stop renewing", so a machine that has not synced still expires out of authority
  at a known, deliberately small horizon.
- **Secrets are sealed to a recipient and never appear as plaintext in the
  channel.** X25519 + XChaCha20-Poly1305, keyed through HKDF-SHA256 salted with the
  ephemeral and recipient public keys, with the encryption identity a separate
  keypair from the signing key — see
  [ADR 0010](adr/0010-secrets-through-the-channel.md). Secret-use grants fail closed
  at every use. Revealing a secret once always requires rotating the upstream
  credential; no Ferryman control can undo that.
- **No cross-machine clock comparison may release a claim.** Only a machine may
  judge its own runs abandoned, and only about itself — see
  [ADR 0011](adr/0011-recovering-a-dead-worker.md).

## What is not defended

- **A worker runs the agent CLI with the full privileges of the account that
  started it.** This is the central risk in the product and it is not mitigated by
  default. `sandbox` in `agent.toml` (or `ferry enable --sandbox IMAGE`) runs each
  task in a fresh podman or docker container with a network-egress policy instead,
  and that is the mode to use for anything you would not run yourself. The default
  is `Bare` because the default has to work on a machine with no container runtime.
- **The agent CLI is a third party.** Ferryman scrubs secret-named variables out of
  the child environment and passes arguments as vectors rather than through a shell,
  but what the engine does with a prompt is between you and its vendor.
- **Anything an approved Syncthing device does with the folder outside Ferryman.**
  Deleting files, filling the disk, or replicating the folder onward are device-level
  concerns, and the answer is which devices you approve.
- **A stolen machine.** The signing key lives on disk under `.ferryman/keys` at
  0600. Full-disk encryption is yours to have.

## Server mode (optional)

Everything below concerns `ferryman-server`, the HTTP mode described in
[GETTING_STARTED](GETTING_STARTED.md) as "an older integration path". If you are
using the channel — which is the default and what the README documents — none of it
applies to you.

## Assets

Project inputs/results, artifact metadata and contents, project bearer tokens, secret references, policy decisions, and audit history.

## Trust boundaries and controls

- **Client to API:** project bearer token is required; tokens are compared by SHA-256 hash. Run only behind TLS in production.
- **API to worker:** registration requires an operator project token once; it returns an 8-hour, worker-specific token exactly once. That token is stored only as a hash and can access worker protocol routes for its own worker ID—not operator, memory-write, recovery, consent, or outbound-submission routes. Job completion also requires its opaque lease ID.
- **Communications consumers:** a project operator token can configure routes,
  send work, inspect status, and mint an eight-hour actor token. Claim and
  acknowledgement reject project tokens and require the token for the exact
  registered recipient. Actor tokens are stored only as hashes.
- **Portable messages:** inline JSON is limited to 256 KiB and recursively
  rejects common credential-bearing keys. The outer token, runtime state,
  claims, locks, and quarantine never enter the synced folder or the portable Git repository.
- **Transport subprocesses:** Git and GitHub checks have hard timeouts; the Syncthing API probe is loopback-only with a 5-second deadline.
  GitHub privacy and exact inner-origin checks fail closed; failed delivery
  stays in the durable local outbox.
- **Artifacts:** content is hashed, written under the bridge-owned artifact root, and metadata is associated with one project. Paths from requests are never used as filesystem paths.
- **Recovery providers:** raw artifacts are never mirrored. Local-first continuity packs are encrypted before any configured network, Drive, MEGA, or private-Git recovery target receives them. External adapters fail closed until a target policy, credential reference, consent manifest, and remote hash verification are available.
- **Sensitive data:** logs/events redact top-level keys containing `secret`; this is defense-in-depth, not a replacement for application-level data minimization.
- **Destructive/external work:** project submits it with `requires_approval`; v0.1 requires a separate approve transition before dispatch.
- **Agent access grants and invitations:** any authority a human or agent holds
  over another principal's agents, secrets, or membership is a **renewable
  lease**, not a durable capability - short-lived, signed by its owner,
  expiring on its own, renewed by publication into the channel (see
  [ADR 0013](adr/0013-agent-access-grants-are-renewable-leases.md)). This is
  the offline story: revocation means "stop renewing", so a machine that has
  not synced still expires out of authority at a known, deliberately small
  horizon. Secret-use grants additionally fail closed at every use and never
  place plaintext in any channel artifact; revealing a secret once always
  requires rotating the upstream credential, which no Ferryman control can
  undo.

## Non-goals and residual risks

Workers and communications consumers remain trusted execution environments:
Ferryman does not sandbox their local execution or prove external effects are
idempotent. A claim prevents duplicate Ferryman execution, but integrations
must use their own idempotency protection for irreversible external effects. Do
not place production secrets in job or message input or run untrusted workers.
SQLite at-rest encryption and a real identity provider remain deployment
choices. Deploy with TLS, restrictive filesystem permissions, token rotation,
backups, and a recovery drill before handling sensitive project data.