Files
picoclaw/pkg/config/SECURITY_CONFIG.md
T
2026-03-24 20:23:29 +08:00

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:

  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:

{
  "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.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:

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

  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

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_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:

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.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