Packages: auth/, grants/. Two independent auth systems: MCP tier gating for agents, web auth for human operators.
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.
| Tier | Default rules |
|---|---|
| 0 | * (all tools) |
| 1 | Management tools + send |
| 2 | send only |
| 3+ | send_reply only |
Rules are strings in the format: [!]action[(param=glob,...)]
! prefix: deny the actionsend_message(jid=telegram:*)* matches any actionExamples:
# Allow everything except spawn_group
*
!spawn_group
# Allow send only to telegram JIDs
send_message(jid=telegram:*)
send_reply(jid=telegram:*)
| Function | Description |
|---|---|
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(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.
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.
proxyd is the single public-facing HTTP server. Path prefixes determine auth:
| Path prefix | Auth required | Notes |
|---|---|---|
/pub/* | None | Publicly accessible; web apps must live here to be public |
/health | None | Health check endpoint |
/auth/* | None | Login, OAuth, refresh, logout โ served directly by proxyd |
/slink/* | Token only | Per-group slink token from registered_groups.slink_token; 10 req/min per IP |
/dash/* | JWT | Proxied to dashd after validation |
/dav/* | JWT | Proxied to dufs WebDAV container; requires DAV_ADDR |
| all other | JWT | Auth-gated; proxied to Vite or served from web/dist/ |
requireAuth checks in order:
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.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./auth/login if neither check passes.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/.
Stored as argon2id hashes in auth_users.hash. No plaintext is ever written.
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.
sub: user identifiergroups: JSON array of folder names, or null for operator (unrestricted access)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.
| Provider | Env vars | Notes |
|---|---|---|
| GitHub | GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET | Optional: GITHUB_ALLOWED_ORG for org membership gate |
| Discord | DISCORD_CLIENT_ID, DISCORD_CLIENT_SECRET | |
| Telegram | TELEGRAM_BOT_TOKEN | Login Widget; auth_date must be within 5 minutes (replay protection) |
GOOGLE_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.
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
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.
Operators manage users via the arizuko CLI (subcommands under arizuko user). No web UI for user creation.