- 09-security: compress 9-bullet edge-case list to 6-row Attack Surface/Mitigation table - 20-api-keys-auth: replace auth.go:tokenMatch + router.go:handleConnect citations with behavior descriptions; remove RoleFromScopes() file ref - 21-agent-evolution: remove resolver.go:346 code block + loop.go/loop_history.go line citations, replace with behavioral descriptions - 19-websocket-rpc: already clean (no body-text .go:line refs)
15 KiB
20 — API Keys & Authentication
GoClaw supports two authentication mechanisms: a single gateway token (configured at startup) and multiple API keys with fine-grained RBAC scopes. Both work across HTTP REST and WebSocket RPC.
1. Gateway Token
The gateway token is set in config.json under gateway.token. It grants full admin access.
{
"gateway": {
"token": "my-secret-token"
}
}
Used as a Bearer token:
Authorization: Bearer my-secret-token
Or in WebSocket connect:
{"method": "connect", "params": {"token": "my-secret-token", "user_id": "admin"}}
Security
The gateway token is compared using constant-time comparison (crypto/subtle.ConstantTimeCompare) in both HTTP and WebSocket auth paths to prevent timing attacks. The comparison reveals no information about where the provided token first differs from the expected token.
2. API Keys
API keys provide scoped, revocable access for CI/CD, integrations, and third-party clients.
Key Format
goclaw_a1b2c3d4e5f6789012345678901234567890abcdef
- Prefix:
goclaw_(6 chars) - Random: 32 hex characters (128 bits of entropy)
- Display prefix:
goclaw_+ first 8 hex chars (shown in UI after creation)
Security Model
Keys are hashed with SHA-256 before storage — the raw key is never persisted. This follows the same pattern as GitHub Personal Access Tokens.
raw key → SHA-256 → stored hash
On authentication, the incoming token is hashed and looked up in the api_keys table via a partial index on non-revoked keys.
Show-Once Pattern
The raw key is returned only once in the create response. All subsequent list/get calls show only the prefix field (8 hex characters representing the first 4 bytes of the random component). Users must copy the key immediately after creation, as it cannot be retrieved again.
3. RBAC Scopes
Each API key is assigned one or more scopes that determine what operations it can perform. Scopes are validated against the set of valid scopes at creation time.
| Scope | Description |
|---|---|
operator.admin |
Full access — equivalent to gateway token, can manage API keys and SecureCLI configs |
operator.read |
Read-only access to agents, sessions, skills, chat history, and metadata |
operator.write |
Read + write access — can send chat messages, manage sessions, trigger cron jobs |
operator.approvals |
Manage shell command execution approvals (approve/deny exec requests) |
operator.pairing |
Manage browser device pairings (list, revoke paired devices) |
Role Derivation
The highest-privilege scope determines the effective role:
if admin scope present → RoleAdmin
if write/approvals/pairing scope → RoleOperator
if read scope only → RoleViewer
default → RoleViewer
The derived role is then used by the PolicyEngine.CanAccess() method to gate RPC method access (see 19 — WebSocket RPC).
4. Authentication Flow
Prioritized Auth Paths
GoClaw tries authentication methods in this priority order:
- Gateway token (exact match via constant-time comparison) →
RoleAdminorRoleOwnerfor configured owner IDs - API key (SHA-256 hash lookup in
api_keystable) → role from scopes - Browser pairing (sender ID must be paired with "browser" device type) →
RoleOperator(HTTP only; requiresX-GoClaw-Sender-Idheader) - No auth configured (backward compatibility: if no gateway token is set) → full-access dev mode
- No valid auth found →
401 Unauthorized
HTTP Request Flow
flowchart TD
A[Incoming HTTP request] --> B{Authorization header?}
B -->|No| C{X-GoClaw-Sender-Id header?}
B -->|Yes, extract Bearer token| D{Match gateway token?}
D -->|Yes| E[RoleAdmin]
D -->|No| F[Hash token + lookup in api_keys]
F --> G{Found + valid?}
G -->|Yes| H[Derive role from scopes]
G -->|No| I{Gateway token configured?}
I -->|Yes| J[401 Unauthorized]
I -->|No| K[Full-access backward compat]
C -->|Check paired device| L{Device paired?}
L -->|Yes| M[RoleOperator]
L -->|No| J
E --> N[Authenticate request]
H --> N
K --> N
M --> N
WebSocket Connect Flow
The same auth paths apply for WebSocket connect messages. The connection parameter token is checked against the gateway token first, then API keys, then browser pairing.
API Key Caching
API keys are cached in-memory with a 5-minute TTL to reduce database load. The cache:
- Maps SHA-256 hash →
APIKeyData+ derived role - Supports negative caching (tracks failed lookups) to prevent repeated DB queries for invalid tokens
- Caps negative cache to 10,000 entries to prevent memory exhaustion from token spraying attacks
- Is invalidated globally via pubsub on key creation, revocation, or update
Last-Used Tracking
On successful API key authentication, last_used_at is updated asynchronously (fire-and-forget goroutine with 5-second timeout) to avoid blocking the request path. This tracks API usage without slowing down authentication.
5. Authentication Priority & Backward Compatibility
HTTP Request Headers
- Bearer token:
Authorization: Bearer <token>— checked first for gateway token or API key - User ID:
X-GoClaw-User-Id: <user-id>— optional external user identifier (max 255 chars) - Browser pairing:
X-GoClaw-Sender-Id: <sender-id>— identifies a previously-paired browser device - Tenant scope:
X-GoClaw-Tenant-Id: <tenant-uuid-or-slug>— owner/system-key scope narrowing; non-owner gateway token and browser-pairing callers must already belong to the requested tenant - Locale:
Accept-Language— user's preferred language (en, vi, zh; default: en)
Tenant Scope Rules
- Gateway token + owner user ID: may narrow to any tenant via
X-GoClaw-Tenant-Id - Gateway token + non-owner user ID: may only use
X-GoClaw-Tenant-Idfor a tenant where that user already has membership; otherwise auth fails - Browser pairing: same tenant-membership rule as non-owner gateway-token HTTP requests
- Tenant-bound API key: always stays bound to its stored
tenant_id; request headers cannot move it - System-level API key (
tenant_id = NULL): keeps its scope-derived role (admin,operator, orviewer) and may narrow requests to a tenant viaX-GoClaw-Tenant-Id, but it does not becomeowner
Backward Compatibility
If no gateway token is configured (gateway.token is empty in config.json), unauthenticated requests run in backward-compatibility full-access mode. This enables self-hosted deployments without strict authentication. Once a gateway token is configured, all requests must authenticate or use browser pairing.
5. Database Schema
CREATE TABLE api_keys (
id UUID PRIMARY KEY,
name VARCHAR(100) NOT NULL,
prefix VARCHAR(8) NOT NULL, -- first 8 chars for display
key_hash VARCHAR(64) NOT NULL UNIQUE, -- SHA-256 hex digest (unique constraint)
scopes TEXT[] NOT NULL DEFAULT '{}', -- e.g. {'operator.admin','operator.read'}
expires_at TIMESTAMPTZ, -- NULL = never expires
last_used_at TIMESTAMPTZ,
revoked BOOLEAN NOT NULL DEFAULT false,
created_by VARCHAR(255), -- user ID who created the key
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Fast lookup by hash (only active, non-revoked keys)
CREATE INDEX idx_api_keys_key_hash ON api_keys (key_hash) WHERE NOT revoked;
-- Fast lookup by prefix (for display/search in web UI)
CREATE INDEX idx_api_keys_prefix ON api_keys (prefix);
The partial index on key_hash ensures only active (non-revoked) keys are searched during authentication, keeping lookups fast regardless of how many keys have been revoked over time. The key_hash column has a UNIQUE constraint, so only one key can have a given hash.
6. API Endpoints
HTTP REST
| Method | Path | Description |
|---|---|---|
GET |
/v1/api-keys |
List all keys (masked) |
POST |
/v1/api-keys |
Create key |
POST |
/v1/api-keys/{id}/revoke |
Revoke key |
WebSocket RPC
| Method | Description |
|---|---|
api_keys.list |
List all keys (masked) |
api_keys.create |
Create key |
api_keys.revoke |
Revoke key |
All API key management operations require admin access (gateway token or API key with operator.admin scope). Owner callers are accepted because owner is a superset of admin.
Create Request
{
"name": "ci-deploy",
"scopes": ["operator.read", "operator.write"],
"expires_in": 2592000
}
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Human-readable label |
scopes |
string[] | Yes | Permission scopes |
expires_in |
int | No | TTL in seconds (omit for no expiry) |
Create Response
{
"id": "01961234-5678-7abc-def0-123456789012",
"name": "ci-deploy",
"prefix": "goclaw_a1b2c3d4",
"key": "goclaw_a1b2c3d4e5f6789012345678901234567890abcdef",
"scopes": ["operator.read", "operator.write"],
"expires_at": "2026-04-14T12:00:00Z",
"created_at": "2026-03-15T12:00:00Z"
}
List Response
[
{
"id": "01961234-...",
"name": "ci-deploy",
"prefix": "goclaw_a1b2c3d4",
"scopes": ["operator.read", "operator.write"],
"expires_at": "2026-04-14T12:00:00Z",
"last_used_at": "2026-03-15T14:30:00Z",
"revoked": false,
"created_by": "admin",
"created_at": "2026-03-15T12:00:00Z"
}
]
Note:
keyfield is absent in list responses. Onlyprefixis shown.
7. Backward Compatibility
The gateway token continues to work exactly as before. API keys are an additional authentication path — no breaking changes to existing integrations.
| Auth method | Before | After |
|---|---|---|
| Gateway token | Admin access | Admin access (unchanged) |
| API key | N/A | Scoped access per key |
| Device pairing | Operator access | Operator access (unchanged) |
8. SecureCLI — CLI Credential Injection
SecureCLI is a feature that allows GoClaw to automatically inject credentials into CLI tools (e.g., gh, gcloud, aws) without requiring the agent to handle plaintext secrets. Credentials are stored encrypted at rest and injected at process startup.
Use Case
When an agent needs to run gh auth, gcloud auth, or other authenticated CLI commands, the admin can configure a SecureCLI binary with encrypted environment variables. The agent never sees the raw credentials — they are injected directly into the child process environment via Direct Exec Mode.
Database Schema
CREATE TABLE secure_cli_binaries (
id UUID PRIMARY KEY,
binary_name TEXT NOT NULL, -- "gh", "gcloud", "aws", etc.
binary_path TEXT, -- optional absolute path (auto-resolved if null)
description TEXT,
encrypted_env BYTEA NOT NULL, -- AES-256-GCM encrypted JSON
deny_args JSONB DEFAULT '[]', -- regex patterns to block subcommands
deny_verbose JSONB DEFAULT '[]', -- verbose flag patterns
timeout_seconds INTEGER DEFAULT 30,
tips TEXT, -- injected into TOOLS.md context for agents
agent_id UUID, -- null = global (all agents), else agent-specific
enabled BOOLEAN DEFAULT true,
created_by TEXT,
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ
);
HTTP REST API
| Method | Path | Description |
|---|---|---|
GET |
/v1/cli-credentials |
List all SecureCLI configurations |
POST |
/v1/cli-credentials |
Create a new SecureCLI credential config |
GET |
/v1/cli-credentials/{id} |
Get a specific SecureCLI config |
PUT |
/v1/cli-credentials/{id} |
Update a SecureCLI config |
DELETE |
/v1/cli-credentials/{id} |
Delete a SecureCLI config |
POST |
/v1/cli-credentials/{id}/test |
Dry-run test (requires admin) |
GET |
/v1/cli-credentials/presets |
List preset templates for common CLIs |
Features
- Agent-specific or global: Configs can be scoped to a single agent or shared across all agents (agent_id = null)
- Encryption: Environment variables are encrypted with AES-256-GCM (same as other secrets)
- Deny patterns: Regex patterns to block dangerous subcommands (e.g., prevent
gh auth logout) - Timeout: Per-config timeout override for long-running CLI operations
- Tips: Admin-provided hints injected into agent TOOLS.md context to guide which CLI commands are safe
Example Configuration
{
"binary_name": "gh",
"binary_path": "/usr/local/bin/gh",
"description": "GitHub CLI for PRs and issues",
"encrypted_env": {
"GH_TOKEN": "ghp_xxxxxxxxxxxx"
},
"deny_args": ["auth", "logout"],
"deny_verbose": ["--verbose", "-v"],
"timeout_seconds": 60,
"tips": "Use 'gh pr list' to search PRs, 'gh issue create' to open issues. Avoid 'auth' and 'logout' commands.",
"agent_id": null
}
9. Web UI
The API Keys management page is accessible from the sidebar under System > API Keys (admin only).
Features:
- Create dialog: Name, scope checkboxes, optional expiry (1 day, 7 days, 30 days, 90 days, never)
- Show-once dialog: Displays the raw key with copy button after creation
- List view: Searchable, paginated table with name, prefix, scopes, expiry, last used, status
- Revoke: Confirmation dialog before revoking a key
10. Usage Examples
cURL with API Key
# List agents (read scope required)
curl -H "Authorization: Bearer goclaw_a1b2c3d4..." \
http://localhost:9090/v1/agents
# Send chat message (write scope required)
curl -X POST -H "Authorization: Bearer goclaw_a1b2c3d4..." \
-H "Content-Type: application/json" \
-d '{"model":"goclaw:my-agent","messages":[{"role":"user","content":"Hello"}]}' \
http://localhost:9090/v1/chat/completions
WebSocket with API Key
{"id": 1, "method": "connect", "params": {
"token": "goclaw_a1b2c3d4e5f6...",
"user_id": "ci-bot"
}}
Create Key via API
curl -X POST -H "Authorization: Bearer gateway-admin-token" \
-H "Content-Type: application/json" \
-d '{"name":"ci","scopes":["operator.read","operator.write"]}' \
http://localhost:9090/v1/api-keys
11. File Reference
| Module | Path | Purpose |
|---|---|---|
| Crypto & key generation | internal/crypto/apikey.go |
Key generation, SHA-256 hashing |
| Store & persistence | internal/store/api_key_store.go, internal/store/secure_cli_store.go, internal/store/pg/api_keys.go, internal/store/pg/secure_cli.go |
API key + SecureCLI interfaces and PostgreSQL implementations |
| HTTP & gateway auth | internal/http/auth.go, internal/http/api_key_cache.go, internal/http/api_keys.go, internal/http/secure_cli.go, internal/gateway/router.go, internal/gateway/methods/api_keys.go |
Auth middleware, cache, HTTP handlers, WS connect auth |
| Permissions & UI | internal/permissions/policy.go, ui/web/src/pages/api-keys/ |
RBAC role derivation, scope validation, web management page |
Use grep or your editor's symbol search for specific files.