Auth

Packages: auth/, grants/. Two independent auth systems: MCP tier gating for agents, web auth for human operators.

MCP auth: tier-based tool gating

See also: IPC.

Every agent container runs at a tier determined by its group's depth in the directory tree. Tier 0 is the root (no parent); each level of nesting adds one tier. The IPC server uses grants.DeriveRules to compute the default grant set for a tier, then intersects with any custom rules stored for the group.

Default grants by tier

TierDefault rules
0* (all tools)
1Management tools + send
2send only
3+send_reply only

Grant rule syntax

Rules are strings in the format: [!]action[(param=glob,...)]

Examples:

# Allow everything except spawn_group
*
!spawn_group

# Allow send only to telegram JIDs
send_message(jid=telegram:*)
send_reply(jid=telegram:*)

Grants engine functions

FunctionDescription
CheckAction(rules, action, params)Returns allow or deny for a specific call
NarrowRules(parent, child)Merge parent and child rules; child can only restrict, never expand
MatchingRules(rules, action)Return the subset of rules that apply to a given action
DeriveRules(store, folder, tier, worldFolder)Compute default rules from tier and group hierarchy

NarrowRules

NarrowRules(parent, child) produces a merged rule set where the child can only restrict what the parent allows. If the parent denies an action and the child allows it, the denial stands. This is called at container spawn time when delegating to a child group, and by the delegate_group IPC tool when creating child groups dynamically.

Grant injection

Before each container run, grants.DeriveRules computes the effective grant set for the group. The rules are injected into start.json (the container input). The IPC MCP manifest is filtered at server startup to only list tools the group is permitted to call.

Web auth

Access model

proxyd is the single public-facing HTTP server. Path prefixes determine auth:

Path prefixAuth requiredNotes
/pub/*NonePublicly accessible; web apps must live here to be public
/healthNoneHealth check endpoint
/auth/*NoneLogin, OAuth, refresh, logout โ€” served directly by proxyd
/slink/*Token onlyPer-group slink token from registered_groups.slink_token; 10 req/min per IP
/dash/*JWTProxied to dashd after validation
/dav/*JWTProxied to dufs WebDAV container; requires DAV_ADDR
all otherJWTAuth-gated; proxied to Vite or served from web/dist/

Auth resolution

requireAuth checks in order:

  1. Authorization: Bearer <jwt> โ€” validates JWT, injects X-User-Sub, X-User-Name, optionally X-User-Groups. Raw AUTH_SECRET accepted as a bypass token for operator tooling.
  2. refresh_token cookie โ€” fallback for browser navigation without a JS Bearer header. Looks up session by auth.HashToken(cookie), injects X-User-Sub and X-User-Name.
  3. Redirect to /auth/login if neither check passes.

vhosts.json

proxyd receives DATA_DIR to locate web/vhosts.json. This file maps virtual hostnames to web apps and is reloaded every 5 seconds without restart. Web apps must be placed under DATA_DIR/web/pub/ to be served at /pub/.

Passwords

Stored as argon2id hashes in auth_users.hash. No plaintext is ever written.

JWT

Access tokens have a 1-hour expiry. Refresh tokens are stored as hashes in auth_sessions and rotated on use. Both tokens are delivered as HttpOnly cookies. When authBaseURL starts with https://, cookies include the Secure flag.

JWT claims

groups: null = operator. groups: [] = no group access. groups: ["folder"] = specific folders only. The group list is read from user_groups at login time and embedded in the JWT.

OAuth providers

ProviderEnv varsNotes
GitHubGITHUB_CLIENT_ID, GITHUB_CLIENT_SECRETOptional: GITHUB_ALLOWED_ORG for org membership gate
DiscordDISCORD_CLIENT_ID, DISCORD_CLIENT_SECRET
TelegramTELEGRAM_BOT_TOKENLogin Widget; auth_date must be within 5 minutes (replay protection)
GoogleGOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET

Login page shows provider buttons when the corresponding env vars are set. All providers use the shared createOAuthSession path in auth/oauth.go.

Routes

POST /auth/login              password login, returns JWT + refresh token
POST /auth/logout             revoke refresh token
POST /auth/refresh            rotate refresh token
GET  /auth/github             OAuth redirect
GET  /auth/github/callback    OAuth callback
GET  /auth/discord            OAuth redirect
GET  /auth/discord/callback   OAuth callback
GET  /auth/telegram/callback  Telegram Widget callback

Rate limiting

loginAllowed(ip) allows at most 5 POST /auth/login attempts per IP per 15-minute sliding window. In-memory; resets on restart. Returns HTTP 429 on breach.

User management

Operators manage users via the arizuko CLI (subcommands under arizuko user). No web UI for user creation.