Merge pull request #1948 from cytown/fix-doc
update security migration documents
This commit is contained in:
commit
3d20976803
7 changed files with 1061 additions and 771 deletions
|
|
@ -322,14 +322,17 @@ This creates `~/.picoclaw/config.json` and the workspace directory.
|
||||||
"model_list": [
|
"model_list": [
|
||||||
{
|
{
|
||||||
"model_name": "gpt-5.4",
|
"model_name": "gpt-5.4",
|
||||||
"model": "openai/gpt-5.4",
|
"model": "openai/gpt-5.4"
|
||||||
"api_key": "sk-your-api-key"
|
// 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.
|
> 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**
|
**3. Chat**
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -240,79 +240,6 @@
|
||||||
"reasoning_channel_id": ""
|
"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": {
|
"tools": {
|
||||||
"allow_read_paths": null,
|
"allow_read_paths": null,
|
||||||
"allow_write_paths": null,
|
"allow_write_paths": null,
|
||||||
|
|
|
||||||
|
|
@ -454,6 +454,70 @@ This design also enables **multi-agent support** with flexible provider selectio
|
||||||
- **Load balancing**: Distribute requests across multiple endpoints
|
- **Load balancing**: Distribute requests across multiple endpoints
|
||||||
- **Centralized configuration**: Manage all providers in one place
|
- **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
|
#### All Supported Vendors
|
||||||
|
|
||||||
| Vendor | `model` Prefix | Default API Base | Protocol | API Key |
|
| 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
|
#### 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).
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><b>OpenAI</b></summary>
|
<summary><b>OpenAI</b></summary>
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"model_name": "gpt-5.4",
|
"model_name": "gpt-5.4",
|
||||||
"model": "openai/gpt-5.4",
|
"model": "openai/gpt-5.4"
|
||||||
"api_key": "sk-..."
|
// api_key: set in .security.yml
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -536,8 +604,8 @@ This design also enables **multi-agent support** with flexible provider selectio
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"model_name": "ark-code-latest",
|
"model_name": "ark-code-latest",
|
||||||
"model": "volcengine/ark-code-latest",
|
"model": "volcengine/ark-code-latest"
|
||||||
"api_key": "sk-..."
|
// api_key: set in .security.yml
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -549,8 +617,8 @@ This design also enables **multi-agent support** with flexible provider selectio
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"model_name": "glm-4.7",
|
"model_name": "glm-4.7",
|
||||||
"model": "zhipu/glm-4.7",
|
"model": "zhipu/glm-4.7"
|
||||||
"api_key": "your-key"
|
// api_key: set in .security.yml
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -562,8 +630,8 @@ This design also enables **multi-agent support** with flexible provider selectio
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"model_name": "deepseek-chat",
|
"model_name": "deepseek-chat",
|
||||||
"model": "deepseek/deepseek-chat",
|
"model": "deepseek/deepseek-chat"
|
||||||
"api_key": "sk-..."
|
// api_key: set in .security.yml
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -575,8 +643,8 @@ This design also enables **multi-agent support** with flexible provider selectio
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"model_name": "claude-sonnet-4.6",
|
"model_name": "claude-sonnet-4.6",
|
||||||
"model": "anthropic/claude-sonnet-4.6",
|
"model": "anthropic/claude-sonnet-4.6"
|
||||||
"api_key": "sk-ant-your-key"
|
// 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_name": "my-custom-model",
|
||||||
"model": "openai/custom-model",
|
"model": "openai/custom-model",
|
||||||
"api_base": "https://my-proxy.com/v1",
|
"api_base": "https://my-proxy.com/v1"
|
||||||
"api_key": "sk-..."
|
// 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:
|
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
|
```json
|
||||||
{
|
{
|
||||||
"model_list": [
|
"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.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|
@ -701,18 +798,10 @@ This keeps the runtime lightweight while making new OpenAI-compatible backends m
|
||||||
"dm_scope": "per-channel-peer",
|
"dm_scope": "per-channel-peer",
|
||||||
"backlog_limit": 20
|
"backlog_limit": 20
|
||||||
},
|
},
|
||||||
"providers": {
|
|
||||||
"openrouter": {
|
|
||||||
"api_key": "sk-or-v1-xxx"
|
|
||||||
},
|
|
||||||
"groq": {
|
|
||||||
"api_key": "gsk_xxx"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"channels": {
|
"channels": {
|
||||||
"telegram": {
|
"telegram": {
|
||||||
"enabled": true,
|
"enabled": true"
|
||||||
"token": "123456:ABC...",
|
// token: set in .security.yml
|
||||||
"allow_from": ["123456789"]
|
"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.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### Scheduled Tasks / Reminders
|
### Scheduled Tasks / Reminders
|
||||||
|
|
@ -754,6 +845,7 @@ Scheduled tasks persist across restarts and are stored in `~/.picoclaw/workspace
|
||||||
|
|
||||||
| Topic | Description |
|
| 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 |
|
| [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 |
|
| [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 |
|
| [Steering](steering.md) | Inject messages into a running agent loop between tool calls |
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ enc://AAAA...base64...
|
||||||
{
|
{
|
||||||
"model_name": "gpt-4o",
|
"model_name": "gpt-4o",
|
||||||
"model": "openai/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"
|
"api_base": "https://api.openai.com/v1"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
644
docs/security_configuration.md
Normal file
644
docs/security_configuration.md
Normal file
|
|
@ -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:
|
||||||
|
<model_name>:
|
||||||
|
api_keys:
|
||||||
|
- "key-1"
|
||||||
|
- "key-2"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Mapping:**
|
||||||
|
- Field `api_keys` (array) maps to the model's API keys
|
||||||
|
- The `<model_name>` 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_<SECTION>_<KEY>_<FIELD>` 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
|
||||||
|
|
@ -1,545 +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.<model_name>.api_key`
|
|
||||||
|
|
||||||
Example: `ref:model_list.gpt-5.4.api_key`
|
|
||||||
|
|
||||||
### Channel Tokens/Secrets
|
|
||||||
|
|
||||||
Format: `ref:channels.<channel_name>.<field>`
|
|
||||||
|
|
||||||
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.secret`
|
|
||||||
- `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.<provider>.<field>`
|
|
||||||
|
|
||||||
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.<registry>.<field>`
|
|
||||||
|
|
||||||
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_<SECTION>_<KEY1>_<KEY2>_<FIELD>` 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
|
|
||||||
|
|
@ -11,20 +11,33 @@ Package config
|
||||||
|
|
||||||
# Example: Using Security Configuration
|
# 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
|
```yaml
|
||||||
# Model API Keys
|
# Model API Keys
|
||||||
# Note: Use 'api_keys' array for multiple keys (load balancing/failover)
|
# All models MUST use 'api_keys' (plural) array format
|
||||||
# Single key should be provided as an array with one element
|
# Even a single key must be provided as an array with one element
|
||||||
model_list:
|
model_list:
|
||||||
|
|
||||||
gpt-5.4:
|
gpt-5.4:
|
||||||
api_keys:
|
api_keys:
|
||||||
- "sk-proj-your-actual-openai-key-1"
|
- "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:
|
claude-sonnet-4.6:
|
||||||
api_keys:
|
api_keys:
|
||||||
- "sk-ant-your-actual-anthropic-key" # Single key in array format
|
- "sk-ant-your-actual-anthropic-key" # Single key in array format
|
||||||
|
|
@ -38,26 +51,33 @@ channels:
|
||||||
token: "your-discord-bot-token"
|
token: "your-discord-bot-token"
|
||||||
|
|
||||||
# Web Tool Keys
|
# Web Tool Keys
|
||||||
# Note: Use 'api_keys' array for multiple keys (load balancing/failover)
|
# Brave, Tavily, Perplexity: Use 'api_keys' array
|
||||||
# For GLMSearch, use 'api_key' (single string)
|
# GLMSearch, BaiduSearch: Use 'api_key' single string
|
||||||
web:
|
web:
|
||||||
|
|
||||||
brave:
|
brave:
|
||||||
api_keys:
|
api_keys:
|
||||||
- "BSAyour-brave-api-key-1"
|
- "BSAyour-brave-api-key-1"
|
||||||
- "BSAyour-brave-api-key-2" # Failover key
|
- "BSAyour-brave-api-key-2" # Optional: Multiple keys for failover
|
||||||
tavily:
|
tavily:
|
||||||
api_keys:
|
api_keys:
|
||||||
- "tvly-your-tavily-api-key" # Single key in array format
|
- "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:
|
glm_search:
|
||||||
api_key: "your-glm-search-api-key" # Single key (not array)
|
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
|
File: ~/.picoclaw/config.json
|
||||||
|
|
||||||
|
Note: Sensitive fields are omitted because they're loaded from .security.yml
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
@ -72,35 +92,43 @@ File: ~/.picoclaw/config.json
|
||||||
{
|
{
|
||||||
"model_name": "gpt-5.4",
|
"model_name": "gpt-5.4",
|
||||||
"model": "openai/gpt-5.4",
|
"model": "openai/gpt-5.4",
|
||||||
"api_base": "https://api.openai.com/v1",
|
"api_base": "https://api.openai.com/v1"
|
||||||
"api_key": "ref:model_list.gpt-5.4.api_key"
|
// api_key is automatically loaded from .security.yml
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model_name": "claude-sonnet-4.6",
|
"model_name": "claude-sonnet-4.6",
|
||||||
"model": "anthropic/claude-sonnet-4.6",
|
"model": "anthropic/claude-sonnet-4.6",
|
||||||
"api_base": "https://api.anthropic.com/v1",
|
"api_base": "https://api.anthropic.com/v1"
|
||||||
"api_key": "ref:model_list.claude-sonnet-4.6.api_key"
|
// api_key is automatically loaded from .security.yml
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"channels": {
|
"channels": {
|
||||||
"telegram": {
|
"telegram": {
|
||||||
"enabled": true,
|
"enabled": true
|
||||||
"token": "ref:channels.telegram.token"
|
// token is automatically loaded from .security.yml
|
||||||
},
|
},
|
||||||
"discord": {
|
"discord": {
|
||||||
"enabled": true,
|
"enabled": true
|
||||||
"token": "ref:channels.discord.token"
|
// token is automatically loaded from .security.yml
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tools": {
|
"tools": {
|
||||||
"web": {
|
"web": {
|
||||||
"brave": {
|
"brave": {
|
||||||
"enabled": true,
|
"enabled": true
|
||||||
"api_key": "ref:web.brave.api_key"
|
// api_key is automatically loaded from .security.yml
|
||||||
},
|
},
|
||||||
"tavily": {
|
"tavily": {
|
||||||
"enabled": true,
|
"enabled": true
|
||||||
"api_key": "ref:web.tavily.api_key"
|
// 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -111,7 +139,7 @@ File: ~/.picoclaw/config.json
|
||||||
## 3. Set proper permissions
|
## 3. Set proper permissions
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
chmod 600 ~/.picoclaw/security.yml
|
chmod 600 ~/.picoclaw/.security.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
## 4. Add to .gitignore
|
## 4. Add to .gitignore
|
||||||
|
|
@ -127,51 +155,131 @@ chmod 600 ~/.picoclaw/security.yml
|
||||||
picoclaw --version
|
picoclaw --version
|
||||||
```
|
```
|
||||||
|
|
||||||
# Available Reference Paths
|
# Supported Fields in .security.yml
|
||||||
|
|
||||||
## Model API Keys
|
## Model API Keys
|
||||||
- ref:model_list.<model_name>.api_key
|
|
||||||
|
All models MUST use the `api_keys` (plural) array format in .security.yml.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
model_list:
|
||||||
|
|
||||||
|
<model_name>:
|
||||||
|
api_keys:
|
||||||
|
- "key-1"
|
||||||
|
- "key-2" # Optional: Multiple keys for failover
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
- ref:model_list.gpt-5.4.api_key
|
```yaml
|
||||||
- ref:model_list.claude-sonnet-4.6.api_key
|
model_list:
|
||||||
|
|
||||||
**Note:** In .security.yml, use `api_keys` (array) format for models.
|
gpt-5.4:
|
||||||
Both single and multiple keys should use the array format.
|
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
|
## Channel Tokens/Secrets
|
||||||
- ref:channels.telegram.token
|
|
||||||
- ref:channels.feishu.app_secret
|
```yaml
|
||||||
- ref:channels.feishu.encrypt_key
|
channels:
|
||||||
- ref:channels.feishu.verification_token
|
|
||||||
- ref:channels.discord.token
|
telegram:
|
||||||
- ref:channels.qq.app_secret
|
token: "value"
|
||||||
- ref:channels.dingtalk.client_secret
|
feishu:
|
||||||
- ref:channels.slack.bot_token
|
app_secret: "value"
|
||||||
- ref:channels.slack.app_token
|
encrypt_key: "value"
|
||||||
- ref:channels.matrix.access_token
|
verification_token: "value"
|
||||||
- ref:channels.line.channel_secret
|
discord:
|
||||||
- ref:channels.line.channel_access_token
|
token: "value"
|
||||||
- ref:channels.onebot.access_token
|
weixin:
|
||||||
- ref:channels.wecom.secret
|
token: "value"
|
||||||
- ref:channels.pico.token
|
qq:
|
||||||
- ref:channels.irc.password
|
app_secret: "value"
|
||||||
- ref:channels.irc.nickserv_password
|
dingtalk:
|
||||||
- ref:channels.irc.sasl_password
|
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
|
## 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:**
|
||||||
- Brave, Tavily, Perplexity: Use `api_keys` (array) format in .security.yml
|
```yaml
|
||||||
- GLMSearch: Use `api_key` (single string) format in .security.yml
|
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
|
## Skills Registry Tokens
|
||||||
- ref:skills.github.token
|
|
||||||
- ref:skills.clawhub.auth_token
|
```yaml
|
||||||
|
skills:
|
||||||
|
|
||||||
|
github:
|
||||||
|
token: "value"
|
||||||
|
clawhub:
|
||||||
|
auth_token: "value"
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
# Backward Compatibility
|
# Backward Compatibility
|
||||||
|
|
||||||
|
|
@ -185,14 +293,14 @@ You can still use direct values in config.json if needed:
|
||||||
"model_name": "local-model",
|
"model_name": "local-model",
|
||||||
"model": "ollama/llama3",
|
"model": "ollama/llama3",
|
||||||
"api_base": "http://localhost:11434/v1",
|
"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
|
```json
|
||||||
|
|
||||||
|
|
@ -200,10 +308,12 @@ You can also mix references and direct values:
|
||||||
"model_list": [
|
"model_list": [
|
||||||
{
|
{
|
||||||
"model_name": "cloud-model",
|
"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_name": "local-model",
|
||||||
|
"model": "ollama/llama3",
|
||||||
|
"api_base": "http://localhost:11434/v1",
|
||||||
"api_key": "ollama" // Direct value
|
"api_key": "ollama" // Direct value
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -211,6 +321,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
|
# Migration from Old Config
|
||||||
|
|
||||||
## Step 1: Backup your config
|
## Step 1: Backup your config
|
||||||
|
|
@ -218,7 +333,7 @@ You can also mix references and direct values:
|
||||||
cp ~/.picoclaw/config.json ~/.picoclaw/config.json.backup
|
cp ~/.picoclaw/config.json ~/.picoclaw/config.json.backup
|
||||||
```
|
```
|
||||||
|
|
||||||
## Step 2: Copy the example security file
|
## Step 2: Create .security.yml
|
||||||
```bash
|
```bash
|
||||||
cp security.example.yml ~/.picoclaw/.security.yml
|
cp security.example.yml ~/.picoclaw/.security.yml
|
||||||
```
|
```
|
||||||
|
|
@ -226,10 +341,19 @@ cp security.example.yml ~/.picoclaw/.security.yml
|
||||||
## Step 3: Fill in your API keys
|
## Step 3: Fill in your API keys
|
||||||
Edit ~/.picoclaw/.security.yml and replace placeholders with your actual keys.
|
Edit ~/.picoclaw/.security.yml and replace placeholders with your actual keys.
|
||||||
|
|
||||||
## Step 4: Update config.json references
|
## Step 4: Simplify config.json (Recommended)
|
||||||
Replace sensitive values in ~/.picoclaw/config.json with ref: references.
|
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
|
```bash
|
||||||
picoclaw --version
|
picoclaw --version
|
||||||
```
|
```
|
||||||
|
|
@ -243,9 +367,11 @@ rm ~/.picoclaw/config.json.backup
|
||||||
|
|
||||||
## Multiple API Keys (Load Balancing & Failover)
|
## 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
|
- **Load balancing**: Requests are distributed across multiple keys
|
||||||
- **Failover**: If a key fails, the system automatically switches to another key
|
- **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
|
### Example: Model with Multiple Keys
|
||||||
|
|
||||||
|
|
@ -269,7 +395,7 @@ model_list:
|
||||||
{
|
{
|
||||||
"model_name": "gpt-5.4",
|
"model_name": "gpt-5.4",
|
||||||
"model": "openai/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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -301,8 +427,13 @@ web:
|
||||||
"tools": {
|
"tools": {
|
||||||
"web": {
|
"web": {
|
||||||
"brave": {
|
"brave": {
|
||||||
"enabled": true,
|
"enabled": true
|
||||||
"api_key": "ref:web.brave.api_key"
|
},
|
||||||
|
"tavily": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"glm_search": {
|
||||||
|
"enabled": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -310,9 +441,9 @@ web:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Single Key
|
## Single Key Format
|
||||||
|
|
||||||
Use array format with one element:
|
**Models, Brave, Tavily, Perplexity:**
|
||||||
```yaml
|
```yaml
|
||||||
model_list:
|
model_list:
|
||||||
|
|
||||||
|
|
@ -322,36 +453,32 @@ model_list:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Multiple Keys (Load Balancing & Failover)
|
**GLMSearch, BaiduSearch:**
|
||||||
|
|
||||||
Use array format with multiple elements:
|
|
||||||
```yaml
|
```yaml
|
||||||
model_list:
|
web:
|
||||||
|
|
||||||
gpt-5.4:
|
glm_search:
|
||||||
api_keys:
|
api_key: "your-glm-key" # Single key (not array)
|
||||||
- "sk-proj-key-1"
|
|
||||||
- "sk-proj-key-2"
|
|
||||||
- "sk-proj-key-3"
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Important:** All model keys in .security.yml must use the `api_keys` (plural) array format.
|
## Model Name Matching
|
||||||
The single `api_key` (singular) format is NOT supported for models.
|
|
||||||
|
|
||||||
### Model Index Matching
|
|
||||||
|
|
||||||
The system supports intelligent model name matching in .security.yml:
|
The system supports intelligent model name matching in .security.yml:
|
||||||
|
|
||||||
**Example 1: Exact Match**
|
### Example 1: Exact Match
|
||||||
```yaml
|
|
||||||
# config.json
|
**config.json:**
|
||||||
|
```json
|
||||||
|
|
||||||
{
|
{
|
||||||
"model_name": "gpt-5.4:0"
|
"model_name": "gpt-5.4:0"
|
||||||
}
|
}
|
||||||
|
|
||||||
# .security.yml (exact match with index)
|
```
|
||||||
|
|
||||||
|
**.security.yml (exact match with index):**
|
||||||
|
```yaml
|
||||||
model_list:
|
model_list:
|
||||||
|
|
||||||
gpt-5.4:0:
|
gpt-5.4:0:
|
||||||
|
|
@ -359,26 +486,30 @@ model_list:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Example 2: Base Name Match**
|
### Example 2: Base Name Match
|
||||||
```yaml
|
|
||||||
# config.json
|
**config.json:**
|
||||||
|
```json
|
||||||
|
|
||||||
{
|
{
|
||||||
"model_name": "gpt-5.4:0"
|
"model_name": "gpt-5.4:0"
|
||||||
}
|
}
|
||||||
|
|
||||||
# .security.yml (base name without index)
|
```
|
||||||
|
|
||||||
|
**.security.yml (base name without index):**
|
||||||
|
```yaml
|
||||||
model_list:
|
model_list:
|
||||||
|
|
||||||
gpt-5.4:
|
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
|
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.
|
even when your config uses indexed model names for load balancing.
|
||||||
|
|
||||||
### Security File Permissions
|
## Security File Permissions
|
||||||
|
|
||||||
The security file should have restricted permissions:
|
The security file should have restricted permissions:
|
||||||
|
|
||||||
|
|
@ -391,26 +522,64 @@ This ensures only the owner can read and write the file.
|
||||||
# Security Best Practices
|
# Security Best Practices
|
||||||
|
|
||||||
1. Never commit .security.yml to version control
|
1. Never commit .security.yml to version control
|
||||||
2. Set file permissions: chmod 600 ~/.picoclaw/.security.yml
|
2. Add .security.yml to your .gitignore file
|
||||||
3. Use different keys for different environments
|
3. Set file permissions: chmod 600 ~/.picoclaw/.security.yml
|
||||||
4. Rotate keys regularly and update .security.yml
|
4. Use different keys for different environments (dev, staging, production)
|
||||||
5. Encrypt backups containing .security.yml
|
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
|
# 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"
|
## Error: "model security entry not found"
|
||||||
- Check that the model name in config.json matches exactly in .security.yml
|
- Check that the model name in config.json matches exactly in .security.yml
|
||||||
- Verify the model_list section exists 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"
|
## Multiple API Keys Not Working
|
||||||
- Ensure .security.yml exists in the same directory as config.json
|
- Ensure you're using `api_keys` (plural) in .security.yml for models and web tools (except GLMSearch/BaiduSearch)
|
||||||
- Check YAML syntax is valid
|
- Check that the array format is correct in YAML (proper indentation with dashes)
|
||||||
- Verify file permissions allow reading
|
- 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"
|
## Keys Not Being Applied
|
||||||
- Verify the reference format is correct
|
- Check that .security.yml is in the same directory as config.json
|
||||||
- Check the path structure matches the examples above
|
- Verify the file permissions allow reading (chmod 600 ~/.picoclaw/.security.yml)
|
||||||
- Ensure all required sections exist in .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
|
package config
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue