# 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