crabidy/quality/roles-auth.md

3.4 KiB

Quality gates — roles and rights

LLM-verified gates for architecture/roles-auth.md. Automatic tests live in crabidy-server/src/auth.rs, crabidy-server/src/main.rs (hash helper) and cbd-tui/src/rpc.rs.

Security posture

  • Fail-closed everywhere: an unknown/future gRPC method requires owner; a present-but-malformed crabidy-server.toml aborts server startup instead of silently running open; a missing file (or empty [auth]) is the documented open mode.
  • Guarding order enforced: valid guarded sets are prefixes of [owner, queue_owner, queue_appender]. A config guarding a lower role while a higher one is open (queue_owner without owner, or queue_appender without queue_owner) aborts startup, and crabidy-server guard refuses to write it.
  • A present-but-wrong credential is denied (UNAUTHENTICATED), never silently downgraded to the anonymous role.
  • No panics on request input: malformed authorization headers (bad base64, missing colon, non-UTF-8, wrong scheme), unknown role names, and oversized values all produce UNAUTHENTICATED, never a panic.
  • Indistinguishable failures: wrong user and wrong password both answer plain UNAUTHENTICATED with the same message.
  • Secrets redacted: passwords, authorization header values, and PHC hashes never appear in logs, traces, or error messages (including the hash-password helper and TUI logs).
  • Authorization is enforced in exactly one place (the tower layer), before any handler runs; RPC handlers did not change.

Semantics

  • Rights matrix implemented as specified: appender = reads + Append + CreateLibraryNode; queue-owner adds every queue and playback verb; owner-only = CaptureLibraryNode, SaveQueue, RenameLibraryNode, DeleteLibraryNode. Higher roles include lower ones. A test pins the full method list of the proto service so an unmapped new RPC fails the suite.
  • A caller (anonymous or credentialed) below a method's minimum role gets PERMISSION_DENIED (not UNAUTHENTICATED).
  • Anonymous callers inherit the highest unguarded role: nothing guarded ⇒ owner (today's open server); owner guarded ⇒ queue-owner; owner+queue_owner guarded ⇒ queue-appender; all guarded ⇒ nothing (UNAUTHENTICATED). A credential elevates above that floor.
  • No [auth] hashes ⇒ exactly today's behavior: no header required, all methods allowed.
  • A role without a configured hash cannot authenticate (a login attempt as it is denied; anonymous callers may still inherit it).

Performance

  • Argon2 verification runs once per credential: successful verifications are cached (header value → role) and the cache is only fed by successes, keeping it bounded by the number of valid credentials.

Client

  • Without configured credentials the TUI sends no header (zero-config local use unchanged); with credentials, every request — including update-stream reconnects — carries the same authorization: Basic header.
  • The client config documents that password is plaintext and the file must be kept private.

Tooling

  • crabidy-server hash-password reads the password from stdin (nothing echoed by the tool itself), prints only the PHC string, and its output verifies against the same server code path.