Configuration reference
ShellWatch is configured by a single config.yaml. Endpoints, keys, and passkeys are managed dynamically through the Web UI / REST API and live in the SQLite database; OAuth clients and tokens live in Ory Hydra — the config file is only for bootstrap and security.
The schema is validated by Zod at startup. See config.sample.yaml for the canonical example.
Resolution order: CLI arg → SHELLWATCH_CONFIG env var → ./config.yaml.
Minimal example
Section titled “Minimal example”server: externalUrl: http://localhost:3000
security: rpId: localhost trustedWebauthnOrigins: - http://localhost:3000 allowedNetworks: - 127.0.0.1/32 - "::1/128"
hydra: publicUrl: http://localhost:4444 adminUrl: http://localhost:4445server
Section titled “server”server: port: 3000 # default 3000 externalUrl: https://shellwatch.example.com # required — used for OAuth, deep links trustProxy: # default false - 10.0.0.0/8 - 172.16.0.0/12| Key | Type | Notes |
|---|---|---|
port | number | HTTP listen port. Default 3000. |
externalUrl | string (URL) | The URL clients reach the broker on. Used by OAuth discovery (never derived from request headers), the SPA’s default redirect URI, and deep links. |
trustProxy | bool / number / string / string[] | Reverse-proxy trust. Use CIDR(s) of your proxy. true lets any client spoof their IP and is unsafe in production. See Reverse proxy. |
security
Section titled “security”security: rpId: shellwatch.example.com trustedWebauthnOrigins: - https://shellwatch.example.com selfRegistrationEnabled: false # default false allowedNetworks: # default loopback only - 0.0.0.0/0 - "::/0" rateLimit: selfRegister: { max: 5, windowMinutes: 15 } passkeyRegister: { max: 10, windowMinutes: 15 } loginOptions: { max: 20, windowMinutes: 15 } loginVerify: { max: 10, windowMinutes: 15 }| Key | Type | Notes |
|---|---|---|
rpId | string | WebAuthn relying-party ID. Must match the registration domain. Changing this invalidates existing passkeys. |
trustedWebauthnOrigins | string[] | Allowed origins for WebAuthn ceremonies. Exact match. Include protocol + port. |
selfRegistrationEnabled | bool | Allow /register without admin invite. The first-user bootstrap always works regardless. |
allowedNetworks | CIDR[] | Per-IP allowlist for /mcp and /agent-proxy. Defaults to loopback. |
rateLimit | object | Per-IP, per-route limits for auth-adjacent endpoints. |
hydra (required)
Section titled “hydra (required)”ShellWatch delegates all OAuth2/OIDC to Ory Hydra — a hard runtime dependency. There is no fallback shim; ShellWatch refuses to start without this section. See Docker for standing up Hydra (file SQLite, no separate DB server), and Reverse proxy for the URL layout.
hydra: publicUrl: https://shellwatch.example.com # must equal Hydra's urls.self.issuer adminUrl: http://hydra:4445 # trusted network ONLY — never public spa: clientId: shellwatch-web # default # redirectUri: https://shellwatch.example.com/auth/callback # default: ${externalUrl}/auth/callback introspectionCacheTtlMs: 60000 # default 60s, max 300000; 0 disables dcr: allowedScopes: ["mcp", "agent"] # default # Default is loopback-only (the two patterns below). Setting this REPLACES # the default, so to keep loopback AND allow a hosted client list them all. redirectUriPatterns: # default shown - "^http://(127\\.0\\.0\\.1|localhost)(:\\d+)?(/.*)?$" - "^http://\\[::1\\](:\\d+)?(/.*)?$" # To allow Claude.ai / Claude.com as a hosted MCP client, add: # - "^https://claude\\.ai/api/mcp/auth_callback$" # - "^https://claude\\.com/api/mcp/auth_callback$"| Key | Type | Notes |
|---|---|---|
publicUrl | string (URL) | Hydra’s public issuer. Advertised in discovery, embedded in tokens, reached by the browser SPA, MCP clients, and the agent-client. Must exactly equal Hydra’s urls.self.issuer. |
adminUrl | string (URL) | Hydra’s admin API — login/consent acceptance, client CRUD, token introspection. Never expose to the internet. |
spa.clientId | string | First-party public PKCE client the web UI uses. ShellWatch provisions it in Hydra automatically on boot (idempotent). Default shellwatch-web. |
spa.redirectUri | string (URL) | The SPA’s authorization-code redirect URI. Defaults to ${server.externalUrl}/auth/callback. |
introspectionCacheTtlMs | number | How long bearer-introspection results are cached. This caps revocation latency: a revoked/logged-out token keeps working for up to this long. Lower for tighter revocation; 0 introspects every request. |
dcr.allowedScopes | string[] | Scopes grantable through mediated Dynamic Client Registration (/api/hydra/register). offline_access is always added (refresh tokens); ui is reserved for the SPA and never grantable via DCR. agent is only grantable when agentSocket.proxyEnabled is on. |
dcr.redirectUriPatterns | string[] (RegExp) | A registering client’s redirect_uri must match one of these. Defaults to loopback only (the agent-client and local MCP tools). Setting the key replaces the default list — to allow a hosted client like Claude.ai, list both the loopback patterns and the hosted callback. Anchor every pattern with ^…$. |
agentSocket
Section titled “agentSocket”agentSocket: proxyEnabled: true # default false — enables /agent-proxy WebSocketnotifications
Section titled “notifications”notifications: mcp: debounceMs: 100 # default 100 — output_available debounce windowvapid (Web Push)
Section titled “vapid (Web Push)”vapid: subject: "mailto:admin@example.com" publicKey: "BEl62iU..." privateKey: "UGo..."When omitted, the entire Web Push UI is hidden. Generate keys with npx web-push generate-vapid-keys.
Seeding (first run only)
Section titled “Seeding (first run only)”These keys only do anything on first run, when the database is empty.
seedAdminEndpoints: - label: Dev Box address: ubuntu@dev.example.com # [user@]host[:port] - label: Staging address: deploy@staging.example.com:2222
seedAdminPasskeys: - credentialId: "base64url..." publicKeyHex: "cose-public-key-hex" counter: 0 transports: [] label: "Admin Passkey"| Key | Use |
|---|---|
seedAdminEndpoints | Pre-populate endpoints. Keys are assigned via the UI after discovery. |
seedAdminPasskeys | Pre-load passkeys (typically exported from another instance via Admin → General). |
(seedAdminApiKey is gone — API keys no longer exist. All access tokens are minted through the OAuth flow.)
Database location
Section titled “Database location”SHELLWATCH_DB=sqlite:./data/shellwatch.db # env var — defaultCurrently SQLite-only. data/ must be writable.
Where to next
Section titled “Where to next”- HTTP endpoints — what’s served on which path.
- MCP tools — tool schemas.
- Agent client —
shellwatch-agentflags.