Skip to content

Security

Fjarr grants remote eyes, hands, a shell, and (later) software installation on machines that move in the physical world. Security is architecture here, not review.

Assets: robot control/input injection, camera/desktop imagery, customer files, fleet metadata, update artifacts (M8 — the supply-chain crown jewel).

ThreatVectorMitigation
Spoofed robotstolen/guessed device identityper-device credentials from enrollment; no fleet-shared secrets (below)
Spoofed operatorleaked/forged grantshort-lived signed JWTs, capability-scoped, tenant-keyed; server verifies before the agent ever hears about a session
Media interceptionon-path attackerDTLS-SRTP end-to-end (relay forwards ciphertext); WSS for signaling
TURN abuseharvested credentialsephemeral HMAC creds (use-auth-secret), TTL ≤ session; never static (rcartc lesson)
Robot-side privilege escalationcompromise of the agent processagent runs unprivileged; injection helper is minimal and separate (ADR-0009); no arbitrary-shell escape hatches, ever (relox FIFO anti-lesson)
Terminal/file abuseover-broad grantsper-capability grant params (view_only, read/write, path allow-lists); audit every session
Replay/tamper on webhooksforged callbacksHMAC-signed, timestamped, event_id idempotency
Stuck controldead operator/agentheartbeat teardown + release_all_input(); deadman on actuation channels (docs/15)

Explicitly rejecting the prior art (MAC address + fleet-shared bearer token):

  1. Enrollment: PUT /v1/robots/{robot_id} returns a one-time bootstrap token; the agent redeems it for a per-device credential (Ed25519 keypair generated on-device, public key registered; private key 0600, TPM-backed where available — open question).
  2. Authentication: agent hello signs a server nonce; no long-lived bearer tokens on the wire.
  3. Revocation: single API call kills a device’s access; rotation supported without touching the robot (re-enroll flow).
  4. The customer’s robot_id is carried in signed messages, not only in connection metadata, so audit trails survive proxies.

Covered by the grant contract (docs/09). Security-relevant rules:

  • Grants authorize session creation, not indefinitely: ≤ 5 min validity to start; server may cap session duration per tenant policy.
  • The agent re-checks capability names against its local config — a grant can never enable a capability the integrator didn’t compile/configure in.
  • session.started/ended webhooks + agent-side audit log give the fleet admin the “who/what/when” view; terminal sessions additionally log start/end with operator identity from day one.

Concurrent access policy (from the relox lesson, generalized):

  • Multiple viewers are fine (FrameHub exists for this).
  • Input-bearing capabilities (desktop input, teleop, terminal) take an ownership claim: default policy one owner at a time, later owners read-only until transfer.
  • Claims are leases: refreshed by the heartbeat, fail open on staleness (30 s without refresh clears the claim, logged). A dead process must never leave a robot unownable — the media plane is the component most likely to hang.

coturn in use-auth-secret mode only. fjarr-server mints username = expiry:session_id, credential = HMAC-SHA1(secret, username) with TTL bound to the grant. Relay usage is metered per session (billing + abuse detection). The dev compose ships this exact mode so no code path ever sees a static TURN password.

fjarr-agent unprivileged user "fjarr"
├─ media plane same user; GPU via render group
├─ desktop backend session user integration per ADR-0006
└─ fjarr-inputd SEPARATE minimal binary, owns /dev/uinput (or XTest),
speaks a 5-verb protocol over a mode-0700 unix socket:
key / button / motion / wheel / release_all

The privileged surface is auditable in one sitting (< 500 lines target). It validates ranges, rate-limits, and refuses when no session claim exists.

OTA artifacts are signed (SWUpdate signing + our manifest); agents verify before flashing; A/B + health-check rollback bounds the blast radius. Detailed in the M8 spec revision of this document.

  • Shipped artifacts: permissive or LGPL-dynamic dependencies only; no GPL (x264enc is the canonical example — doctor enforces its absence). Rationale: dual-licensed commercial builds cannot carry GPL code.
  • Every dependency is recorded with its license in docs/14; additions require a docs/14 row in the same PR.
  • External contributions require a CLA (needed for dual licensing).

No secrets in the repo, images, or frontend bundles — .env (gitignored) locally, secret stores in deployment. The rcartc frontend shipping TURN passwords as build-time defaults is the canonical anti-example.