Skip to content

Security: tpsdev-ai/flair

SECURITY.md

Flair Security Model

Overview

Flair uses cryptographic identity (Ed25519) for agent authentication. Within an org, memory reads are open — agents see each other's non-private memories — while writes are isolated per agent and private memories stay strictly owner-only. The hard access boundary is the federation edge (cross-instance).

Authentication

Agent Authentication (Ed25519)

Every request includes an Authorization header:

TPS-Ed25519 <agentId>:<timestamp>:<nonce>:<signature>

The signature covers agentId:timestamp:nonce:METHOD:/path using the agent's Ed25519 private key. The server verifies against the agent's registered public key.

  • Replay window: 30 seconds
  • Nonce deduplication: Prevents replay within the window
  • No shared secrets: Each agent has its own key pair

Admin Authentication

Admin operations (agent management, backup, restore) use Harper's built-in HTTP Basic auth:

Authorization: Basic <base64(admin:password)>

The admin password is generated on flair init and stored at ~/.flair/admin-pass (mode 0600, owner-only) — it can also be set explicitly via --admin-pass-file, FLAIR_ADMIN_PASS, or HDB_ADMIN_PASSWORD. It is never printed to the console. See docs/secrets-and-keys.md for the full precedence and rotation model.

The Harper operations API is the admin/management surface. Network requests to it require admin Basic auth: Flair ships with authorizeLocal: false, so an unauthenticated loopback request is rejected (401) instead of being auto-elevated to super_user. The same API is also reachable over a Unix domain socket (~/.flair/data/operations-server, owner-write-only), which Harper treats as an inherent local-admin channel — a request over the socket is authorized as super_user without credentials. This is by design (the admin-password rotation flow relies on it) and is constrained to the box owner; see Threats NOT Mitigated below.

Data Scoping

Memory Read/Write Model

Reads are open within an org; writes are isolated per agent. Enforced at the database layer (Harper resource search() override via a single centralized read-scope rule), not application logic:

  • GET /Memory/ — returns the authenticated agent's own memories (any visibility) plus every non-private memory in the org
  • POST /MemorySearch — same read scope: own memories plus all non-private
  • POST /Memory — writes are attributed to the authenticated agent; an agent cannot write as another (write isolation)

Private memories are strictly owner-only — a memory written with visibility: private is returned only to its author, never to another agent.

Cross-Agent Access (within an org)

Within an org, an agent reads every non-private memory directly — no grant required. This is deliberate: the goal is relevance (surface what's useful to the org) over secrecy. A writer keeps something private by marking it visibility: private, which keeps it strictly owner-only. The access boundary that stays hard is the federation edge (cross-instance / cross-org), not within-org reads.

Admin Bypass

Admin-authenticated requests bypass agent scoping. Admin can read all memories, all souls, and all agent records. SQL and GraphQL endpoints are restricted to admin-only.

Key Management

Key Storage

Private keys are stored at ~/.flair/keys/<agentId>.key with 0600 permissions (owner read/write only). Legacy path ~/.tps/secrets/flair/ is supported with deprecation warnings.

Key Generation

Keys are generated during flair agent add using Ed25519 (via tweetnacl). The 32-byte seed is stored; the full 64-byte secret key is derived at runtime.

Key Rotation

flair agent rotate-key <id> generates a new key pair, updates the public key in Flair, and backs up the old key as <agentId>.key.bak.

Threat Model

Threats Mitigated

  • Agent impersonation: Ed25519 signatures prevent one agent from acting as another without possessing the private key.
  • Private-memory leakage: memories marked visibility: private are strictly owner-only and never returned to another agent. Non-private memories are intentionally readable within the org (by design, not leakage); the cross-instance boundary is the federation edge.
  • Write forgery: writes are attributed to the authenticated agent — an agent cannot write memories as another.
  • Replay attacks: 30-second window + nonce deduplication.
  • Privilege escalation via SQL: SQL and GraphQL endpoints blocked for non-admin agents.
  • Admin token theft from filesystem: Admin credentials are only in process environment, never on disk.

Threats NOT Mitigated (Runtime Responsibility)

  • Same-OS-user key theft: If multiple agents run as the same OS user, file permissions alone don't prevent key access. Use process-level sandboxing (nono, Docker, separate users) to isolate agents.
  • Memory dumps: Private keys exist in process memory. A compromised process could extract them.
  • Network sniffing: Flair uses HTTP by default. Use HTTPS in production or restrict to localhost.
  • Same-OS-user ops-API socket access: the operations-API domain socket (operations-server, owner-write-only) is an unauthenticated super_user channel by Harper design — any process running as the box owner can perform admin operations through it (the admin-password rotation flow uses this path). Owner-write permissions keep it unreachable by other OS users, co-tenants, and the network; isolate untrusted workloads to separate OS users.

Recommendations

  1. Use HTTPS for any non-localhost deployment
  2. Sandbox agents with nono, Docker, or separate OS users
  3. Rotate keys periodically with flair agent rotate-key
  4. Back up with flair backup — backup files contain memory content and should be treated as sensitive
  5. Monitor with flair status — warns about key permission issues

There aren't any published security advisories