diff --git a/README.md b/README.md
index 72d38103c..3ddce3a3f 100644
--- a/README.md
+++ b/README.md
@@ -322,14 +322,17 @@ This creates `~/.picoclaw/config.json` and the workspace directory.
"model_list": [
{
"model_name": "gpt-5.4",
- "model": "openai/gpt-5.4",
- "api_key": "sk-your-api-key"
+ "model": "openai/gpt-5.4"
+ // api_key is now loaded from .security.yml
}
]
}
```
> See `config/config.example.json` in the repo for a complete configuration template with all available options.
+>
+> Please note: config.example.json format is version 0, with sensitive codes in it, and will be auto migrated to version 1+, then, the config.json will only store insensitive data, the sensitive codes will be stored in .security.yml, if you need manually modify the codes, please see `docs/security_configuration.md` for more details.
+
**3. Chat**
diff --git a/config/config.example.json b/config/config.example.json
index 88578701a..82aee2904 100644
--- a/config/config.example.json
+++ b/config/config.example.json
@@ -264,79 +264,6 @@
"reasoning_channel_id": ""
}
},
- "providers": {
- "_comment": "DEPRECATED: Use model_list instead. This will be removed in a future version",
- "anthropic": {
- "api_key": "",
- "api_base": ""
- },
- "openai": {
- "api_key": "",
- "api_base": "",
- "web_search": true
- },
- "openrouter": {
- "api_key": "sk-or-v1-xxx",
- "api_base": ""
- },
- "groq": {
- "api_key": "gsk_xxx",
- "api_base": ""
- },
- "zhipu": {
- "api_key": "YOUR_ZHIPU_API_KEY",
- "api_base": ""
- },
- "gemini": {
- "api_key": "",
- "api_base": ""
- },
- "vllm": {
- "api_key": "",
- "api_base": ""
- },
- "nvidia": {
- "api_key": "nvapi-xxx",
- "api_base": "",
- "proxy": "http://127.0.0.1:7890"
- },
- "moonshot": {
- "api_key": "sk-xxx",
- "api_base": ""
- },
- "qwen": {
- "api_key": "sk-xxx",
- "api_base": ""
- },
- "ollama": {
- "api_key": "",
- "api_base": "http://localhost:11434/v1"
- },
- "cerebras": {
- "api_key": "",
- "api_base": ""
- },
- "volcengine": {
- "api_key": "",
- "api_base": ""
- },
- "mistral": {
- "api_key": "",
- "api_base": "https://api.mistral.ai/v1"
- },
- "avian": {
- "api_key": "",
- "api_base": "https://api.avian.io/v1"
- },
- "longcat": {
- "api_key": "",
- "api_base": "https://api.longcat.chat/openai"
- },
- "modelscope": {
- "api_key": "",
- "api_base": "https://api-inference.modelscope.cn/v1"
- }
- },
"tools": {
"allow_read_paths": null,
"allow_write_paths": null,
diff --git a/docs/configuration.md b/docs/configuration.md
index 4e77300cf..d39806887 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -454,6 +454,70 @@ This design also enables **multi-agent support** with flexible provider selectio
- **Load balancing**: Distribute requests across multiple endpoints
- **Centralized configuration**: Manage all providers in one place
+#### 🔒 Security Configuration (Recommended)
+
+PicoClaw supports separating sensitive data (API keys, tokens, secrets) from your main configuration by storing them in a `.security.yml` file.
+
+**Key Benefits:**
+- **Security**: Sensitive data is never in your main config file
+- **Easy sharing**: Share config.json without exposing API keys
+- **Version control**: Add `.security.yml` to `.gitignore`
+- **Flexible deployment**: Different environments can use different security files
+
+**Quick Setup:**
+
+1. Create `~/.picoclaw/.security.yml` with your API keys:
+```yaml
+model_list:
+ gpt-5.4:
+ api_keys:
+ - "sk-proj-your-actual-openai-key"
+ claude-sonnet-4.6:
+ api_keys:
+ - "sk-ant-your-actual-anthropic-key"
+channels:
+ telegram:
+ token: "your-telegram-bot-token"
+web:
+ brave:
+ api_keys:
+ - "BSAyour-brave-api-key"
+ glm_search:
+ api_key: "your-glm-search-api-key"
+```
+
+2. Set proper permissions:
+```bash
+chmod 600 ~/.picoclaw/.security.yml
+```
+
+3. Remove sensitive fields from `config.json` (recommended):
+```json
+{
+ "model_list": [
+ {
+ "model_name": "gpt-5.4",
+ "model": "openai/gpt-5.4"
+ // api_key loaded from .security.yml
+ }
+ ],
+ "channels": {
+ "telegram": {
+ "enabled": true"
+ // token loaded from .security.yml
+ }
+ }
+}
+```
+
+**How it works:**
+- Values from `.security.yml` are automatically mapped to config fields
+- No special syntax needed — just omit sensitive fields from config.json
+- If a field exists in both files, `.security.yml` value takes precedence
+- You can mix direct values in config.json with security values
+
+For complete documentation, see [`security_configuration.md`](security_configuration.md).
+
#### All Supported Vendors
| Vendor | `model` Prefix | Default API Base | Protocol | API Key |
@@ -515,16 +579,20 @@ This design also enables **multi-agent support** with flexible provider selectio
}
```
+> **Security Note**: You can remove `api_key` fields from your config and store them in `.security.yml` instead. See [Security Configuration](#-security-configuration-recommended) above for details.
+
#### Vendor-Specific Examples
+> **Tip**: You can omit `api_key` fields and store them in `.security.yml` for better security. See [Security Configuration](#-security-configuration-recommended).
+
OpenAI
```json
{
"model_name": "gpt-5.4",
- "model": "openai/gpt-5.4",
- "api_key": "sk-..."
+ "model": "openai/gpt-5.4"
+ // api_key: set in .security.yml
}
```
@@ -536,8 +604,8 @@ This design also enables **multi-agent support** with flexible provider selectio
```json
{
"model_name": "ark-code-latest",
- "model": "volcengine/ark-code-latest",
- "api_key": "sk-..."
+ "model": "volcengine/ark-code-latest"
+ // api_key: set in .security.yml
}
```
@@ -549,8 +617,8 @@ This design also enables **multi-agent support** with flexible provider selectio
```json
{
"model_name": "glm-4.7",
- "model": "zhipu/glm-4.7",
- "api_key": "your-key"
+ "model": "zhipu/glm-4.7"
+ // api_key: set in .security.yml
}
```
@@ -562,8 +630,8 @@ This design also enables **multi-agent support** with flexible provider selectio
```json
{
"model_name": "deepseek-chat",
- "model": "deepseek/deepseek-chat",
- "api_key": "sk-..."
+ "model": "deepseek/deepseek-chat"
+ // api_key: set in .security.yml
}
```
@@ -575,8 +643,8 @@ This design also enables **multi-agent support** with flexible provider selectio
```json
{
"model_name": "claude-sonnet-4.6",
- "model": "anthropic/claude-sonnet-4.6",
- "api_key": "sk-ant-your-key"
+ "model": "anthropic/claude-sonnet-4.6"
+ // api_key: set in .security.yml
}
```
@@ -616,8 +684,8 @@ For direct Anthropic API access or custom endpoints that only support Anthropic'
{
"model_name": "my-custom-model",
"model": "openai/custom-model",
- "api_base": "https://my-proxy.com/v1",
- "api_key": "sk-..."
+ "api_base": "https://my-proxy.com/v1"
+ // api_key: set in .security.yml
}
```
@@ -629,6 +697,33 @@ PicoClaw strips only the outer `litellm/` prefix before sending the request, so
Configure multiple endpoints for the same model name — PicoClaw will automatically round-robin between them:
+**Option 1: Multiple API Keys in .security.yml (Recommended)**
+
+```yaml
+# .security.yml
+model_list:
+ gpt-5.4:
+ api_keys:
+ - "sk-proj-key-1"
+ - "sk-proj-key-2"
+```
+
+```json
+// config.json
+{
+ "model_list": [
+ {
+ "model_name": "gpt-5.4",
+ "model": "openai/gpt-5.4",
+ "api_base": "https://api.openai.com/v1"
+ // api_keys loaded from .security.yml
+ }
+ ]
+}
+```
+
+**Option 2: Multiple Model Entries**
+
```json
{
"model_list": [
@@ -685,6 +780,8 @@ This keeps the runtime lightweight while making new OpenAI-compatible backends m
}
```
+> **Note**: The `providers` format is deprecated. Use the new `model_list` format with `.security.yml` for better security.
+
@@ -701,18 +798,10 @@ This keeps the runtime lightweight while making new OpenAI-compatible backends m
"dm_scope": "per-channel-peer",
"backlog_limit": 20
},
- "providers": {
- "openrouter": {
- "api_key": "sk-or-v1-xxx"
- },
- "groq": {
- "api_key": "gsk_xxx"
- }
- },
"channels": {
"telegram": {
- "enabled": true,
- "token": "123456:ABC...",
+ "enabled": true"
+ // token: set in .security.yml
"allow_from": ["123456789"]
}
},
@@ -731,6 +820,8 @@ This keeps the runtime lightweight while making new OpenAI-compatible backends m
}
```
+> **Note**: Sensitive fields (`api_key`, `token`, etc.) can be omitted and stored in `.security.yml` for better security.
+
### Scheduled Tasks / Reminders
@@ -754,6 +845,7 @@ Scheduled tasks persist across restarts and are stored in `~/.picoclaw/workspace
| Topic | Description |
| ----- | ----------- |
+| [Security Configuration](security_configuration.md) | Store API keys and secrets in separate `.security.yml` file |
| [Sensitive Data Filtering](sensitive_data_filtering.md) | Filter API keys and tokens from tool results before sending to LLM |
| [Hook System](hooks/README.md) | Event-driven hooks: observers, interceptors, approval hooks |
| [Steering](steering.md) | Inject messages into a running agent loop between tool calls |
diff --git a/docs/credential_encryption.md b/docs/credential_encryption.md
index dde8c782c..de3b70e09 100644
--- a/docs/credential_encryption.md
+++ b/docs/credential_encryption.md
@@ -31,7 +31,7 @@ enc://AAAA...base64...
{
"model_name": "gpt-4o",
"model": "openai/gpt-4o",
- "api_key": "enc://AAAA...base64...",
+ // "api_key": "enc://AAAA...base64..." move to .security.yml
"api_base": "https://api.openai.com/v1"
}
]
diff --git a/docs/security_configuration.md b/docs/security_configuration.md
new file mode 100644
index 000000000..f4fe0e304
--- /dev/null
+++ b/docs/security_configuration.md
@@ -0,0 +1,644 @@
+# Security Configuration
+
+## Overview
+
+PicoClaw supports separating sensitive data (API keys, tokens, secrets, passwords) from the main configuration by storing them in a `.security.yml` file. This improves security by:
+
+1. **Separation of concerns**: Configuration settings and secrets are in separate files
+2. **Easier sharing**: The main config can be shared without exposing sensitive data
+3. **Better version control**: `.security.yml` should be added to `.gitignore`
+4. **Flexible deployment**: Different environments can use different security files
+
+## File Structure
+
+```
+~/.picoclaw/
+├── config.json # Main configuration (safe to share)
+└── .security.yml # Security data (never share)
+```
+
+## How It Works
+
+The security configuration works through **direct field mapping**, NOT through `ref:` string references. The system automatically loads values from `.security.yml` and applies them to the corresponding fields in `config.json`.
+
+### Key Points:
+
+- Values in `.security.yml` are automatically mapped to corresponding fields in the config
+- The mapping is based on field names and structure, not on reference strings
+- If a value exists in `.security.yml`, it **overrides** the value in `config.json`
+- You can omit sensitive fields from `config.json` entirely (recommended)
+
+## Security Configuration Structure
+
+### Complete Example: .security.yml
+
+```yaml
+# Model API Keys
+# All models MUST use `api_keys` (plural) array format
+# Even a single key must be provided as an array with one element
+model_list:
+ gpt-5.4:
+ api_keys:
+ - "sk-proj-your-actual-openai-key-1"
+ - "sk-proj-your-actual-openai-key-2" # Optional: Multiple keys for failover
+ claude-sonnet-4.6:
+ api_keys:
+ - "sk-ant-your-actual-anthropic-key" # Single key in array format
+
+# Channel Tokens
+channels:
+ telegram:
+ token: "your-telegram-bot-token"
+ feishu:
+ app_secret: "your-feishu-app-secret"
+ encrypt_key: "your-feishu-encrypt-key"
+ verification_token: "your-feishu-verification-token"
+ discord:
+ token: "your-discord-bot-token"
+ weixin:
+ token: "your-weixin-token"
+ qq:
+ app_secret: "your-qq-app-secret"
+ dingtalk:
+ client_secret: "your-dingtalk-client-secret"
+ slack:
+ bot_token: "your-slack-bot-token"
+ app_token: "your-slack-app-token"
+ matrix:
+ access_token: "your-matrix-access-token"
+ line:
+ channel_secret: "your-line-channel-secret"
+ channel_access_token: "your-line-channel-access-token"
+ onebot:
+ access_token: "your-onebot-access-token"
+ wecom:
+ token: "your-wecom-token"
+ encoding_aes_key: "your-wecom-encoding-aes-key"
+ wecom_app:
+ corp_secret: "your-wecom-app-corp-secret"
+ token: "your-wecom-app-token"
+ encoding_aes_key: "your-wecom-app-encoding-aes-key"
+ wecom_aibot:
+ secret: "your-wecom-aibot-secret"
+ token: "your-wecom-aibot-token"
+ encoding_aes_key: "your-wecom-aibot-encoding-aes-key"
+ pico:
+ token: "your-pico-token"
+ irc:
+ password: "your-irc-password"
+ nickserv_password: "your-irc-nickserv-password"
+ sasl_password: "your-irc-sasl-password"
+
+# Web Tool API Keys
+web:
+ brave:
+ api_keys:
+ - "BSAyour-brave-api-key-1"
+ - "BSAyour-brave-api-key-2" # Optional: Multiple keys for failover
+ tavily:
+ api_keys:
+ - "tvly-your-tavily-api-key" # Single key in array format
+ perplexity:
+ api_keys:
+ - "pplx-your-perplexity-api-key" # Single key in array format
+ glm_search:
+ api_key: "your-glm-search-api-key" # GLMSearch uses single key format (not array)
+ baidu_search:
+ api_key: "your-baidu-search-api-key"
+
+# Skills Registry Tokens
+skills:
+ github:
+ token: "your-github-token"
+ clawhub:
+ auth_token: "your-clawhub-auth-token"
+```
+
+## Usage
+
+### Step 1: Create .security.yml
+
+Create or copy the security file:
+```bash
+cp security.example.yml ~/.picoclaw/.security.yml
+```
+
+### Step 2: Fill in your actual values
+
+Edit `~/.picoclaw/.security.yml` and replace placeholder values with your actual API keys and tokens.
+
+### Step 3: Set proper permissions
+
+```bash
+chmod 600 ~/.picoclaw/.security.yml
+```
+
+### Step 4: Simplify config.json (Recommended)
+
+You can now remove sensitive fields from `config.json` since they're loaded from `.security.yml`:
+
+**Before:**
+```json
+{
+ "model_list": [
+ {
+ "model_name": "gpt-5.4",
+ "model": "openai/gpt-5.4",
+ "api_base": "https://api.openai.com/v1",
+ "api_key": "sk-your-actual-api-key-here"
+ }
+ ],
+ "channels": {
+ "telegram": {
+ "enabled": true,
+ "token": "1234567890:ABCdefGHIjklMNOpqrsTUVwxyz"
+ }
+ }
+}
+```
+
+**After:**
+```json
+{
+ "model_list": [
+ {
+ "model_name": "gpt-5.4",
+ "model": "openai/gpt-5.4",
+ "api_base": "https://api.openai.com/v1"
+ // api_key is now loaded from .security.yml
+ }
+ ],
+ "channels": {
+ "telegram": {
+ "enabled": true"
+ // token is now loaded from .security.yml
+ }
+ }
+}
+```
+
+### Step 5: Verify
+
+Restart PicoClaw and verify it loads correctly:
+```bash
+picoclaw --version
+```
+
+## Field Mapping Rules
+
+### Models
+
+**In .security.yml:**
+```yaml
+model_list:
+ :
+ api_keys:
+ - "key-1"
+ - "key-2"
+```
+
+**Mapping:**
+- Field `api_keys` (array) maps to the model's API keys
+- The `` must match the `model_name` field in `config.json`
+- Supports indexed names (e.g., "gpt-5.4:0") - the system will also try the base name ("gpt-5.4")
+
+### Channels
+
+Each channel maps its fields directly:
+
+**In .security.yml:**
+```yaml
+channels:
+ telegram:
+ token: "value"
+ feishu:
+ app_secret: "value"
+ encrypt_key: "value"
+ verification_token: "value"
+ discord:
+ token: "value"
+```
+
+**Mapping:**
+- `channels.telegram.token` → `config.channels.telegram.token`
+- `channels.feishu.app_secret` → `config.channels.feishu.app_secret`
+- etc.
+
+### Web Tools
+
+**Brave, Tavily, Perplexity:**
+```yaml
+web:
+ brave:
+ api_keys:
+ - "key-1"
+ - "key-2"
+```
+- Use `api_keys` (plural) array format
+
+**GLMSearch:**
+```yaml
+web:
+ glm_search:
+ api_key: "single-key-here"
+```
+- Use `api_key` (singular) single string format
+
+**BaiduSearch:**
+```yaml
+web:
+ baidu_search:
+ api_key: "your-key"
+```
+- Use `api_key` (singular) single string format
+
+### Skills
+
+**In .security.yml:**
+```yaml
+skills:
+ github:
+ token: "value"
+ clawhub:
+ auth_token: "value"
+```
+
+## API Key Formats
+
+### Models - Single key
+
+Use array format with one element:
+```yaml
+model_list:
+ gpt-5.4:
+ api_keys:
+ - "sk-your-key"
+```
+
+### Models - Multiple keys (Load Balancing & Failover)
+
+Use array format with multiple elements:
+```yaml
+model_list:
+ gpt-5.4:
+ api_keys:
+ - "sk-your-key-1"
+ - "sk-your-key-2"
+ - "sk-your-key-3"
+```
+
+**Benefits:**
+- **Load balancing**: Requests are distributed across multiple keys
+- **Failover**: Automatic switching to another key if one fails
+- **Rate limit management**: Distribute usage across multiple keys
+- **High availability**: Reduce downtime during API provider issues
+
+### Web Tools (Brave/Tavily/Perplexity) - Single key
+
+```yaml
+web:
+ brave:
+ api_keys:
+ - "BSA-your-key"
+```
+
+### Web Tools (Brave/Tavily/Perplexity) - Multiple keys
+
+```yaml
+web:
+ brave:
+ api_keys:
+ - "BSA-key-1"
+ - "BSA-key-2"
+```
+
+### Web Tool (GLMSearch/BaiduSearch) - Single key only
+
+```yaml
+web:
+ glm_search:
+ api_key: "your-glm-key" # Single string (NOT array)
+ baidu_search:
+ api_key: "your-baidu-key" # Single string (NOT array)
+```
+
+## Model Name Matching
+
+The system supports intelligent model name matching in `.security.yml`:
+
+### Example 1: Exact Match
+
+**config.json:**
+```json
+{
+ "model_name": "gpt-5.4:0"
+}
+```
+
+**.security.yml (exact match with index):**
+```yaml
+model_list:
+ gpt-5.4:0:
+ api_keys: ["key-1"]
+```
+
+### Example 2: Base Name Match
+
+**config.json:**
+```json
+{
+ "model_name": "gpt-5.4:0"
+}
+```
+
+**.security.yml (base name without index):**
+```yaml
+model_list:
+ gpt-5.4:
+ api_keys: ["key-1", "key-2"]
+```
+
+Both methods work. The base name match allows you to use simpler keys in `.security.yml` even when your config uses indexed model names for load balancing.
+
+## Backward Compatibility
+
+The system maintains full backward compatibility:
+
+1. **Direct values**: You can still use direct values in `config.json` (not recommended for production)
+2. **Mixed usage**: You can have some fields in `.security.yml` and others in `config.json`
+3. **Optional security file**: If `.security.yml` doesn't exist, the system will only use values from `config.json`
+4. **Override behavior**: If a field exists in both files, `.security.yml` value takes precedence
+
+## Environment Variables
+
+You can override any security value using environment variables:
+
+**For models:**
+```bash
+export PICOCLAW_CHANNELS_TELEGRAM_TOKEN="token-from-env"
+```
+
+**For channels:**
+```bash
+export PICOCLAW_CHANNELS_TELEGRAM_TOKEN="token-from-env"
+export PICOCLAW_CHANNELS_FEISHU_APP_SECRET="secret-from-env"
+```
+
+**For web tools:**
+```bash
+export PICOCLAW_TOOLS_WEB_BRAVE_API_KEY="key-from-env"
+export PICOCLAW_TOOLS_WEB_BAIDU_API_KEY="baidu-key-from-env"
+```
+
+Environment variables have the highest priority and will override both `config.json` and `.security.yml` values.
+
+The pattern is: `PICOCLAW___` with underscores separating path segments and converted to uppercase.
+
+## Security Best Practices
+
+1. **Never commit `.security.yml`** to version control
+2. **Add to .gitignore**: Ensure `.security.yml` is in your `.gitignore` file
+3. **Set file permissions**: `chmod 600 ~/.picoclaw/.security.yml`
+4. **Use different keys** for different environments (dev, staging, production)
+5. **Rotate keys regularly** and update `.security.yml`
+6. **Backup securely**: Encrypt backups containing `.security.yml`
+7. **Review access**: Ensure only authorized users have read access to the file
+
+## API
+
+### loadSecurityConfig
+
+```go
+func loadSecurityConfig(securityPath string) (*SecurityConfig, error)
+```
+
+Loads the security configuration from `.security.yml`. Returns an empty `SecurityConfig` if the file doesn't exist.
+
+### saveSecurityConfig
+
+```go
+func saveSecurityConfig(securityPath string, sec *SecurityConfig) error
+```
+
+Saves the security configuration to `.security.yml` with `0o600` permissions.
+
+### applySecurityConfig
+
+```go
+func applySecurityConfig(cfg *Config, sec *SecurityConfig) error
+```
+
+Applies security configuration to the main config by copying values from `.security.yml` to the corresponding fields in the config.
+
+### securityPath
+
+```go
+func securityPath(configPath string) string
+```
+
+Returns the path to `.security.yml` relative to the config file.
+
+## Example: Complete Configuration
+
+### config.json
+
+```json
+{
+ "version": 1,
+ "agents": {
+ "defaults": {
+ "workspace": "~/picoclaw-workspace",
+ "model_name": "gpt-5.4"
+ }
+ },
+ "model_list": [
+ {
+ "model_name": "gpt-5.4",
+ "model": "openai/gpt-5.4",
+ "api_base": "https://api.openai.com/v1"
+ },
+ {
+ "model_name": "claude-sonnet-4.6",
+ "model": "anthropic/claude-sonnet-4.6",
+ "api_base": "https://api.anthropic.com/v1"
+ }
+ ],
+ "channels": {
+ "telegram": {
+ "enabled": true
+ }
+ },
+ "tools": {
+ "web": {
+ "brave": {
+ "enabled": true
+ }
+ }
+ }
+}
+```
+
+### .security.yml
+
+```yaml
+model_list:
+ gpt-5.4:
+ api_keys:
+ - "sk-proj-actual-openai-key-1"
+ - "sk-proj-actual-openai-key-2"
+ claude-sonnet-4.6:
+ api_keys:
+ - "sk-ant-actual-anthropic-key"
+
+channels:
+ telegram:
+ token: "1234567890:ABCdefGHIjklMNOpqrsTUVwxyz"
+
+web:
+ brave:
+ api_keys:
+ - "BSAactualbravekey-1"
+ - "BSAactualbravekey-2"
+ tavily:
+ api_keys:
+ - "tvly-your-tavily-key"
+ glm_search:
+ api_key: "your-glm-key"
+ baidu_search:
+ api_key: "your-baidu-key"
+```
+
+## Testing
+
+Run the security configuration tests:
+
+```bash
+go test ./pkg/config -run TestSecurityConfig
+```
+
+## Troubleshooting
+
+### Error: "failed to load security config"
+
+- Verify `.security.yml` exists in the same directory as `config.json`
+- Check the YAML syntax is valid (use a YAML validator)
+- Ensure file permissions allow reading
+
+### Error: "model security entry not found"
+
+- Ensure the model name in `config.json` matches exactly in `.security.yml`
+- Check that the `model_list` section exists in `.security.yml`
+- For models with indexed names (e.g., "gpt-5.4:0"), ensure the exact name is used or check the base name without index
+- Verify the YAML structure is correct (proper indentation)
+
+### Multiple API Keys Not Working
+
+- Ensure you're using `api_keys` (plural) in `.security.yml` for models and web tools (except GLMSearch/BaiduSearch)
+- Check that the array format is correct in YAML (proper indentation with dashes)
+- Remember: Models, Brave, Tavily, Perplexity MUST use `api_keys` (array format)
+- GLMSearch and BaiduSearch MUST use `api_key` (single string format)
+
+### Load Balancing/Failover Issues
+
+- Verify all API keys in the `api_keys` array are valid
+- Check that all keys have the same rate limits and permissions
+- Monitor logs to see which keys are being used and failing
+- Ensure the `api_keys` array is properly formatted in YAML
+
+### Keys Not Being Applied
+
+- Check that `.security.yml` is in the same directory as `config.json`
+- Verify the file permissions allow reading (`chmod 600 ~/.picoclaw/.security.yml`)
+- Ensure the YAML structure matches the expected format
+- Check for typos in field names (case-sensitive)
+- Verify the model/channel names match exactly (case-sensitive)
+
+## Migration Guide
+
+### Step 1: Backup your config
+
+```bash
+cp ~/.picoclaw/config.json ~/.picoclaw/config.json.backup
+```
+
+### Step 2: Create .security.yml
+
+```bash
+cp security.example.yml ~/.picoclaw/.security.yml
+```
+
+### Step 3: Fill in your API keys
+
+Edit `~/.picoclaw/.security.yml` and replace placeholder values with your actual keys.
+
+### Step 4: Remove sensitive fields from config.json
+
+Remove or comment out sensitive fields from `config.json`:
+- `api_key` fields from `model_list` entries
+- `token` fields from `channels`
+- `api_key` fields from `tools.web`
+- `token`/`auth_token` fields from `tools.skills`
+
+### Step 5: Set proper permissions
+
+```bash
+chmod 600 ~/.picoclaw/.security.yml
+```
+
+### Step 6: Test
+
+```bash
+picoclaw --version
+```
+
+### Step 7: Verify functionality
+
+Test your models and channels to ensure everything works correctly.
+
+### Step 8: Clean up (optional)
+
+If everything works, you can delete the backup:
+```bash
+rm ~/.picoclaw/config.json.backup
+```
+
+## Advanced: Encrypted API Keys
+
+PicoClaw supports encrypting API keys in the security file for additional protection.
+
+### Setup
+
+1. Set a passphrase via environment variable:
+```bash
+export PICOCLAW_CREDENTIAL_PASSPHRASE="your-secure-passphrase"
+```
+
+2. When saving config, API keys will be encrypted automatically:
+```go
+SaveConfig(path, config)
+```
+
+### Encrypted Format
+
+Encrypted keys are stored as:
+```yaml
+model_list:
+ gpt-5.4:
+ api_keys:
+ - "enc://encrypted-base64-string"
+```
+
+The system automatically decrypts keys at runtime when loading the configuration.
+
+### Benefits
+
+- Additional layer of security
+- Keys are encrypted at rest
+- Passphrase can be managed separately from the config file
+
+### Important Notes
+
+- Always backup your passphrase securely
+- If you lose the passphrase, you'll lose access to encrypted keys
+- Use a strong, unique passphrase
+- Never commit the passphrase to version control
diff --git a/pkg/config/SECURITY_CONFIG.md b/pkg/config/SECURITY_CONFIG.md
deleted file mode 100644
index c5aed54ae..000000000
--- a/pkg/config/SECURITY_CONFIG.md
+++ /dev/null
@@ -1,551 +0,0 @@
-# Security Configuration Refactoring
-
-## Overview
-
-This refactoring introduces a `.security.yml` file to store all sensitive data (API keys, tokens, secrets, passwords) separately from the main configuration. This improves security by:
-
-1. **Separation of concerns**: Configuration settings and secrets are in separate files
-2. **Easier sharing**: The main config can be shared without exposing sensitive data
-3. **Better version control**: `.security.yml` can be added to `.gitignore`
-4. **Flexible deployment**: Different environments can use different security files
-
-## File Structure
-
-```
-~/.picoclaw/
-├── config.json # Main configuration (safe to share)
-└── .security.yml # Security data (never share)
-```
-
-## Usage
-
-### Basic Configuration
-
-In your `config.json`, use `ref:` references to point to values in `.security.yml`:
-
-```json
-{
- "version": 1,
- "model_list": [
- {
- "model_name": "gpt-5.4",
- "model": "openai/gpt-5.4",
- "api_base": "https://api.openai.com/v1",
- "api_key": "ref:model_list.gpt-5.4.api_key"
- }
- ],
- "channels": {
- "telegram": {
- "enabled": true,
- "token": "ref:channels.telegram.token"
- }
- }
-}
-```
-
-### Security Configuration
-
-In your `.security.yml`, store the actual values:
-
-```yaml
-model_list:
- gpt-5.4:
- api_keys:
- - "sk-your-actual-api-key-1"
- - "sk-your-actual-api-key-2" # Optional: Multiple keys for failover
- claude-sonnet-4.6:
- api_keys:
- - "sk-your-actual-anthropic-key" # Single key in array format
-
-channels:
- telegram:
- token: "your-telegram-bot-token"
-
-web:
- brave:
- api_keys:
- - "BSAyour-brave-api-key-1"
- - "BSAyour-brave-api-key-2" # Optional: Multiple keys for failover
- tavily:
- api_keys:
- - "tvly-your-tavily-api-key" # Single key in array format
- glm_search:
- api_key: "your-glm-search-api-key" # GLMSearch uses single key format
-```
-
-## Reference Format
-
-### Model API Keys
-
-Format: `ref:model_list..api_key`
-
-Example: `ref:model_list.gpt-5.4.api_key`
-
-### Channel Tokens/Secrets
-
-Format: `ref:channels..`
-
-Examples:
-- `ref:channels.telegram.token`
-- `ref:channels.feishu.app_secret`
-- `ref:channels.feishu.encrypt_key`
-- `ref:channels.feishu.verification_token`
-- `ref:channels.discord.token`
-- `ref:channels.qq.app_secret`
-- `ref:channels.dingtalk.client_secret`
-- `ref:channels.slack.bot_token`
-- `ref:channels.slack.app_token`
-- `ref:channels.matrix.access_token`
-- `ref:channels.line.channel_secret`
-- `ref:channels.line.channel_access_token`
-- `ref:channels.onebot.access_token`
-- `ref:channels.wecom.token`
-- `ref:channels.wecom.encoding_aes_key`
-- `ref:channels.wecom_app.corp_secret`
-- `ref:channels.wecom_app.token`
-- `ref:channels.wecom_app.encoding_aes_key`
-- `ref:channels.wecom_aibot.token`
-- `ref:channels.wecom_aibot.encoding_aes_key`
-- `ref:channels.pico.token`
-- `ref:channels.irc.password`
-- `ref:channels.irc.nickserv_password`
-- `ref:channels.irc.sasl_password`
-
-### Web Tool API Keys
-
-Format: `ref:web..`
-
-Examples:
-- `ref:web.brave.api_key`
-- `ref:web.tavily.api_key`
-- `ref:web.perplexity.api_key`
-- `ref:web.glm_search.api_key`
-
-### Skills Registry Tokens
-
-Format: `ref:skills..`
-
-Examples:
-- `ref:skills.github.token`
-- `ref:skills.clawhub.auth_token`
-
-## Backward Compatibility
-
-The refactoring maintains full backward compatibility:
-
-1. **Direct values**: You can still use direct values in `config.json` (not recommended for production)
-2. **Mixed usage**: You can mix `ref:` references and direct values
-3. **Optional security file**: If `.security.yml` doesn't exist, all references will fail (but direct values still work)
-
-### API Key Formats in .security.yml
-
-**Models (gpt-5.4, claude-sonnet-4.6, etc.):**
-- Must use `api_keys` (array) format
-- Both single and multiple keys use array format
-
-**Web Tools (Brave, Tavily, Perplexity):**
-- Must use `api_keys` (array) format
-- Both single and multiple keys use array format
-
-**Web Tools (GLMSearch):**
-- Must use `api_key` (single string) format
-- Does NOT support array format
-
-**Channels (Telegram, Discord, etc.):**
-- Use single field names (e.g., `token`, `app_secret`)
-- Each channel uses its specific field names
-
-### Single Key (Models)
-
-Use array format with one element:
-```yaml
-model_list:
- gpt-5.4:
- api_keys:
- - "sk-your-key"
-```
-
-In `config.json`:
-```json
-{
- "api_key": "ref:model_list.gpt-5.4.api_key"
-}
-```
-
-### Single Key (GLMSearch)
-
-Use single string format:
-```yaml
-web:
- glm_search:
- api_key: "your-glm-key"
-```
-
-In `config.json`:
-```json
-{
- "api_key": "ref:web.glm_search.api_key"
-}
-```
-
-## Migration Guide
-
-### Step 1: Create .security.yml
-
-Copy the example template:
-```bash
-cp security.example.yml ~/.picoclaw/.security.yml
-```
-
-### Step 2: Fill in your actual values
-
-Edit `~/.picoclaw/.security.yml` and replace placeholder values with your actual API keys and tokens.
-
-### Step 3: Update config.json
-
-Replace sensitive values in `~/.picoclaw/config.json` with `ref:` references:
-
-**Before:**
-```json
-{
- "model_list": [
- {
- "model_name": "gpt-5.4",
- "model": "openai/gpt-5.4",
- "api_key": "sk-your-actual-api-key-here"
- }
- ]
-}
-```
-
-**After:**
-```json
-{
- "model_list": [
- {
- "model_name": "gpt-5.4",
- "model": "openai/gpt-5.4",
- "api_key": "ref:model_list.gpt-5.4.api_key"
- }
- ]
-}
-```
-
-### Step 4: Verify
-
-Restart PicoClaw and verify it loads correctly:
-```bash
-picoclaw --version
-```
-
-## Security Best Practices
-
-1. **Never commit `.security.yml`** to version control
-2. **Set file permissions**: `chmod 600 ~/.picoclaw/.security.yml`
-3. **Use different keys** for different environments (dev, staging, production)
-4. **Rotate keys regularly** and update `.security.yml`
-5. **Backup securely**: Encrypt backups containing `.security.yml`
-
-## API
-
-### LoadSecurityConfig
-
-```go
-func LoadSecurityConfig(securityPath string) (*SecurityConfig, error)
-```
-
-Loads the security configuration from `.security.yml`. Returns an empty `SecurityConfig` if the file doesn't exist.
-
-### SaveSecurityConfig
-
-```go
-func SaveSecurityConfig(securityPath string, sec *SecurityConfig) error
-```
-
-Saves the security configuration to `.security.yml` with `0o600` permissions.
-
-### ResolveReference
-
-```go
-func (sec *SecurityConfig) ResolveReference(ref string) (string, error)
-```
-
-Resolves a reference string (e.g., `"ref:model_list.test.api_key"`) and returns the actual value.
-
-### SecurityPath
-
-```go
-func SecurityPath(configPath string) string
-```
-
-Returns the path to `.security.yml` relative to the config file.
-
-## Example: Complete Configuration
-
-### config.json
-```json
-{
- "version": 1,
- "agents": {
- "defaults": {
- "workspace": "~/picoclaw-workspace",
- "model_name": "gpt-5.4"
- }
- },
- "model_list": [
- {
- "model_name": "gpt-5.4",
- "model": "openai/gpt-5.4",
- "api_base": "https://api.openai.com/v1",
- "api_key": "ref:model_list.gpt-5.4.api_key"
- },
- {
- "model_name": "claude-sonnet-4.6",
- "model": "anthropic/claude-sonnet-4.6",
- "api_base": "https://api.anthropic.com/v1",
- "api_key": "ref:model_list.claude-sonnet-4.6.api_key"
- }
- ],
- "channels": {
- "telegram": {
- "enabled": true,
- "token": "ref:channels.telegram.token"
- }
- },
- "tools": {
- "web": {
- "brave": {
- "enabled": true,
- "api_key": "ref:web.brave.api_key"
- }
- }
- }
-}
-```
-
-### .security.yml
-```yaml
-model_list:
- gpt-5.4:
- api_keys:
- - "sk-proj-actual-openai-key-1"
- - "sk-proj-actual-openai-key-2"
- claude-sonnet-4.6:
- api_keys:
- - "sk-ant-actual-anthropic-key" # Single key in array format
-
-channels:
- telegram:
- token: "1234567890:ABCdefGHIjklMNOpqrsTUVwxyz"
-
-web:
- brave:
- api_keys:
- - "BSAactualbravekey-1"
- - "BSAactualbravekey-2"
- tavily:
- api_keys:
- - "tvly-your-tavily-key" # Single key in array format
- glm_search:
- api_key: "your-glm-key" # GLMSearch uses single key format
-```
-
-## Testing
-
-The refactoring includes comprehensive tests:
-
-```bash
-go test ./pkg/config -run TestSecurityConfig
-```
-
-## Troubleshooting
-
-### Error: "model security entry not found"
-
-- Ensure the model name in your reference matches exactly in `.security.yml`
-- Check that the `model_list` section exists in `.security.yml`
-- For models with indexed names (e.g., "gpt-5.4:0"), ensure the exact name is used or check the base name without index
-
-### Error: "failed to load security config"
-
-- Verify `.security.yml` exists in the same directory as `config.json`
-- Check the YAML syntax is valid (use a YAML validator)
-- Ensure file permissions allow reading
-
-### Error: "unknown reference path"
-
-- Verify the reference format is correct
-- Check the path structure matches the examples above
-- Ensure all required sections exist in `.security.yml`
-
-## Advanced Features
-
-### Multiple API Keys (Load Balancing & Failover)
-
-Both models and web tools support multiple API keys for improved reliability:
-
-**Benefits:**
-- **Load balancing**: Requests are distributed across multiple keys
-- **Failover**: Automatic switching to another key if one fails
-- **Rate limit management**: Distribute usage across multiple keys
-- **High availability**: Reduce downtime during API provider issues
-
-#### Example: Model with Multiple Keys
-
-**.security.yml:**
-```yaml
-model_list:
- gpt-5.4:
- api_keys:
- - "sk-proj-key-1"
- - "sk-proj-key-2"
- - "sk-proj-key-3"
-```
-
-**config.json:**
-```json
-{
- "model_list": [
- {
- "model_name": "gpt-5.4",
- "model": "openai/gpt-5.4",
- "api_key": "ref:model_list.gpt-5.4.api_key"
- }
- ]
-}
-```
-
-#### Example: Web Tool with Multiple Keys
-
-**.security.yml:**
-```yaml
-web:
- brave:
- api_keys:
- - "BSA-key-1"
- - "BSA-key-2"
- tavily:
- api_keys:
- - "tvly-your-key" # Single key in array format
- glm_search:
- api_key: "your-glm-key" # GLMSearch uses single key format
-```
-
-**config.json:**
-```json
-{
- "tools": {
- "web": {
- "brave": {
- "enabled": true,
- "api_key": "ref:web.brave.api_key"
- },
- "tavily": {
- "enabled": true,
- "api_key": "ref:web.tavily.api_key"
- }
- }
- }
-}
-```
-
-#### Supported Formats
-
-**Models - Single key:**
-```yaml
-model_list:
- gpt-5.4:
- api_keys:
- - "sk-your-key" # Array with one element
-```
-
-**Models - Multiple keys:**
-```yaml
-model_list:
- gpt-5.4:
- api_keys:
- - "sk-your-key-1"
- - "sk-your-key-2"
- - "sk-your-key-3"
-```
-
-**Web Tools (Brave/Tavily/Perplexity) - Single key:**
-```yaml
-web:
- brave:
- api_keys:
- - "BSA-your-key" # Array with one element
-```
-
-**Web Tools (Brave/Tavily/Perplexity) - Multiple keys:**
-```yaml
-web:
- brave:
- api_keys:
- - "BSA-key-1"
- - "BSA-key-2"
-```
-
-**Web Tool (GLMSearch) - Single key only:**
-```yaml
-web:
- glm_search:
- api_key: "your-glm-key" # Single string (NOT array)
-```
-
-All formats work identically in `config.json` - you always use the same reference format:
-```json
-{
- "api_key": "ref:model_list.gpt-5.4.api_key"
-}
-```
-
-### Model Indexing for Load Balancing
-
-When you have multiple models with the same base name but different API keys, you can use indexed names:
-
-**.security.yml:**
-```yaml
-model_list:
- gpt-5.4:
- api_keys:
- - "sk-proj-key-1"
- - "sk-proj-key-2"
-```
-
-The system will automatically expand this into multiple model entries with fallback support.
-
-### Environment Variables
-
-You can override any security value using environment variables:
-
-**For models:**
-```bash
-export PICOCLAW_MODEL_LIST_GPT-5.4_API_KEY="sk-from-env"
-```
-
-**For channels:**
-```bash
-export PICOCLAW_CHANNELS_TELEGRAM_TOKEN="token-from-env"
-```
-
-**For web tools:**
-```bash
-export PICOCLAW_WEB_BRAVE_API_KEY="key-from-env"
-```
-
-Environment variables follow this pattern: `PICOCLAW____` with dots replaced by underscores and converted to uppercase.
-
-### Multiple API Keys Not Working
-
-- Ensure you're using `api_keys` (plural) in `.security.yml` for models and web tools (except GLMSearch)
-- Check that the array format is correct in YAML (proper indentation)
-- Remember: Models, Brave, Tavily, Perplexity MUST use `api_keys` (array format)
-- GLMSearch MUST use `api_key` (single string format)
-- The reference in `config.json` is the same regardless of single or multiple keys
-
-### Load Balancing/Failover Issues
-
-- Verify all API keys in the `api_keys` array are valid
-- Check that all keys have the same rate limits and permissions
-- Monitor logs to see which keys are being used and failing
diff --git a/pkg/config/example_security_usage.go b/pkg/config/example_security_usage.go
index cba76c6bc..0a6749537 100644
--- a/pkg/config/example_security_usage.go
+++ b/pkg/config/example_security_usage.go
@@ -11,20 +11,33 @@ Package config
# Example: Using Security Configuration
-## 1. Create security.yml
+## Overview
-File: ~/.picoclaw/security.yml
+The security configuration feature allows you to separate sensitive data (API keys,
+tokens, secrets, passwords) from your main configuration. The system automatically
+loads values from `.security.yml` and applies them to the corresponding fields in
+your config.
+
+**Key Points:**
+- Values from `.security.yml` are automatically mapped to config fields
+- No `ref:` syntax is needed - just omit sensitive fields from config.json
+- If a field exists in both files, `.security.yml` value takes precedence
+- You can mix direct values in config.json with security values
+
+## 1. Create .security.yml
+
+File: ~/.picoclaw/.security.yml
```yaml
# Model API Keys
-# Note: Use 'api_keys' array for multiple keys (load balancing/failover)
-# Single key should be provided as an array with one element
+# All models MUST use 'api_keys' (plural) array format
+# Even a single key must be provided as an array with one element
model_list:
gpt-5.4:
api_keys:
- "sk-proj-your-actual-openai-key-1"
- - "sk-proj-your-actual-openai-key-2" # Failover key
+ - "sk-proj-your-actual-openai-key-2" # Optional: Multiple keys for failover
claude-sonnet-4.6:
api_keys:
- "sk-ant-your-actual-anthropic-key" # Single key in array format
@@ -38,80 +51,95 @@ channels:
token: "your-discord-bot-token"
# Web Tool Keys
-# Note: Use 'api_keys' array for multiple keys (load balancing/failover)
-# For GLMSearch, use 'api_key' (single string)
+# Brave, Tavily, Perplexity: Use 'api_keys' array
+# GLMSearch, BaiduSearch: Use 'api_key' single string
web:
brave:
api_keys:
- "BSAyour-brave-api-key-1"
- - "BSAyour-brave-api-key-2" # Failover key
+ - "BSAyour-brave-api-key-2" # Optional: Multiple keys for failover
tavily:
api_keys:
- "tvly-your-tavily-api-key" # Single key in array format
+ perplexity:
+ api_keys:
+ - "pplx-your-perplexity-api-key" # Single key in array format
glm_search:
api_key: "your-glm-search-api-key" # Single key (not array)
+ baidu_search:
+ api_key: "your-baidu-search-api-key" # Single key (not array)
```
-## 2. Update config.json to use references
+## 2. Simplify config.json
File: ~/.picoclaw/config.json
+Note: Sensitive fields are omitted because they're loaded from .security.yml
+
```json
- {
- "version": 1,
- "agents": {
- "defaults": {
- "workspace": "~/picoclaw-workspace",
- "model_name": "gpt-5.4"
- }
- },
- "model_list": [
- {
- "model_name": "gpt-5.4",
- "model": "openai/gpt-5.4",
- "api_base": "https://api.openai.com/v1",
- "api_key": "ref:model_list.gpt-5.4.api_key"
- },
- {
- "model_name": "claude-sonnet-4.6",
- "model": "anthropic/claude-sonnet-4.6",
- "api_base": "https://api.anthropic.com/v1",
- "api_key": "ref:model_list.claude-sonnet-4.6.api_key"
- }
- ],
- "channels": {
- "telegram": {
- "enabled": true,
- "token": "ref:channels.telegram.token"
- },
- "discord": {
- "enabled": true,
- "token": "ref:channels.discord.token"
- }
- },
+ {
+ "version": 1,
+ "agents": {
+ "defaults": {
+ "workspace": "~/picoclaw-workspace",
+ "model_name": "gpt-5.4"
+ }
+ },
+ "model_list": [
+ {
+ "model_name": "gpt-5.4",
+ "model": "openai/gpt-5.4",
+ "api_base": "https://api.openai.com/v1"
+ // api_key is automatically loaded from .security.yml
+ },
+ {
+ "model_name": "claude-sonnet-4.6",
+ "model": "anthropic/claude-sonnet-4.6",
+ "api_base": "https://api.anthropic.com/v1"
+ // api_key is automatically loaded from .security.yml
+ }
+ ],
+ "channels": {
+ "telegram": {
+ "enabled": true"
+ // token is automatically loaded from .security.yml
+ },
+ "discord": {
+ "enabled": true"
+ // token is automatically loaded from .security.yml
+ }
+ },
"tools": {
"web": {
"brave": {
- "enabled": true,
- "api_key": "ref:web.brave.api_key"
+ "enabled": true"
+ // api_key is automatically loaded from .security.yml
},
"tavily": {
- "enabled": true,
- "api_key": "ref:web.tavily.api_key"
+ "enabled": true"
+ // api_key is automatically loaded from .security.yml
+ },
+ "glm_search": {
+ "enabled": true"
+ // api_key is automatically loaded from .security.yml
+ },
+ "baidu_search": {
+ "enabled": true"
+ // api_key is automatically loaded from .security.yml
}
}
}
- }
+ }
```
## 3. Set proper permissions
```bash
-chmod 600 ~/.picoclaw/security.yml
+chmod 600 ~/.picoclaw/.security.yml
```
## 4. Add to .gitignore
@@ -127,57 +155,133 @@ chmod 600 ~/.picoclaw/security.yml
picoclaw --version
```
-# Available Reference Paths
+# Supported Fields in .security.yml
## Model API Keys
-- ref:model_list..api_key
+
+All models MUST use the `api_keys` (plural) array format in .security.yml.
+
+```yaml
+model_list:
+
+ :
+ api_keys:
+ - "key-1"
+ - "key-2" # Optional: Multiple keys for failover
+
+```
Examples:
-- ref:model_list.gpt-5.4.api_key
-- ref:model_list.claude-sonnet-4.6.api_key
+```yaml
+model_list:
-**Note:** In .security.yml, use `api_keys` (array) format for models.
-Both single and multiple keys should use the array format.
+ gpt-5.4:
+ api_keys:
+ - "sk-proj-key-1"
+ - "sk-proj-key-2"
+ claude-sonnet-4.6:
+ api_keys:
+ - "sk-ant-key"
+
+```
+
+**Important:**
+- Always use `api_keys` (plural) for models
+- Even a single key must be in an array format
+- The model_name in .security.yml must match the model_name in config.json
## Channel Tokens/Secrets
-- ref:channels.telegram.token
-- ref:channels.feishu.app_secret
-- ref:channels.feishu.encrypt_key
-- ref:channels.feishu.verification_token
-- ref:channels.discord.token
-- ref:channels.qq.app_secret
-- ref:channels.dingtalk.client_secret
-- ref:channels.slack.bot_token
-- ref:channels.slack.app_token
-- ref:channels.matrix.access_token
-- ref:channels.line.channel_secret
-- ref:channels.line.channel_access_token
-- ref:channels.onebot.access_token
-- ref:channels.wecom.token
-- ref:channels.wecom.encoding_aes_key
-- ref:channels.wecom_app.corp_secret
-- ref:channels.wecom_app.token
-- ref:channels.wecom_app.encoding_aes_key
-- ref:channels.wecom_aibot.token
-- ref:channels.wecom_aibot.encoding_aes_key
-- ref:channels.pico.token
-- ref:channels.irc.password
-- ref:channels.irc.nickserv_password
-- ref:channels.irc.sasl_password
+
+```yaml
+channels:
+
+ telegram:
+ token: "value"
+ feishu:
+ app_secret: "value"
+ encrypt_key: "value"
+ verification_token: "value"
+ discord:
+ token: "value"
+ weixin:
+ token: "value"
+ qq:
+ app_secret: "value"
+ dingtalk:
+ client_secret: "value"
+ slack:
+ bot_token: "value"
+ app_token: "value"
+ matrix:
+ access_token: "value"
+ line:
+ channel_secret: "value"
+ channel_access_token: "value"
+ onebot:
+ access_token: "value"
+ wecom:
+ token: "value"
+ encoding_aes_key: "value"
+ wecom_app:
+ corp_secret: "value"
+ token: "value"
+ encoding_aes_key: "value"
+ wecom_aibot:
+ secret: "value"
+ token: "value"
+ encoding_aes_key: "value"
+ pico:
+ token: "value"
+ irc:
+ password: "value"
+ nickserv_password: "value"
+ sasl_password: "value"
+
+```
## Web Tool API Keys
-- ref:web.brave.api_key
-- ref:web.tavily.api_key
-- ref:web.perplexity.api_key
-- ref:web.glm_search.api_key
-**Note:**
-- Brave, Tavily, Perplexity: Use `api_keys` (array) format in .security.yml
-- GLMSearch: Use `api_key` (single string) format in .security.yml
+**Brave, Tavily, Perplexity:**
+```yaml
+web:
+
+ brave:
+ api_keys:
+ - "BSA-key-1"
+ - "BSA-key-2"
+ tavily:
+ api_keys:
+ - "tvly-key"
+ perplexity:
+ api_keys:
+ - "pplx-key"
+
+```
+Use `api_keys` (plural) array format.
+
+**GLMSearch, BaiduSearch:**
+```yaml
+web:
+
+ glm_search:
+ api_key: "your-glm-key"
+ baidu_search:
+ api_key: "your-baidu-key"
+
+```
+Use `api_key` (singular) single string format.
## Skills Registry Tokens
-- ref:skills.github.token
-- ref:skills.clawhub.auth_token
+
+```yaml
+skills:
+
+ github:
+ token: "value"
+ clawhub:
+ auth_token: "value"
+
+```
# Backward Compatibility
@@ -191,14 +295,14 @@ You can still use direct values in config.json if needed:
"model_name": "local-model",
"model": "ollama/llama3",
"api_base": "http://localhost:11434/v1",
- "api_key": "ollama" // Direct value (no reference)
+ "api_key": "ollama" // Direct value (works fine)
}
]
}
```
-You can also mix references and direct values:
+You can also mix security values and direct values:
```json
@@ -206,10 +310,12 @@ You can also mix references and direct values:
"model_list": [
{
"model_name": "cloud-model",
- "api_key": "ref:model_list.cloud-model.api_key" // From .security.yml
+ // api_key loaded from .security.yml
},
{
"model_name": "local-model",
+ "model": "ollama/llama3",
+ "api_base": "http://localhost:11434/v1",
"api_key": "ollama" // Direct value
}
]
@@ -217,6 +323,11 @@ You can also mix references and direct values:
```
+**Priority Order:**
+1. Environment variables (highest priority)
+2. .security.yml values
+3. config.json direct values (lowest priority)
+
# Migration from Old Config
## Step 1: Backup your config
@@ -224,7 +335,7 @@ You can also mix references and direct values:
cp ~/.picoclaw/config.json ~/.picoclaw/config.json.backup
```
-## Step 2: Copy the example security file
+## Step 2: Create .security.yml
```bash
cp security.example.yml ~/.picoclaw/.security.yml
```
@@ -232,10 +343,19 @@ cp security.example.yml ~/.picoclaw/.security.yml
## Step 3: Fill in your API keys
Edit ~/.picoclaw/.security.yml and replace placeholders with your actual keys.
-## Step 4: Update config.json references
-Replace sensitive values in ~/.picoclaw/config.json with ref: references.
+## Step 4: Simplify config.json (Recommended)
+Remove sensitive fields from ~/.picoclaw/config.json:
+- `api_key` fields from model_list entries
+- `token` fields from channels
+- `api_key` fields from tools.web
+- `token`/`auth_token` fields from tools.skills
-## Step 5: Test
+## Step 5: Set permissions
+```bash
+chmod 600 ~/.picoclaw/.security.yml
+```
+
+## Step 6: Test
```bash
picoclaw --version
```
@@ -249,9 +369,11 @@ rm ~/.picoclaw/config.json.backup
## Multiple API Keys (Load Balancing & Failover)
-You can configure multiple API keys for both models and web tools to enable:
+You can configure multiple API keys for models and web tools to enable:
- **Load balancing**: Requests are distributed across multiple keys
- **Failover**: If a key fails, the system automatically switches to another key
+- **Rate limit management**: Distribute usage across multiple keys
+- **High availability**: Reduce downtime during API provider issues
### Example: Model with Multiple Keys
@@ -275,7 +397,7 @@ model_list:
{
"model_name": "gpt-5.4",
"model": "openai/gpt-5.4",
- "api_key": "ref:model_list.gpt-5.4.api_key"
+ "api_base": "https://api.openai.com/v1"
}
]
}
@@ -307,8 +429,13 @@ web:
"tools": {
"web": {
"brave": {
- "enabled": true,
- "api_key": "ref:web.brave.api_key"
+ "enabled": true"
+ },
+ "tavily": {
+ "enabled": true"
+ },
+ "glm_search": {
+ "enabled": true"
}
}
}
@@ -316,9 +443,9 @@ web:
```
-### Single Key
+## Single Key Format
-Use array format with one element:
+**Models, Brave, Tavily, Perplexity:**
```yaml
model_list:
@@ -328,36 +455,32 @@ model_list:
```
-### Multiple Keys (Load Balancing & Failover)
-
-Use array format with multiple elements:
+**GLMSearch, BaiduSearch:**
```yaml
-model_list:
+web:
- gpt-5.4:
- api_keys:
- - "sk-proj-key-1"
- - "sk-proj-key-2"
- - "sk-proj-key-3"
+ glm_search:
+ api_key: "your-glm-key" # Single key (not array)
```
-**Important:** All model keys in .security.yml must use the `api_keys` (plural) array format.
-The single `api_key` (singular) format is NOT supported for models.
-
-### Model Index Matching
+## Model Name Matching
The system supports intelligent model name matching in .security.yml:
-**Example 1: Exact Match**
-```yaml
-# config.json
+### Example 1: Exact Match
+
+**config.json:**
+```json
{
"model_name": "gpt-5.4:0"
}
-# .security.yml (exact match with index)
+```
+
+**.security.yml (exact match with index):**
+```yaml
model_list:
gpt-5.4:0:
@@ -365,26 +488,30 @@ model_list:
```
-**Example 2: Base Name Match**
-```yaml
-# config.json
+### Example 2: Base Name Match
+
+**config.json:**
+```json
{
"model_name": "gpt-5.4:0"
}
-# .security.yml (base name without index)
+```
+
+**.security.yml (base name without index):**
+```yaml
model_list:
gpt-5.4:
- api_keys: ["key-1"]
+ api_keys: ["key-1", "key-2"]
```
Both methods work. The base name match allows you to use simpler keys in .security.yml
even when your config uses indexed model names for load balancing.
-### Security File Permissions
+## Security File Permissions
The security file should have restricted permissions:
@@ -397,26 +524,64 @@ This ensures only the owner can read and write the file.
# Security Best Practices
1. Never commit .security.yml to version control
-2. Set file permissions: chmod 600 ~/.picoclaw/.security.yml
-3. Use different keys for different environments
-4. Rotate keys regularly and update .security.yml
-5. Encrypt backups containing .security.yml
+2. Add .security.yml to your .gitignore file
+3. Set file permissions: chmod 600 ~/.picoclaw/.security.yml
+4. Use different keys for different environments (dev, staging, production)
+5. Rotate keys regularly and update .security.yml
+6. Encrypt backups containing .security.yml
+7. Review access regularly
+
+# Environment Variables
+
+You can override any security value using environment variables:
+
+```bash
+# Channels
+export PICOCLAW_CHANNELS_TELEGRAM_TOKEN="token-from-env"
+export PICOCLAW_CHANNELS_DISCORD_TOKEN="discord-token-from-env"
+
+# Web Tools
+export PICOCLAW_TOOLS_WEB_BRAVE_API_KEY="brave-key-from-env"
+export PICOCLAW_TOOLS_WEB_BAIDU_API_KEY="baidu-key-from-env"
+
+# Skills
+export PICOCLAW_TOOLS_SKILLS_GITHUB_TOKEN="github-token-from-env"
+```
+
+Environment variables have the highest priority and will override both config.json
+and .security.yml values.
# Troubleshooting
+## Error: "failed to load security config"
+- Ensure .security.yml exists in the same directory as config.json
+- Check YAML syntax is valid (use a YAML validator)
+- Verify file permissions allow reading
+
## Error: "model security entry not found"
- Check that the model name in config.json matches exactly in .security.yml
- Verify the model_list section exists in .security.yml
+- For indexed names (e.g., "gpt-5.4:0"), check both exact match and base name match
+- Ensure the YAML structure is correct (proper indentation)
-## Error: "failed to load security config"
-- Ensure .security.yml exists in the same directory as config.json
-- Check YAML syntax is valid
-- Verify file permissions allow reading
+## Multiple API Keys Not Working
+- Ensure you're using `api_keys` (plural) in .security.yml for models and web tools (except GLMSearch/BaiduSearch)
+- Check that the array format is correct in YAML (proper indentation with dashes)
+- Remember: Models, Brave, Tavily, Perplexity MUST use `api_keys` (array format)
+- GLMSearch and BaiduSearch MUST use `api_key` (single string format)
-## Error: "unknown reference path"
-- Verify the reference format is correct
-- Check the path structure matches the examples above
-- Ensure all required sections exist in .security.yml
+## Keys Not Being Applied
+- Check that .security.yml is in the same directory as config.json
+- Verify the file permissions allow reading (chmod 600 ~/.picoclaw/.security.yml)
+- Ensure the YAML structure matches the expected format
+- Check for typos in field names (case-sensitive)
+- Verify the model/channel names match exactly (case-sensitive)
+
+## Load Balancing/Failover Issues
+- Verify all API keys in the api_keys array are valid
+- Check that all keys have the same rate limits and permissions
+- Monitor logs to see which keys are being used and failing
+- Ensure the api_keys array is properly formatted in YAML
*/
package config