Watch
1
0
Fork
You've already forked RedFlag
0
RedFlag/RAF/security/06-standalone-authority.md

8.4 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 local/mirror artifact (hash mismatch) yes yes — helper rehashes a supplied local path
Registry artifact with no local path not helper-rehashed today not helper-rehashed today
Known-vuln package (OSV) yes, over reported resolved entries yes, over reported resolved entries
Too-new package (age/soak gates) fleet policy not yet applied — local evidence records not_applicable
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, short-lived 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 the current local predicate    (OSV.dev query over resolved entries)
       any vuln in the checked set = full stop — 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 evidence where the current contract permits: closure entry shape, evidence timestamps, host binding, operation, and the presence of an explicit override reason when OSV was vulnerable or unreachable. It does not re-run OSV; the unprivileged agent performs that query and the privileged mint path journals the supplied verdict. Network reachability is not the enforcement boundary here: the current helper unit retains host network access.

Age and soak evidence fields exist, but the local approval handler currently records both as not_applicable. Bringing the fleet age/soak policies into standalone mode remains work.

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 should converge. Fleet and standalone share capability and helper verification primitives, while standalone currently omits the age/soak policies. Closing that drift is required; future gate changes must extend both resolution paths in the same change.
  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 in a reported resolved entry = 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 complete execute-time registry artifact rehashing are not current standalone guarantees.
  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.

Last reviewed: 2026-08-25