feat(#1997): add prettier markdown formatter to treefmt

- .prettierrc: proseWrap=preserve (no prose reflow)
- .prettierignore: exclude hivectl-cli.md (auto-generated) + 11 docs
  with multi-line list-item continuations prettier would strip to col 0
  (CommonMark limitation in prettier's list handling)
- format 16 markdown files: cosmetic only (*→_, table alignment,
  heading normalisation) — verified no broken continuations, idempotent
This commit is contained in:
atlas 2026-07-02 19:55:26 +02:00 committed by mara
commit 8406a45275
17 changed files with 254 additions and 230 deletions

View file

@ -15,10 +15,10 @@ An agent is **trusted code running inside its own nspawn container**. The
boundary that matters is the container: a sub-agent cannot see the host
netns, another agent's container, or another agent's state dir. Within its
own container the agent is privileged — it has **passwordless `sudo` by
default**. Isolating credentials *from the agent itself* is therefore **not a
default**. Isolating credentials _from the agent itself_ is therefore **not a
goal**: an agent can read its own tokens, its own `/home/<name>/.claude`, and
run arbitrary commands as root inside its container. (The narrow exception is
*cross-tenant* leakage — e.g. the unsandboxed-nix-build `0600` token policy
_cross-tenant_ leakage — e.g. the unsandboxed-nix-build `0600` token policy
below stops a build's nixbld user reading the agent's own forge token, and the
state-file endpoint stops one agent proxying another's files. Those harden the
boundary; they do not sandbox the agent from itself.)
@ -36,7 +36,7 @@ Each agent gets its own scoped credentials, never shared:
- **matrix token** → that agent's matrix account only.
So a compromised/confused agent's reach on the forge or matrix is bounded by
*its own* account's scope, not the swarm's. This is the main thing standing
_its own_ account's scope, not the swarm's. This is the main thing standing
between "one agent does something dumb" and "the whole hive is affected".
### Threat model: prompt injection → confused deputy
@ -44,7 +44,7 @@ between "one agent does something dumb" and "the whole hive is affected".
The realistic adversary **never needs to breach the container**. They supply
**untrusted input the agent reads and acts on**: a poisoned issue or PR
comment, a cloned repo's README/CI, a scraped webpage, a crafted matrix
message. The agent is the trusted, capable party; the *input* is the
message. The agent is the trusted, capable party; the _input_ is the
untrusted part. A successful injection turns the agent into a **confused
deputy** — it uses its legitimate capabilities (push, comment, deploy, run
shell) on the attacker's behalf.
@ -52,7 +52,7 @@ shell) on the attacker's behalf.
Mitigations are therefore about **bounding capability and inserting human
checkpoints**, not about sandboxing the agent from its own tools:
- **Operator merges, not the agent** — an agent may *push* branches, but a
- **Operator merges, not the agent** — an agent may _push_ branches, but a
**human (the operator) merges the PR**, keeping a person in the loop on the
highest-value action. On the **internal forge this is technically enforced,
not just convention**: agents can't create repos (`max_repo_creation = 0`),
@ -76,7 +76,7 @@ accepting**. The rule of thumb:
If an agent can deploy to prod, you are accepting the risk of a dropped
production database (via injection or plain error). If that's unacceptable,
the answer is *don't grant the capability* — not "grant it and hope the
the answer is _don't grant the capability_ — not "grant it and hope the
sandbox holds", because there is no sandbox between an agent and the tools you
handed it.
@ -101,7 +101,7 @@ before touching the filesystem:
(canonical host form or the in-container view `/agents/<n>/state/`)
- `/var/lib/hyperhive/shared/` — shared docs (`/shared/` in-container)
`/state/...` without an agent prefix is explicitly *not* accepted — it is
`/state/...` without an agent prefix is explicitly _not_ accepted — it is
ambiguous from the host's perspective.
Defense-in-depth layers (in order):
@ -181,22 +181,22 @@ for every operation that genuinely requires root.
**Narrow interface** — `PrivRequest` variants map 1:1 to specific
known operations; there is no arbitrary command pass-through:
| Operation | What it runs |
|-----------|-------------|
| `StartContainer` / `StopContainer` / `KillContainer` | `nixos-container start/stop/kill <name>` |
| `CreateContainer` / `UpdateContainer` | `nixos-container create/update <name> --flake <ref>` |
| `DestroyContainer` | `nixos-container destroy <name>` |
| `ListContainers` | `nixos-container list` |
| `ReadContainerJournal` | `journalctl -M <container> -n <n> [filters...]` |
| `ReloadGatewayNginx` | `systemctl -M hive-gateway reload/start/reset-failed nginx` |
| `WriteNspawnFlags` | write `/etc/nixos-containers/<container>.conf` (bind-mount list + network isolation vars) |
| `WriteResourceLimits` | write `CPUQuota=`/`MemoryMax=` systemd drop-in for agent container |
| `RemoveServiceDropin` | remove `container@<name>.service.d/` drop-in on destroy |
| `DaemonReload` | `systemctl daemon-reload` |
| `ChownSocketDir` / `ChmodSocketDir` | chown/chmod `/run/hive-agent/<name>/` socket directory |
| `RunForgeAdmin` | `nixos-container run hive-forge -- runuser -u forgejo -- forgejo admin <args>` |
| `WriteAgentForgeToken` / `WriteAgentMatrixToken` | write `0600` credential file into agent state dir |
| `RestartMatrixDaemon` | `systemctl --machine=h-<name> restart hive-matrix-daemon.service` |
| Operation | What it runs |
| ---------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `StartContainer` / `StopContainer` / `KillContainer` | `nixos-container start/stop/kill <name>` |
| `CreateContainer` / `UpdateContainer` | `nixos-container create/update <name> --flake <ref>` |
| `DestroyContainer` | `nixos-container destroy <name>` |
| `ListContainers` | `nixos-container list` |
| `ReadContainerJournal` | `journalctl -M <container> -n <n> [filters...]` |
| `ReloadGatewayNginx` | `systemctl -M hive-gateway reload/start/reset-failed nginx` |
| `WriteNspawnFlags` | write `/etc/nixos-containers/<container>.conf` (bind-mount list + network isolation vars) |
| `WriteResourceLimits` | write `CPUQuota=`/`MemoryMax=` systemd drop-in for agent container |
| `RemoveServiceDropin` | remove `container@<name>.service.d/` drop-in on destroy |
| `DaemonReload` | `systemctl daemon-reload` |
| `ChownSocketDir` / `ChmodSocketDir` | chown/chmod `/run/hive-agent/<name>/` socket directory |
| `RunForgeAdmin` | `nixos-container run hive-forge -- runuser -u forgejo -- forgejo admin <args>` |
| `WriteAgentForgeToken` / `WriteAgentMatrixToken` | write `0600` credential file into agent state dir |
| `RestartMatrixDaemon` | `systemctl --machine=h-<name> restart hive-matrix-daemon.service` |
**Container allowlist** — every request is validated against an
allowlist before any operation: only names matching the agent-name
@ -211,8 +211,8 @@ writes to only the paths `hive-priv` legitimately needs.
### Privilege boundary summary
| Component | Runs as | Privilege needed for |
|-----------|---------|----------------------|
| `hive-c0re` | `hive-core` | broker, HTTP dashboard, scheduling, approvals |
| `hive-priv` | `root` | container lifecycle, journal reads, bind mounts, cred writes |
| `hive-ag3nt` (per-container) | per-agent user | turn execution, MCP serving |
| Component | Runs as | Privilege needed for |
| ---------------------------- | -------------- | ------------------------------------------------------------ |
| `hive-c0re` | `hive-core` | broker, HTTP dashboard, scheduling, approvals |
| `hive-priv` | `root` | container lifecycle, journal reads, bind mounts, cred writes |
| `hive-ag3nt` (per-container) | per-agent user | turn execution, MCP serving |