Files
gh-glittercowboy-taches-cc-…/skills/create-agent-skills/references/api-security.md
2025-11-29 18:28:37 +08:00

6.0 KiB

When building skills that make API calls requiring credentials (API keys, tokens, secrets), follow this protocol to prevent credentials from appearing in chat.

<the_problem> Raw curl commands with environment variables expose credentials:

# ❌ BAD - API key visible in chat
curl -H "Authorization: Bearer $API_KEY" https://api.example.com/data

When Claude executes this, the full command with expanded $API_KEY appears in the conversation. </the_problem>

<the_solution> Use ~/.claude/scripts/secure-api.sh - a wrapper that loads credentials internally.

<for_supported_services>

# ✅ GOOD - No credentials visible
~/.claude/scripts/secure-api.sh <service> <operation> [args]

# Examples:
~/.claude/scripts/secure-api.sh facebook list-campaigns
~/.claude/scripts/secure-api.sh ghl search-contact "email@example.com"

</for_supported_services>

<adding_new_services> When building a new skill that requires API calls:

  1. Add operations to the wrapper (~/.claude/scripts/secure-api.sh):
case "$SERVICE" in
    yourservice)
        case "$OPERATION" in
            list-items)
                curl -s -G \
                    -H "Authorization: Bearer $YOUR_API_KEY" \
                    "https://api.yourservice.com/items"
                ;;
            get-item)
                ITEM_ID=$1
                curl -s -G \
                    -H "Authorization: Bearer $YOUR_API_KEY" \
                    "https://api.yourservice.com/items/$ITEM_ID"
                ;;
            *)
                echo "Unknown operation: $OPERATION" >&2
                exit 1
                ;;
        esac
        ;;
esac
  1. Add profile support to the wrapper (if service needs multiple accounts):
# In secure-api.sh, add to profile remapping section:
yourservice)
    SERVICE_UPPER="YOURSERVICE"
    YOURSERVICE_API_KEY=$(eval echo \$${SERVICE_UPPER}_${PROFILE_UPPER}_API_KEY)
    YOURSERVICE_ACCOUNT_ID=$(eval echo \$${SERVICE_UPPER}_${PROFILE_UPPER}_ACCOUNT_ID)
    ;;
  1. Add credential placeholders to ~/.claude/.env using profile naming:
# Check if entries already exist
grep -q "YOURSERVICE_MAIN_API_KEY=" ~/.claude/.env 2>/dev/null || \
  echo -e "\n# Your Service - Main profile\nYOURSERVICE_MAIN_API_KEY=\nYOURSERVICE_MAIN_ACCOUNT_ID=" >> ~/.claude/.env

echo "Added credential placeholders to ~/.claude/.env - user needs to fill them in"
  1. Document profile workflow in your SKILL.md:
## Profile Selection Workflow

**CRITICAL:** Always use profile selection to prevent using wrong account credentials.

### When user requests YourService operation:

1. **Check for saved profile:**
   ```bash
   ~/.claude/scripts/profile-state get yourservice
  1. If no profile saved, discover available profiles:

    ~/.claude/scripts/list-profiles yourservice
    
  2. If only ONE profile: Use it automatically and announce:

    "Using YourService profile 'main' to list items..."
    
  3. If MULTIPLE profiles: Ask user which one:

    "Which YourService profile: main, clienta, or clientb?"
    
  4. Save user's selection:

    ~/.claude/scripts/profile-state set yourservice <selected_profile>
    
  5. Always announce which profile before calling API:

    "Using YourService profile 'main' to list items..."
    
  6. Make API call with profile:

    ~/.claude/scripts/secure-api.sh yourservice:<profile> list-items
    

Secure API Calls

All API calls use profile syntax:

~/.claude/scripts/secure-api.sh yourservice:<profile> <operation> [args]

# Examples:
~/.claude/scripts/secure-api.sh yourservice:main list-items
~/.claude/scripts/secure-api.sh yourservice:main get-item <ITEM_ID>

Profile persists for session: Once selected, use same profile for subsequent operations unless user explicitly changes it.

</adding_new_services>
</the_solution>

<pattern_guidelines>
<simple_get_requests>
```bash
curl -s -G \
    -H "Authorization: Bearer $API_KEY" \
    "https://api.example.com/endpoint"

</simple_get_requests>

<post_with_json_body>

ITEM_ID=$1
curl -s -X POST \
    -H "Authorization: Bearer $API_KEY" \
    -H "Content-Type: application/json" \
    -d @- \
    "https://api.example.com/items/$ITEM_ID"

Usage:

echo '{"name":"value"}' | ~/.claude/scripts/secure-api.sh service create-item

</post_with_json_body>

<post_with_form_data>

curl -s -X POST \
    -F "field1=value1" \
    -F "field2=value2" \
    -F "access_token=$API_TOKEN" \
    "https://api.example.com/endpoint"

</post_with_form_data> </pattern_guidelines>

<credential_storage> Location: ~/.claude/.env (global for all skills, accessible from any directory)

Format:

# Service credentials
SERVICE_API_KEY=your-key-here
SERVICE_ACCOUNT_ID=account-id-here

# Another service
OTHER_API_TOKEN=token-here
OTHER_BASE_URL=https://api.other.com

Loading in script:

set -a
source ~/.claude/.env 2>/dev/null || { echo "Error: ~/.claude/.env not found" >&2; exit 1; }
set +a

</credential_storage>

<best_practices>

  1. Never use raw curl with $VARIABLE in skill examples - always use the wrapper
  2. Add all operations to the wrapper - don't make users figure out curl syntax
  3. Auto-create credential placeholders - add empty fields to ~/.claude/.env immediately when creating the skill
  4. Keep credentials in ~/.claude/.env - one central location, works everywhere
  5. Document each operation - show examples in SKILL.md
  6. Handle errors gracefully - check for missing env vars, show helpful error messages </best_practices>
Test the wrapper without exposing credentials:
# This command appears in chat
~/.claude/scripts/secure-api.sh facebook list-campaigns

# But API keys never appear - they're loaded inside the script

Verify credentials are loaded:

# Check .env exists
ls -la ~/.claude/.env

# Check specific variables (without showing values)
grep -q "YOUR_API_KEY=" ~/.claude/.env && echo "API key configured" || echo "API key missing"