--- title: "Authentication" description: "Token-based authentication for the daemon API." order: 13 section: "Reference" --- Auth === Auth is optional. By default the [[daemon]] runs in `local` mode: no tokens, no authentication, localhost-only binding. Auth exists for team and remote deployments where the daemon is shared or exposed over a network. The system is intentionally simple — no external dependencies, no OAuth flows, no user database. Portable harness connectors should use named API keys generated by the CLI. Legacy session tokens are signed with HMAC-SHA256 using a secret that lives on disk. Roles and scopes are embedded in the token or API-key record. The dashboard also supports an admin username/password login that issues a normal admin session token; SSO and SAML paths are reserved so those providers can be added without changing the dashboard auth boundary later. Auth Modes --- **local** (default) — No authentication required. All requests are granted full access without any token. The daemon should only be accessible from localhost when running in this mode. Best for single-user local development. **team** — Every request must include a valid Bearer token. Use this when the daemon is accessible over a network or shared among multiple users. Unauthenticated requests get a `401 Unauthorized`. **hybrid** — The middle ground. Localhost requests can proceed without a token (full access), but if a token is included it will be validated. Remote requests always require a valid token. This lets local tooling work without credentials while still securing remote access. Note: localhost detection in hybrid mode uses the TCP peer address. If the daemon cannot determine a localhost peer, it treats the request as remote and requires a valid bearer credential. Do not rely on `hybrid` behind a same-host reverse proxy for untrusted traffic: proxied public requests can arrive from `127.0.0.1` and be treated as local. Use `team` mode or enforce auth at the proxy for public deployments. API Key Management --- API keys are the preferred credential for remote connectors. They are named, revocable, and stored hashed at rest. The raw key is printed once when it is created. For a complete cross-machine connector walkthrough, see [[remote-connectors|Remote Harness Connectors]]. ```bash signet api-key create --name "work laptop pi" --connector pi --agent-id pi-work-laptop signet api-key list signet api-key revoke ``` `--agent-id` is an auth scope, not just a label. A key created with `--agent-id ` defaults authenticated requests to that agent, and scope checks reject requests for other agents. Connector clients send the key as bearer auth: ```http Authorization: Bearer sig_sk_... ``` Use `SIGNET_API_KEY` on remote machines: ```bash SIGNET_DAEMON_URL=https://signet-home.tailnet:3850 \ SIGNET_API_KEY=sig_sk_... \ signet connector install pi --agent-id pi-work-laptop ``` For a Codex client that must attach to a specific agent, create the scoped key on the daemon machine and install Codex with that key: ```bash signet api-key create --name "codex tailnet" --connector codex --agent-id npx -y @signetai/codex-plugin install --url https://signet-home.tailnet:3850 --api-key sig_sk_... ``` Verify the scope from the remote machine without printing the key: ```bash curl -fsS "$SIGNET_DAEMON_URL/api/auth/whoami" \ -H "Authorization: Bearer $SIGNET_API_KEY" ``` `SIGNET_TOKEN` remains a backwards-compatible alias for older integrations. API keys can also carry an optional explicit `permissions` list. When present, that list narrows the key beyond its role; for example an `admin` key with only `["recall"]` cannot perform admin mutations. Connector keys created with `--connector` default to the narrower connector permission set: `recall`, `remember`, and `documents`. For non-connector keys, omit `permissions` for the full role-level permission set. Token Management --- Tokens use a simple signed format: `{base64url(payload)}.{base64url(hmac)}`. The payload is a JSON object encoded with base64url. The signature is HMAC-SHA256 over the encoded payload, also base64url-encoded. There are no external dependencies — only Node's built-in `crypto` module. The signing secret is 32 random bytes, auto-generated on first use and stored at `$SIGNET_WORKSPACE/.daemon/auth-secret` (mode 0600). If you rotate the secret, all existing tokens are immediately invalidated. Token claims: | Claim | Type | Description | |-------|--------|------------------------------------------| | sub | string | Subject identifier (who the token is for)| | role | string | One of: admin, operator, agent, readonly | | scope | object | Optional restriction (see Scope below) | | iat | number | Issued-at timestamp (Unix seconds) | | exp | number | Expiry timestamp (Unix seconds) | Default TTLs: 7 days for regular tokens (`defaultTokenTtlSeconds: 604800`), 24 hours for session tokens (`sessionTokenTtlSeconds: 86400`). Both are configurable in `agent.yaml`. Generate a token: `POST /api/auth/token` with `{ role, scope? }` in the request body. Inspect the current token: `GET /api/auth/whoami`. Dashboard sessions use `POST /api/auth/login` with `{ username, password }` and receive a shorter-lived admin bearer token. See [[api|HTTP API]] for full endpoint details. Roles and Permissions --- Four roles, ordered from most to least privileged: **admin** — Full access including admin operations. Can do everything any other role can do, plus the `admin` permission which gates administrative endpoints. **operator** — Everything except `admin`. Has full memory access plus documents, connectors, diagnostics, and analytics. Intended for trusted automation or infrastructure tooling. **agent** — Core memory operations plus documents. Does not have access to connectors, diagnostics, analytics, or admin. The default role for AI assistant integrations. **readonly** — `recall` only. Can query memory but cannot write, modify, or delete anything. Permission matrix: | Permission | admin | operator | agent | readonly | |-------------|-------|----------|-------|----------| | remember | yes | yes | yes | no | | recall | yes | yes | yes | yes | | modify | yes | yes | yes | no | | forget | yes | yes | yes | no | | recover | yes | yes | yes | no | | documents | yes | yes | yes | no | | connectors | yes | yes | no | no | | diagnostics | yes | yes | no | no | | analytics | yes | yes | no | no | | admin | yes | no | no | no | In `local` mode, permission checks are bypassed entirely — all operations are allowed regardless of any claims present. Scope --- Tokens can be scoped to restrict access to a subset of resources. A scope is an object with up to three optional fields: `project`, `agent`, and `user`. When a scope field is set on a token, requests touching a different value for that field will be rejected with `403 Forbidden`. An unscoped token (empty scope object) has full access within its role's permissions. The `admin` role bypasses scope checks entirely — admin tokens always have full resource access regardless of scope. Scope restrictions only apply in `team` and `hybrid` modes. In `local` mode, scope is ignored. Example: a token scoped to `{ agent: "mr-claude" }` can only access memory records associated with that agent. Requests targeting a different agent will be rejected. Rate Limiting --- The daemon applies a sliding window rate limiter to destructive operations. State is in-memory and resets on daemon restart. Rate limits are only enforced in `team` and `hybrid` modes — `local` mode has no limits. Default limits (per actor per minute): | Operation | Limit | |------------------|-------| | forget | 30 | | modify | 60 | | batchForget | 5 | | forceDelete | 3 | | admin | 10 | | inferenceExplain | 120 | | inferenceExecute | 20 | | inferenceGateway | 30 | | recallLlm | 60 | The actor is identified by the token's `sub` claim when present. For unauthenticated requests (hybrid mode, localhost), requests share the `"anonymous"` bucket. When a limit is exceeded, the daemon returns `429 Too Many Requests` with a `Retry-After` header indicating how many seconds until the window resets. All rate limit configs can be overridden in `agent.yaml` under the `auth.rateLimits` key. Configuration --- Auth is configured in `agent.yaml` under the `auth:` key (see [[configuration]]). All fields are optional — omitting the section entirely gives you `local` mode with defaults. ```yaml auth: mode: local # local | team | hybrid defaultTokenTtlSeconds: 604800 # 7 days sessionTokenTtlSeconds: 86400 # 24 hours login: password: username: admin # Prefer SIGNET_ADMIN_PASSWORD or SIGNET_ADMIN_PASSWORD_HASH in env. # passwordHash may also be set here as pbkdf2-sha256$iterations$salt$hash. passwordHash: null sso: enabled: false saml: enabled: false rateLimits: forget: windowMs: 60000 max: 30 modify: windowMs: 60000 max: 60 batchForget: windowMs: 60000 max: 5 forceDelete: windowMs: 60000 max: 3 admin: windowMs: 60000 max: 10 login: windowMs: 60000 max: 5 inferenceExplain: windowMs: 60000 max: 120 inferenceExecute: windowMs: 60000 max: 20 inferenceGateway: windowMs: 60000 max: 30 ``` The secret path is always `$SIGNET_WORKSPACE/.daemon/auth-secret` and is not configurable. The daemon creates it automatically on first start in any non-local mode. Password dashboard login is enabled when either `SIGNET_ADMIN_PASSWORD`, `SIGNET_ADMIN_PASSWORD_HASH`, or `auth.login.password.passwordHash` is set. The username defaults to `admin` and can be overridden with `SIGNET_ADMIN_USERNAME` or `auth.login.password.username`. Plaintext passwords are only accepted from the environment; persisted config should use a `pbkdf2-sha256$...` hash. HTTP Headers --- `Authorization: Bearer ` — Required in `team` mode. Optional in `hybrid` mode for localhost requests (validated if present, not required). `` may be either a named `sig_sk_...` API key or a legacy signed token. The dashboard stores the session token client-side and sends it as this header on daemon API requests. `x-signet-actor-type: operator|agent` — Actor type hint used by certain policy decisions (e.g. repair action gating). Optional. Deployment Guide --- **Single user, local machine** — Use the default `local` mode. No configuration needed. The daemon listens on `localhost:3850` and any local client has full access. **Shared team server or CI** — Use `team` mode. Configure an admin dashboard password via environment, then generate tokens for each consumer with appropriate roles: ```bash SIGNET_ADMIN_USERNAME=admin \ SIGNET_ADMIN_PASSWORD='choose-a-long-random-password' \ signet daemon ``` ```bash # Generate an operator token for a CI pipeline curl -X POST http://localhost:3850/api/auth/token \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "role": "operator", "sub": "ci-pipeline" }' # Generate a readonly token for a monitoring script curl -X POST http://localhost:3850/api/auth/token \ -H "Authorization: Bearer " \ -d '{ "role": "readonly", "sub": "monitor" }' ``` **Developer machine with remote access** — Use `hybrid` mode. Local tools (the CLI, dashboard, AI assistants) work without credentials. Remote tools like CI jobs, teammates, or portable harness connectors need an API key. Set `mode: hybrid` in `agent.yaml` and distribute keys only to remote consumers. **Scoped agent tokens** — When multiple AI agents share a daemon, issue each one a scoped `agent` token to prevent cross-agent memory access: ```json { "role": "agent", "sub": "project-assistant", "scope": { "agent": "project-assistant" } } ``` Rotating the secret invalidates all outstanding tokens and dashboard sessions immediately. Do this if a token is leaked. The daemon will regenerate a new secret at `$SIGNET_WORKSPACE/.daemon/auth-secret` if the file is deleted, then restart.