12 KiB
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:
- Separation of concerns: Configuration settings and secrets are in separate files
- Easier sharing: The main config can be shared without exposing sensitive data
- Better version control:
.security.ymlcan be added to.gitignore - 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:
{
"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:
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.tokenref:channels.feishu.app_secretref:channels.feishu.encrypt_keyref:channels.feishu.verification_tokenref:channels.discord.tokenref:channels.qq.app_secretref:channels.dingtalk.client_secretref:channels.slack.bot_tokenref:channels.slack.app_tokenref:channels.matrix.access_tokenref:channels.line.channel_secretref:channels.line.channel_access_tokenref:channels.onebot.access_tokenref:channels.wecom.secretref:channels.pico.tokenref:channels.irc.passwordref:channels.irc.nickserv_passwordref:channels.irc.sasl_password
Web Tool API Keys
Format: ref:web.<provider>.<field>
Examples:
ref:web.brave.api_keyref:web.tavily.api_keyref:web.perplexity.api_keyref:web.glm_search.api_key
Skills Registry Tokens
Format: ref:skills.<registry>.<field>
Examples:
ref:skills.github.tokenref:skills.clawhub.auth_token
Backward Compatibility
The refactoring maintains full backward compatibility:
- Direct values: You can still use direct values in
config.json(not recommended for production) - Mixed usage: You can mix
ref:references and direct values - Optional security file: If
.security.ymldoesn'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:
model_list:
gpt-5.4:
api_keys:
- "sk-your-key"
In config.json:
{
"api_key": "ref:model_list.gpt-5.4.api_key"
}
Single Key (GLMSearch)
Use single string format:
web:
glm_search:
api_key: "your-glm-key"
In config.json:
{
"api_key": "ref:web.glm_search.api_key"
}
Migration Guide
Step 1: Create .security.yml
Copy the example template:
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:
{
"model_list": [
{
"model_name": "gpt-5.4",
"model": "openai/gpt-5.4",
"api_key": "sk-your-actual-api-key-here"
}
]
}
After:
{
"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:
picoclaw --version
Security Best Practices
- Never commit
.security.ymlto version control - Set file permissions:
chmod 600 ~/.picoclaw/.security.yml - Use different keys for different environments (dev, staging, production)
- Rotate keys regularly and update
.security.yml - Backup securely: Encrypt backups containing
.security.yml
API
LoadSecurityConfig
func LoadSecurityConfig(securityPath string) (*SecurityConfig, error)
Loads the security configuration from .security.yml. Returns an empty SecurityConfig if the file doesn't exist.
SaveSecurityConfig
func SaveSecurityConfig(securityPath string, sec *SecurityConfig) error
Saves the security configuration to .security.yml with 0o600 permissions.
ResolveReference
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
func SecurityPath(configPath string) string
Returns the path to .security.yml relative to the config file.
Example: Complete Configuration
config.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
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:
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_listsection 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.ymlexists in the same directory asconfig.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:
model_list:
gpt-5.4:
api_keys:
- "sk-proj-key-1"
- "sk-proj-key-2"
- "sk-proj-key-3"
config.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:
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:
{
"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:
model_list:
gpt-5.4:
api_keys:
- "sk-your-key" # Array with one element
Models - Multiple keys:
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:
web:
brave:
api_keys:
- "BSA-your-key" # Array with one element
Web Tools (Brave/Tavily/Perplexity) - Multiple keys:
web:
brave:
api_keys:
- "BSA-key-1"
- "BSA-key-2"
Web Tool (GLMSearch) - Single key only:
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:
{
"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:
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:
export PICOCLAW_MODEL_LIST_GPT-5.4_API_KEY="sk-from-env"
For channels:
export PICOCLAW_CHANNELS_TELEGRAM_TOKEN="token-from-env"
For web tools:
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.ymlfor 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.jsonis the same regardless of single or multiple keys
Load Balancing/Failover Issues
- Verify all API keys in the
api_keysarray are valid - Check that all keys have the same rate limits and permissions
- Monitor logs to see which keys are being used and failing