Skip to main content

Overview

Manage secrets securely in Conductor workflows using Cloudflare Workers secrets, environment variables, and best practices for handling sensitive data.

Quick Start

# Add secret via wrangler
echo "sk-..." | npx wrangler secret put OPENAI_API_KEY

# Use in workflow
config:
  headers:
    Authorization: "Bearer ${env.OPENAI_API_KEY}"

Cloudflare Workers Secrets

Adding Secrets

# Interactive
npx wrangler secret put SECRET_NAME

# From file
cat secret.txt | npx wrangler secret put SECRET_NAME

# From environment variable
echo $MY_SECRET | npx wrangler secret put SECRET_NAME

# For specific environment
echo "value" | npx wrangler secret put SECRET_NAME --env production

Listing Secrets

# List all secrets
npx wrangler secret list

# For specific environment
npx wrangler secret list --env production

Deleting Secrets

npx wrangler secret delete SECRET_NAME
npx wrangler secret delete SECRET_NAME --env production

Environment-Specific Secrets

Development

# .dev.vars (local development only - DO NOT COMMIT)
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
DATABASE_URL=postgres://localhost/dev
# .gitignore
.dev.vars
.env
*.key
*.pem

Staging

echo "sk-staging-..." | npx wrangler secret put OPENAI_API_KEY --env staging

Production

echo "sk-prod-..." | npx wrangler secret put OPENAI_API_KEY --env production

Using Secrets in Workflows

AI Provider Keys

flow:
  - member: analyze
    type: Think
    config:
      provider: openai
      apiKey: ${env.OPENAI_API_KEY}  # From secrets
      model: gpt-4o

API Authentication

- member: fetch-data
  type: Fetch
  config:
    url: "https://api.example.com/data"
    headers:
      Authorization: "Bearer ${env.API_KEY}"
      X-API-Secret: "${env.API_SECRET}"

Database Credentials

- member: query-db
  type: Data
  config:
    storage: external
    connectionString: ${env.DATABASE_URL}

Secret Rotation

Zero-Downtime Rotation

# 1. Add new secret with different name
echo "new-key" | npx wrangler secret put API_KEY_NEW

# 2. Update code to try both keys
# 3. Deploy
# 4. Delete old secret
npx wrangler secret delete API_KEY_OLD

Automated Rotation

name: rotate-secrets
description: Automatic secret rotation

flow:
  # Generate new key
  - member: generate-new-key
    type: Function

  # Update in secrets manager
  - member: update-secret
    type: API
    config:
      url: "${env.SECRETS_API}"
      method: PUT
      headers:
        Authorization: "Bearer ${env.ADMIN_TOKEN}"
    input:
      body:
        key: "API_KEY"
        value: ${generate-new-key.output.key}

  # Test new key
  - member: test-new-key
    type: Fetch
    config:
      url: "https://api.example.com/test"
      headers:
        Authorization: "Bearer ${generate-new-key.output.key}"

  # Rollback if test fails
  - member: rollback
    condition: ${!test-new-key.success}
    type: Function

Security Best Practices

1. Never Commit Secrets

# ❌ NEVER do this
const API_KEY = "sk-1234567890";  // Hardcoded!

# ✅ Always use environment variables
const API_KEY = env.API_KEY;

2. Use Secrets, Not Vars

# wrangler.toml

# ❌ Don't store secrets in vars
[vars]
API_KEY = "sk-123..."  # Visible in config!

# ✅ Use secrets instead
# Set via: wrangler secret put API_KEY

3. Principle of Least Privilege

# ✅ Only request what you need
- member: read-data
  config:
    apiKey: ${env.READ_ONLY_KEY}  # Not admin key

# ❌ Don't use admin keys for everything
- member: read-data
  config:
    apiKey: ${env.ADMIN_KEY}  # Too much access!

4. Audit Secret Access

- member: log-secret-use
  type: Data
  config:
    storage: d1
    operation: query
    query: |
      INSERT INTO secret_access_log
      (secret_name, accessed_by, timestamp)
      VALUES (?, ?, CURRENT_TIMESTAMP)
  input:
    params:
      - "API_KEY"
      - ${execution.id}

Secrets in CI/CD

GitHub Actions

# .github/workflows/deploy.yml
jobs:
  deploy:
    steps:
      - name: Deploy with secrets
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        run: |
          echo $OPENAI_API_KEY | npx wrangler secret put OPENAI_API_KEY
          npx wrangler deploy

GitLab CI

