Watch
1
0
Fork
You've already forked RedFlag
0
RedFlag/RAF/components/02-agent.md
Fimeg ff2f30f47a v0.2.9.3: device classification + ARM support — Pixel 3 lands
DEVICE-002: ARM machine-ID fallback — device-tree model + /etc/machine-id
combo, then /proc/cpuinfo Serial (all-zero rejected), before the weak
hostname fallback. Hardware-bound IDs on DMI-less devices.

DEVICE-001: agent detects device_type (server/desktop/phone/tablet) from
/sys signals — system battery (scope=Device peripherals excluded, UPS
excluded), DRM connector state, framebuffer min-dimension for phone/tablet
split. Reports device_type/device_model/os_distro in registration and
system-info paths.

SERVER-001: migration 061 — device_type, device_type_manual (operator
override, never agent-written), device_model, os_distro on agents.
effective_device_type computed into every serialized agent.

SERVER-002: PUT /admin/agents/:id/device-type — set/clear override,
enum-validated, journaled.

WEB-001: device-type icons + fleet filter, device model in list, detail
header badge with reclassify dropdown, os_distro surfaced.

INSTALL-003: arm64 install path unblocked — helper (required manifest
component) now cross-built aarch64-unknown-linux-musl via rust-lld in the
server image, signed at boot (helperArches += arm64), listed in the release
manifest. Install template already handled uname -m and pacman.

Plus in-flight: desktop tray wiring, enrollment page polish, CI workflow
updates, RAF session-broker/pacman-scanner docs, native installer scaffold.
2026-07-06 18:21:23 -04:00

6 KiB

Agent Component

Stateless Go executor that polls the server, verifies every command cryptographically, and reports everything back.


Doctrine

The agent does not track lifecycle states, make policy decisions, or hold install privileges on gated ecosystems. The server owns every state transition. The agent's only autonomous decisions are: verify this signature, check this nonce, reject this replay. See core/02-architecture-decisions.


Package Structure

agent/
├── cmd/agent/                  # Entry point, flag parsing, service bootstrap
├── internal/
│   ├── agent/loop.go           # RunAgentLoop → RunPollingLoop — the heartbeat of the process
│   ├── handlers/               # Command handlers, routed by dispatch.go
│   │   ├── dispatch.go         # Command-type → handler routing
│   │   ├── scan.go             # Subsystem scan execution
│   │   ├── install.go          # Legacy-path installs (docker, winget, windows_update)
│   │   ├── dry_run.go          # Dependency resolution + hash discovery
│   │   ├── agent_update.go     # Self-update commands
│   │   ├── heartbeat.go        # Heartbeat + rapid polling
│   │   ├── processes.go        # On-demand process explorer scans
│   │   ├── local_approve.go    # Desktop-self local approval flow
│   │   └── reboot.go, screenshot.go, upgrade_attestation.go
│   ├── scanner/                # apt, dnf, pacman, winget, windows (WUA), detect
│   ├── installer/              # DiscoveryRunner + per-ecosystem installers
│   │   ├── discovery.go        # Single chokepoint for read-only package ops
│   │   ├── apt.go, dnf.go      # Gated: 4-method interface, no mutation
│   │   ├── docker.go, winget.go, windows.go  # Legacy: direct mutation via type assertion
│   │   └── artifact_hash.go    # SHA-256 resolution for closure pinning
│   ├── supplychain/            # consumer.go — capability-token → helper invocation
│   ├── crypto/                 # TOFU pubkey cache, signature/nonce/replay verification
│   ├── instancelock/           # flock (Unix) / named mutex (Windows) — one agent per config
│   ├── circuitbreaker/         # Per-scanner circuit breakers
│   ├── event/                  # TeeLogger (structured dual-output), buffered event reporting
│   ├── system/                 # machine_id, system info, /proc process explorer
│   ├── cache/                  # Hash cache, local state
│   ├── config/                 # config.json, subsystems, kernel enforcement flags
│   ├── localapi/, desktop/     # Local API + desktop tray session integration
│   ├── kernel/                 # eBPF scaffold (inert — not wired to capability model)
│   └── registration/, recovery/, retry/, receipt/, acknowledgment/
└── pkg/windowsupdate/          # WUA COM bindings (vendored fork, Apache 2.0)

The Polling Loop

RunAgentLoop (agent/internal/agent/loop.go) initializes config, instance lock, crypto, and circuit breakers, then enters RunPollingLoop:

  1. Check in — report metrics, buffered events, security events, circuit-breaker health
  2. Fetch commands — verify signature, nonce, timestamp on each; reject replays
  3. processCommands — route through dispatch.go to handlers
  4. processCapabilityTokens — fetch minted tokens, hand to supplychain/consumer.go
  5. Sleep — server-controlled interval (applyServerPolling), jittered

Failure handling: classifyFailure buckets errors into failure classes; delayForFailure applies a unified backoff policy per class (BUG-014). Typed sentinel errors (ErrUnauthorized, ErrRefreshTokenInvalid, ErrMachineMismatch) are terminal — not retried, logged as [CRITICAL].


Two Execution Paths (agent side)

Path Ecosystems Mechanism
Capability gate dnf, apt Token fetched in loop → consumer.ProcessTokensudo systemd-run --piperedflag-helper verifies + executes. Agent never runs the install command.
Legacy command docker, winget, windows_update Signed command → handler → installer mutation method (type-asserted).

Discovery (scan, dry-run, hash-resolve) always runs unprivileged through DiscoveryRunner. Sudoers grants only discovery commands plus the single helper invocation line — zero sudo otherwise.

Cross-references:


Verification (every command, no exceptions)

  • TOFU pubkey cache (crypto/pubkey.go) — keys cached by key_id; unknown signer triggers re-fetch, no restart needed
  • Signature — Ed25519 over the v3 message format
  • Nonce + timestamp — 10-minute validity window, executed-nonce tracking, replay rejection
  • Signing-required is doctrine, not config. There is no skip path.

Cross-references:


Resilience Machinery

  • Instance lockGlobal\RedFlagAgent_v1 mutex / flock; prevents two agents racing one config.json and burning refresh-token rotations (security/03-refresh-tokens)
  • Circuit breakers — fragile scanners (notably WUA) trip open instead of hammering; health reported to server
  • TeeLogger — every loop event goes to both structured local log and server-bound buffer; tracker save failures tee inward (ETHOS #1)
  • At-least-once acksacknowledgment/tracker.go persists until the server confirms result-recorded

Last reviewed: 2026-06-14