Watch
1
0
Fork
You've already forked RedFlag
0

RAF: full docs pass — components, flows, security, scanners, reference, testing, verification, overview

This commit is contained in:
Fimeg 2026-06-11 11:32:21 -04:00
commit a19dcf4f14
34 changed files with 5085 additions and 28 deletions

View file

@ -0,0 +1,137 @@
# 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).
## 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.