Authentication & Secrets
Artic uses a layered authentication system with encrypted secret management.
Authentication Methods
JWT Authentication (TUI, Web)
Used for interactive sessions with short-lived tokens.
POST /auth/login
{ "email": "user@example.com", "password": "secret" }
→ { "access_token": "eyJ...", "refresh_token": "..." }
| Token | Lifetime | Storage | Revocation |
|---|---|---|---|
| Access (JWT) | 15 minutes | Client memory | Expires naturally |
| Refresh | Long-lived | Server-side | Server-side revocable |
Usage: Include Authorization: Bearer <access_token> on all /api/* requests.
Refresh: When the access token expires, use POST /auth/refresh with the refresh token.
API Key Authentication (CLI, Telegram)
Used for long-lived, non-interactive access.
POST /api/keys
Authorization: Bearer <token>
→ { "api_key": "ak_..." }
- Hub stores SHA-256 hash of the key in
users.api_key_hash - Raw key is returned once — store it securely
- Client includes
X-API-Key: <raw_key>header
Internal Authentication (Agent → Hub)
Agent containers push data to hub via /internal/* endpoints:
X-Internal-Secretheader must match hub'sINTERNAL_SECRETenv var- Secret is injected as Docker env var at container spawn time
- Clients never use this authentication method
Secret Management
Encrypted Storage
API keys and sensitive credentials are stored encrypted in PostgreSQL:
- Encryption: AES with key derived from user password/passphrase
- Storage:
user_secretsandagent_secret_overridestables - Values stored: Only AES ciphertext — never plaintext
Secret Resolution Order
When an agent needs a secret, it resolves in this order (first match wins):
- Ephemeral override — per-request, never stored (highest priority)
- Agent secret override — per-agent, encrypted in
agent_secret_overrides - User secret — user-level, encrypted in
user_secrets - Process
.env— hub host environment (lowest priority)
Storing Secrets
User-level secret:
POST /api/secrets
Authorization: Bearer <token>
{ "key_name": "OPENAI_API_KEY", "encrypted_value": "<AES-ciphertext>" }
Agent-specific override:
POST /api/agents/{id}/secrets
Authorization: Bearer <token>
{ "key_name": "ANTHROPIC_API_KEY", "encrypted_value": "<AES-ciphertext>" }
Listing secrets:
GET /api/secrets
Authorization: Bearer <token>
Returns key names only — never values.
Known Secret Keys
| Key | Used By | Purpose |
|---|---|---|
TWELVE_DATA_API_KEY | Hub market cache | Candle data |
OPENAI_API_KEY | App LLM planner | OpenAI LLM |
ANTHROPIC_API_KEY | App LLM planner | Anthropic LLM |
DEEPSEEK_API_KEY | App LLM planner | DeepSeek LLM |
GEMINI_API_KEY | App LLM planner | Google Gemini |
CMC_API_KEY | App market client | CoinMarketCap metadata |
LLM_PROVIDER | App LLM planner | Provider selector |
LLM_MODEL | App LLM planner | Model name |
HASHKEY_API_KEY | App executor | Exchange API key |
HASHKEY_SECRET | App executor | Exchange signing secret |
HASHKEY_SANDBOX | App executor | true for sandbox mode |
HSK_RPC_URL | App on-chain logger | HashKey Chain RPC |
HSK_PRIVATE_KEY | App on-chain logger | Contract caller key |
INTERNAL_SECRET | Hub + App | Agent→hub push auth |
Security Rules
- API keys never stored in plaintext — AES encryption mandatory
- Never log secret values — mask in all output
- Agents receive secrets as Docker env vars at spawn time
- SHA-256 hashing for API key comparison (constant-time)
- Bcrypt for password hashing
- All agent queries scoped by
user_id— multi-tenant isolation