# .gitlab-ci.yml
deploy:
  script:
    - echo $OPENAI_API_KEY | npx wrangler secret put OPENAI_API_KEY
    - npx wrangler deploy
  variables:
    CLOUDFLARE_API_TOKEN: $CI_CLOUDFLARE_API_TOKEN

Encryption at Rest

Encrypt Sensitive Data

// Encrypt before storing
async function encryptData(data: string, key: CryptoKey): Promise<string> {
  const encoded = new TextEncoder().encode(data);
  const encrypted = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv: crypto.getRandomValues(new Uint8Array(12)) },
    key,
    encoded
  );
  return btoa(String.fromCharCode(...new Uint8Array(encrypted)));
}

// Decrypt when retrieving
async function decryptData(encrypted: string, key: CryptoKey): Promise<string> {
  const decoded = Uint8Array.from(atob(encrypted), c => c.charCodeAt(0));
  const decrypted = await crypto.subtle.decrypt(
    { name: 'AES-GCM', iv: decoded.slice(0, 12) },
    key,
    decoded.slice(12)
  );
  return new TextDecoder().decode(decrypted);
}

Multi-Tenant Secrets

Per-Tenant Keys

flow:
  - member: get-tenant-key
    type: Data
    config:
      storage: kv
      operation: get
      binding: TENANT_KEYS
    input:
      key: "tenant:${input.tenantId}:api_key"

  - member: use-tenant-key
    type: Fetch
    config:
      url: "https://api.example.com/data"
      headers:
        Authorization: "Bearer ${get-tenant-key.output.value}"

Key Isolation

// Separate KV namespace per tenant
const tenantKV = env[`TENANT_${tenantId}_KV`];
const apiKey = await tenantKV.get('API_KEY');

Secrets Validation

Check Secret Format

- member: validate-key
  type: Function
  input:
    key: ${env.API_KEY}
    pattern: "^sk-[a-zA-Z0-9]{48}$"

Test Secret Validity

- member: test-api-key
  type: Fetch
  config:
    url: "https://api.example.com/validate"
    headers:
      Authorization: "Bearer ${env.API_KEY}"

Emergency Procedures

Leaked Secret Response

# 1. Immediately revoke the key
npx wrangler secret delete COMPROMISED_KEY

# 2. Generate new key at provider
# 3. Add new key
echo "new-key" | npx wrangler secret put API_KEY

# 4. Deploy immediately
npx wrangler deploy

# 5. Audit usage
# Check logs for unauthorized access

# 6. Rotate all related secrets

Automated Leak Detection

name: detect-leaks
description: Scan for leaked secrets

flow:
  - member: scan-commits
    type: Function
    input:
      repository: ${input.repo}
      pattern: "sk-[a-zA-Z0-9]{48}"

  - member: alert-if-found
    condition: ${scan-commits.output.found}
    type: API
    config:
      url: "${env.ALERT_WEBHOOK}"
      method: POST
    input:
      body:
        alert: "Potential secret leak detected"
        details: ${scan-commits.output.matches}

Monitoring Secret Usage

Track API Calls

- member: log-api-usage
  type: Data
  config:
    storage: d1
    operation: query
    query: |
      INSERT INTO api_usage
      (provider, model, tokens, cost, timestamp)
      VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)
  input:
    params:
      - "openai"
      - "gpt-4o"
      - ${think-member.output.usage.total_tokens}
      - ${calculate-cost(think-member.output.usage)}

Alert on Unusual Usage

- member: check-usage
  type: Function
  input:
    current: ${api-usage.output.cost}
    threshold: 100

- member: alert-high-usage
  condition: ${check-usage.output.cost > check-usage.output.threshold}
  type: API

Testing with Secrets

Mock Secrets in Tests

import { describe, it } from 'vitest';
import { TestConductor } from '@ensemble-edge/conductor/testing';

describe('workflows with secrets', () => {
  it('should use secrets', async () => {
    const conductor = await TestConductor.create({
      env: {
        API_KEY: 'test-key-123',
        OPENAI_API_KEY: 'sk-test-...'
      }
    });

    const result = await conductor.executeEnsemble('my-workflow', {});

    expect(result).toBeSuccessful();
  });
});

Best Practices

  1. Never commit secrets - Use .gitignore
  2. Use Workers Secrets - Not environment vars in wrangler.toml
  3. Rotate regularly - Every 90 days minimum
  4. Least privilege - Minimal permissions per secret
  5. Audit access - Log when secrets are used
  6. Test rotation - Ensure zero-downtime rotation works
  7. Monitor usage - Track API calls and costs
  8. Encrypt at rest - For additional sensitive data
  9. Separate by environment - Different keys for dev/staging/prod
  10. Have incident plan - Know how to respond to leaks