Watch
1
0
Fork
You've already forked RedFlag
0
RedFlag/RAF/security/06-standalone-authority.md
Fimeg 8d54fec8eb raf: desktop component + local trust boundary + fleet-join maintainability
- components/05-desktop.md: the tray as credential-less localapi client; no-token surface marked as open decision (peer identity unanswered), not doctrine
- trust-boundaries: Local boundary section — the kernel is the middleware, group stamping at login is the sharp edge
- standalone-authority: fleet join must stay idempotent, single-source gated, and tested in both directions
2026-06-12 13:26:54 -04:00

7.9 KiB

Standalone Authority (Local Mode)

Drafted 2026-06-10. Status: APPROVED 2026-06-10 (Casey signed off on all three open questions; resolutions inlined below). Companion to 05-supply-chain-gate.md (the gate contract is unchanged by this doc). Build tracking: docs/tasks/FEAT-003-standalone-local-authority.md.

The problem

The tray app needs a local approval flow ("approve update → it installs") on a host with no fleet server. In fleet mode the trust chain is:

operator → server (authority, off-host) → agent (consumer) → helper (enforcer)

The authority lives on a different machine than the thing being updated. That is the real boundary: a fully compromised agent host still cannot mint a capability token.

In standalone mode the server does not exist. Something on the same host must mint tokens. No arrangement of processes on one host reproduces the off-host boundary — local root can always reach the key. Pretending otherwise (e.g. "the agent signs its own tokens, the helper checks them") turns the token into self-attestation theater. This doc is honest about what survives and designs for that.

What the gate still buys on one host

The boundary that is preservable locally is the OS privilege boundary:

Defended against Fleet mode Standalone
Tampered artifact (hash mismatch) yes yes — helper verification unchanged
Known-vuln package (OSV) yes yes — gates run before mint
Too-new package (age/soak gates) yes yes
Unprivileged local malware minting installs yes yes — mint key is root-owned
Compromised agent process yes (server refuses) yes — agent user cannot read mint key
Local root attacker yes (authority off-host) no — out of scope, say so in docs

A compromised agent (unprivileged) in standalone can request a mint, exactly as a compromised agent in fleet mode can request approval — and the same gates refuse it. What standalone loses is only the root-compromise case, which on a single sovereign host is game over for every other tool too.

Design

Authority placement: privileged mint, root-owned key

Standalone mint runs as a separate privileged invocation of the helper (redflag-helper --mint), the same sudo systemd-run pattern the executor already uses. Rationale:

  • Keeps the artifact set at three (agent, server, helper) — no fourth binary.
  • The helper is already the audited, privileged, network-less Rust component ("auditable in one sitting"). Mint is ~the token struct it already parses, signed instead of verified.
  • Minter-equals-verifier is acceptable here only because both already run as root on the same host; the boundary being enforced is root-vs-unprivileged, not minter-vs-verifier. In fleet mode this collapse stays forbidden (constraint #2 of 05).

Keys:

  • authority_local.key — Ed25519, generated at install time (same provisioning step that creates the redflag-local group / helper sudoers). Root-owned, 0600, outside the agent's readable tree. Never leaves the host.
  • The helper's pinned keyring gets the corresponding public key with its key_id fingerprint, exactly like a server key. Verify path is byte-identical to fleet mode.

Flow

tray (redflag-local member)
  → POST /v1/updates/:id/approve            (agent local API, group ACL boundary)
  → agent: resolve closure via DiscoveryRunner (dry-run, hash-resolve — read-only)
  → agent: run gate predicates locally       (OSV.dev query, age gate, soak gate)
       any vuln in closure = full stop, same as ApproveUpdate's 409 — no silent waiver;
       override requires explicit reason, journaled locally
  → agent: write mint request file (closure + gate evidence + operator + reason)
  → sudo systemd-run redflag-helper --mint <request>   (narrowed sudoers entry)
       helper re-derives closure_hash, re-checks gate evidence freshness window,
       signs with authority_local.key, emits token, journals the mint
  → token → normal consumer path → helper verify + execute (unchanged)

The mint step re-validates rather than trusts the agent's gate verdicts where it can do so without network (closure hash shape, evidence timestamps, forward-only version check). It cannot re-run OSV (network-less) — the OSV verdict is part of the journaled evidence, so a lying agent user leaves a tamper-evident trail and still cannot bypass artifact-hash verification at execute time.

Doctrine carried over unchanged: signing required, forward-only, no skip-verification path, no doctrinal knobs.

Audit

Every standalone approval/mint/execute writes to a local append-only journal owned by root (mirror of the server's security-event journal). The tray reads it through a read-only local API endpoint. When the host later joins a fleet, the journal is uploaded once during re-provisioning so history survives the mode switch.

Fleet join ("join fleet later")

Registration code + one-time 2FA → agent registers against the server → helper keyring is replaced: server authority key(s) in, local authority key retired and its private half destroyed (journaled). Single authority per mode — no dual-mint window, no local fallback authority in fleet mode. A fleet host that loses its server does what it does today: nothing installs until the server returns (constraint #3's verified-cache fallback applies only to already-minted operations).

Transition maintainability (Casey, 2026-06-12). Fleet join is a supported lifecycle path, not a one-off migration script — it must hold to the same standard as install/upgrade: idempotent, re-runnable, verified by the post-join healthcheck rather than assumed (docs/tasks/INSTALL-001 is the enforcement pattern). Two standing rules keep it from rotting:

  1. Gate logic stays single-source. Standalone and fleet share the same gate code (vuln full-stop, soak, age, hash verification). When a gate gains a fleet-side capability (e.g. DB-backed policy config), the standalone resolution path must be extended in the same change — a gate that behaves differently per mode is drift, not configuration.
  2. The join flow is exercised, not trusted. Keyring replacement, key destruction, and journal upload need test coverage that runs both directions of the matrix (fresh-fleet install vs standalone-then-join must converge on identical end state). If the two end states can diverge, the transition has already broken — it just hasn't been noticed yet.

Non-goals

  • No local approval authority in fleet mode (server remains sole authority).
  • No "lite" trust mode — gates are not weakened because the host is standalone.
  • No network listener for the mint path; the local group ACL + sudoers narrowing is the entire request surface.
  • Tray never touches keys, tokens, or the mint path directly — it only calls the agent's local API.

Resolved questions (Casey, 2026-06-10)

  1. Mint placement: redflag-helper --mint. Artifact set stays at three. Two keys keep roles distinct (mint key root-owned 0600, verify key in pinned keyring); SEC-022 attests both invocation modes.
  2. OSV in standalone: best-effort with honest verdict. Vuln found = full stop. OSV unreachable = explicit operator acceptance of "closure unverified" with reason, journaled — mirrors the fleet unverified hold from v0.2.3.1. Age/soak gates and execute-time hash verification never relax.
  3. Gate-evidence freshness window: 15 minutes, hard-coded. Not configurable (no doctrinal knobs); expired evidence means the agent re-resolves and re-checks.

Cross-refs

  • 05-supply-chain-gate.md — token contract, constraints (esp. #2, #4, #6).
  • docs/tasks/FEAT-002-local-agent-api.md — local API surface this builds on.
  • docs/tasks/SEC-023 — package-mutation boundary (mint path must not weaken it).
  • docs/tasks/SEC-022 — binary mutual attestation (helper trust file).
  • docs/tasks/THREAT-001 — must document the standalone trust model table above.