Security Best Practices
Security is critical when working with AI platforms. This guide covers best practices for protecting your Claro API keys, managing access, and maintaining a secure integration.
API Key Management
Creating Secure API Keys
Use Descriptive Names
Name API keys by environment and purpose: ✅ Good:
- production-web-app
- staging-api-tests
- development-local
❌ Bad:
- key1
- test
- mykey
Create Environment-Specific Keys
Never share API keys across environments:
Development - Local testing only
Staging - Pre-production validation
Production - Live applications only
Document Key Purpose
Keep a record of what each key is used for: production-web-app: Main website customer support
staging-api-tests: CI/CD integration tests
development-jane: Jane's local development
Save Keys Immediately
API keys are only shown once during creation. Save them securely immediately. If you lose an API key, you must delete it and create a new one. There is no way to retrieve a lost key.
Storing API Keys Securely
Never commit API keys to version control or share them publicly:
Environment Variables
Cloud Secrets Manager
CI/CD Secrets
Docker
Local Development # .env file (add to .gitignore!)
BAYT_API_KEY = your_api_key_here
OPENAI_API_KEY = your_openai_key_here
Load in your application: import os
from dotenv import load_dotenv
from baytos.claro import BaytClient
load_dotenv()
client = BaytClient( api_key = os.getenv( "BAYT_API_KEY" ))
Always add .env to .gitignore: echo ".env" >> .gitignore
Production Use your cloud provider’s secrets manager: AWS Secrets Manager: import boto3
import json
from baytos.claro import BaytClient
def get_secret ( secret_name ):
client = boto3.client( 'secretsmanager' )
response = client.get_secret_value( SecretId = secret_name)
return json.loads(response[ 'SecretString' ])
secrets = get_secret( "claro/api-keys" )
client = BaytClient( api_key = secrets[ 'BAYT_API_KEY' ])
Google Cloud Secret Manager: from google.cloud import secretmanager
from baytos.claro import BaytClient
def get_secret ( project_id , secret_id ):
client = secretmanager.SecretManagerServiceClient()
name = f "projects/ { project_id } /secrets/ { secret_id } /versions/latest"
response = client.access_secret_version( request = { "name" : name})
return response.payload.data.decode( "UTF-8" )
api_key = get_secret( "your-project" , "bayt-api-key" )
client = BaytClient( api_key = api_key)
Azure Key Vault: from azure.keyvault.secrets import SecretClient
from azure.identity import DefaultAzureCredential
from baytos.claro import BaytClient
credential = DefaultAzureCredential()
client = SecretClient(
vault_url = "https://your-vault.vault.azure.net/" ,
credential = credential
)
api_key = client.get_secret( "bayt-api-key" ).value
claro_client = BaytClient( api_key = api_key)
GitHub Actions: # .github/workflows/test.yml
jobs :
test :
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v3
- name : Run tests
env :
BAYT_API_KEY : ${{ secrets.BAYT_API_KEY }}
run : pytest tests/
Add secrets in: Settings → Secrets and variables → Actions GitLab CI: # .gitlab-ci.yml
test :
script :
- pytest tests/
variables :
BAYT_API_KEY : $BAYT_API_KEY # Set in CI/CD settings
Add secrets in: Settings → CI/CD → Variables Never hardcode keys in Dockerfiles # ❌ BAD - Never do this
ENV BAYT_API_KEY=sk_live_abc123
# ✅ Good - Pass at runtime
# (No key in Dockerfile)
Pass keys at runtime: # Using env file
docker run --env-file .env your-image
# Using docker-compose
# docker-compose.yml
services:
app:
env_file: .env
Key Rotation
Regular key rotation reduces the risk of compromised keys:
Rotation Schedule
Development Every 6 months
Lower risk, less frequent rotation
Staging Every 3 months
Regular rotation for testing
Production Every 90 days
Highest security standard
Safe Rotation Process
Deploy New Key
Update your application with the new key:
Update environment variables
Deploy to staging first
Test thoroughly
Deploy to production
Monitor for Issues
Watch for errors over 24-48 hours:
Check error rates
Monitor authentication failures
Verify all services updated
Delete Old Key
Once confirmed the new key works everywhere, delete the old key
Never delete the old key before deploying the new one. This will break your production application.
Emergency Key Rotation
If you suspect a key is compromised:
Immediately Create New Key
Don’t wait - create a replacement key right away
Quick Deploy
Deploy the new key as quickly as possible, even if it means brief downtime
Delete Compromised Key
Delete the compromised key immediately to prevent unauthorized access
Audit Access
Review API access logs to understand the extent of unauthorized use
Notify Team
Alert your security team and stakeholders
Access Control
Workspace Permissions
Limit who can create and manage API keys:
Permission Levels for API Keys
Role Can Create Keys Can View Keys Can Delete Keys Owner ✓ ✓ ✓ Admin ✓ ✓ ✓ Member ✗ ✗ ✗ Viewer ✗ ✗ ✗
Only Owners and Admins should manage API keys.
Service Accounts
For production systems, use service accounts instead of personal accounts:
Create Service Account
Create a dedicated account for your application (e.g., api-production@company.com)
Add to Workspace
Invite the service account to your workspace with appropriate permissions
Generate API Keys
Create API keys from the service account
Document Ownership
Document which team owns each service account
Service accounts ensure API keys aren’t tied to individual employees who may leave the organization.
Audit Logging
Monitoring API Usage
Track API key usage in the Claro dashboard:
Navigate to API Keys
Go to Settings → API Keys in the Claro dashboard
View Usage Statistics
For each key, review:
Last used timestamp
Total requests
Error rates
Usage patterns
Identify Anomalies
Look for suspicious activity:
Unexpected spikes in usage
Requests from unusual locations
High error rates
Usage of supposedly inactive keys
Automated Monitoring
Set up alerts for suspicious activity:
import requests
import os
from datetime import datetime, timedelta
def check_api_usage ():
"""Monitor API key usage for anomalies"""
# Fetch usage data from Claro API
api_key = os.getenv( "BAYT_API_KEY" )
headers = { "Authorization" : f "Bearer { api_key } " }
response = requests.get(
"https://api.baytos.ai/v1/usage" ,
headers = headers
)
usage = response.json()
# Check for anomalies
if usage[ 'requests_today' ] > usage[ 'avg_requests' ] * 2 :
send_alert(
f "Unusual API usage: { usage[ 'requests_today' ] } requests "
f "(avg: { usage[ 'avg_requests' ] } )"
)
if usage[ 'error_rate' ] > 0.05 : # 5% error rate
send_alert(
f "High error rate: { usage[ 'error_rate' ] * 100 } %"
)
def send_alert ( message ):
"""Send alert via email, Slack, PagerDuty, etc."""
# Your alerting logic
pass
Secrets Management
Environment-Specific Secrets
Never mix secrets across environments:
# ✅ Good: Clear environment separation
ENVIRONMENTS = {
'development' : {
'BAYT_API_KEY' : os.getenv( 'BAYT_API_KEY_DEV' ),
'OPENAI_API_KEY' : os.getenv( 'OPENAI_API_KEY_DEV' ),
},
'staging' : {
'BAYT_API_KEY' : os.getenv( 'BAYT_API_KEY_STAGING' ),
'OPENAI_API_KEY' : os.getenv( 'OPENAI_API_KEY_STAGING' ),
},
'production' : {
'BAYT_API_KEY' : os.getenv( 'BAYT_API_KEY_PROD' ),
'OPENAI_API_KEY' : os.getenv( 'OPENAI_API_KEY_PROD' ),
}
}
env = os.getenv( 'ENVIRONMENT' , 'development' )
config = ENVIRONMENTS [env]
Secrets in Configuration Files
Never commit secrets to git:
# ❌ BAD - config.yaml committed to git
api :
key : sk_live_abc123 # Never do this!
# ✅ Good - config.yaml with placeholder
api :
key : ${BAYT_API_KEY} # Loaded from environment
Load secrets at runtime:
import yaml
import os
def load_config ( config_file ):
"""Load config with environment variable substitution"""
with open (config_file) as f:
config = yaml.safe_load(f)
# Replace ${VAR} with environment variable
for key, value in config.items():
if isinstance (value, str ) and value.startswith( '${' ):
env_var = value[ 2 : - 1 ] # Extract VAR from ${VAR}
config[key] = os.getenv(env_var)
return config
OWASP Considerations
Preventing Common Vulnerabilities
Risk: User input injected into prompts could manipulate LLM behaviorPrevention: def safe_prompt_injection ( user_input : str , prompt_template : str ) -> str :
"""Safely inject user input into prompts"""
# Sanitize user input
sanitized = user_input.strip()
# Escape special characters if needed
sanitized = sanitized.replace( "{" , " {{ " ).replace( "}" , " }} " )
# Use template with clear boundaries
return prompt_template.format( user_input = sanitized)
# Example usage
from baytos.claro import BaytClient
client = BaytClient( api_key = os.getenv( "BAYT_API_KEY" ))
prompt = client.get_prompt( "@workspace/qa-bot:v1" )
# Inject user input safely
full_prompt = safe_prompt_injection(
user_input = "What are your hours?" ,
prompt_template = f " { prompt.generator } \n\n User question: {{ user_input }} "
)
Risk: Sensitive data leaked through prompts or API responsesPrevention:
Never include PII in prompts
Redact sensitive data before logging
Use environment-specific prompts
Implement data classification
import re
def redact_sensitive_data ( text : str ) -> str :
"""Redact PII from logs and prompts"""
# Email addresses
text = re.sub( r ' \b [ A-Za-z0-9._%+- ] + @ [ A-Za-z0-9.- ] + \. [ A-Z|a-z ] {2,} \b ' ,
'[EMAIL_REDACTED]' , text)
# Phone numbers
text = re.sub( r ' \b\d {3} [ -. ] ? \d {3} [ -. ] ? \d {4} \b ' ,
'[PHONE_REDACTED]' , text)
# Credit cards
text = re.sub( r ' \b\d {4} [ - \s ] ? \d {4} [ - \s ] ? \d {4} [ - \s ] ? \d {4} \b ' ,
'[CARD_REDACTED]' , text)
return text
# Use before logging
logger.info( f "User query: { redact_sensitive_data(user_query) } " )
Risk: Unable to detect or investigate security incidentsPrevention: import logging
from datetime import datetime
# Configure security logging
security_logger = logging.getLogger( 'security' )
handler = logging.FileHandler( 'security.log' )
handler.setFormatter(logging.Formatter(
' %(asctime)s - %(name)s - %(levelname)s - %(message)s '
))
security_logger.addHandler(handler)
security_logger.setLevel(logging. INFO )
# Log security events
def log_api_call ( user_id : str , action : str , success : bool ):
security_logger.info({
'timestamp' : datetime.utcnow().isoformat(),
'user_id' : user_id,
'action' : action,
'success' : success,
'ip' : request.remote_addr # If using Flask/Django
})
# Usage
log_api_call(
user_id = "user_123" ,
action = "get_prompt" ,
success = True
)
Risk: Unauthorized access to your application’s AI featuresPrevention:
Always validate users before allowing prompt access:from functools import wraps
from flask import request, jsonify
def require_auth ( f ):
@wraps (f)
def decorated_function ( * args , ** kwargs ):
# Verify user authentication
auth_header = request.headers.get( 'Authorization' )
if not auth_header or not verify_token(auth_header):
return jsonify({ 'error' : 'Unauthorized' }), 401
return f( * args, ** kwargs)
return decorated_function
@app.route ( '/api/ask' )
@require_auth # Always protect AI endpoints
def ask_question ():
# Your prompt logic
pass
Risk: API abuse or DoS attacksPrevention:
Implement rate limiting on your endpoints:from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
limiter = Limiter(
app,
key_func = get_remote_address,
default_limits = [ "100 per hour" ]
)
@app.route ( '/api/ask' )
@limiter.limit ( "10 per minute" ) # Limit AI endpoint usage
def ask_question ():
# Your prompt logic
pass
Compliance
Data Privacy Regulations
General Data Protection Regulation (EU) Key requirements:
Data minimization - collect only necessary data
Right to deletion - allow users to delete their data
Data portability - export user data on request
Consent management - explicit user consent required
Implementation: def handle_gdpr_deletion ( user_id : str ):
"""Delete all user data per GDPR right to deletion"""
# Delete user prompts
# Delete user interactions
# Remove user from workspace memberships
# Clear cached data
pass
def export_user_data ( user_id : str ):
"""Export user data per GDPR data portability"""
# Collect all user data
# Format as JSON or CSV
# Return to user
pass
California Consumer Privacy Act (US) Key requirements:
Disclosure of data collection
Right to opt-out of data sale
Right to deletion
Non-discrimination for exercising rights
Implementation: def handle_ccpa_optout ( user_id : str ):
"""Handle CCPA opt-out request"""
# Mark user as opted-out
# Stop data collection
# Update privacy settings
pass
Health Insurance Portability and Accountability Act (US Healthcare) Key requirements:
Encrypt PHI (Protected Health Information)
Access controls and audit logs
Business Associate Agreements
Breach notification procedures
If you’re handling healthcare data, consult with legal counsel to ensure HIPAA compliance. Claro alone does not make your application HIPAA compliant.
Security Checklist
Incident Response
If an API Key is Compromised
Immediate Actions (0-15 minutes)
Delete compromised key immediately
Create new API key
Deploy new key to production
Alert security team
Investigation (15-60 minutes)
Review audit logs for unauthorized access
Identify what data was accessed
Determine scope of breach
Document timeline of events
Remediation (1-24 hours)
Rotate all related credentials
Update security procedures
Implement additional monitoring
Review and update access controls
Post-Incident (1-7 days)
Conduct post-mortem analysis
Update documentation
Train team on lessons learned
Implement preventive measures
Notify affected parties if required
Next Steps
Error Handling Handle authentication errors gracefully
Testing Test with secure API key management
Performance Optimize API usage to reduce exposure
Workspace Collaboration Manage team access securely