mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
552 lines
12 KiB
Markdown
552 lines
12 KiB
Markdown
# 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.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.<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
|