Credential management is foundational to infrastructure security. A single exposed API key can compromise entire systems. With AI agents requiring access to multiple services, the credential surface area expands significantly.
This document describes the credential management framework used for ShoreAgents and StepTen operations, covering inventory, storage architecture, access control, rotation policies, and incident response.
Credential Inventory
Current inventory across all projects:
| Provider | Keys | Purpose | |----------|------|---------| | OpenAI | 2 | GPT-4, DALL-E, embeddings | | Anthropic | 2 | Claude API access | | Google | 3 | Gemini, Imagen, Workspace | | Supabase | 10 | 5 projects × 2 keys each | | Vercel | 3 | Deployment APIs | | GitHub | 2 | Repository access | | Xero | 1 | Accounting API | | ElevenLabs | 1 | Text-to-speech | | Replicate | 1 | Image processing | | Runway | 1 | Video generation | | Resend | 1 | Email sending | | Open Exchange Rates | 1 | Currency data | | Others | 22 | Various services |
Total: 50+ credentials requiring management.
Classification System
Credentials are classified by potential impact of compromise:
Tier 1 — Critical
Credentials providing administrative or cross-system access: - Google Workspace service account (can impersonate any domain user) - Supabase service role keys (bypass all RLS policies) - Cloud platform administrative credentials
Requirements: - Monthly rotation - Hardware key protection where supported - Access limited to essential systems only - Enhanced monitoring
Tier 2 — Important
Credentials providing significant data access: - Database connection strings - Financial API credentials (Xero) - GitHub with write access - Production AI model keys
Requirements: - Quarterly rotation - Encrypted storage - Access logging - Regular access review
Tier 3 — Standard
Credentials with limited scope: - Single-purpose API keys (image generation) - Read-only access tokens - Development environment credentials
Requirements: - Annual rotation - Standard encrypted storage - Basic access logging
Storage Architecture
Central Repository
The authoritative source for all credentials is a Supabase table with restricted access:
`sql
CREATE TABLE api_credentials (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT UNIQUE NOT NULL,
provider TEXT NOT NULL,
credential_value TEXT NOT NULL,
tier INTEGER NOT NULL,
environment TEXT DEFAULT 'production',
rotated_at TIMESTAMPTZ,
rotation_schedule TEXT,
notes TEXT,
projects_using TEXT[],
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Restrict access to service role only
ALTER TABLE api_credentials ENABLE ROW LEVEL SECURITY;
-- No SELECT policies = no access via anon or authenticated roles
`
Access requires the service role key, which is itself a Tier 1 credential.
Local Development
Development machines use local credential files:
`
~/.openclaw/workspace/credentials/
├── api-keys.json
├── google-service-account.json
├── supabase-*.json
└── xero-api.json
`
Security requirements for local storage: - Directory excluded from all version control - Disk encryption enabled (FileVault/BitLocker) - File permissions restricted to owner - Not synced to cloud storage services
Production Environment
Production credentials are stored as environment variables in deployment platforms: - Vercel: Project Settings → Environment Variables - Supabase Edge Functions: Secrets management - GitHub Actions: Repository secrets
Never stored in code or configuration files that might be version controlled.
Access Patterns
Retrieval Interface
`typescript
class CredentialManager {
private cache: MapCredential not found: ${name});
}
private async logAccess(name: string): Promise`
Access Logging
Every credential access is logged:
`sql
CREATE TABLE credential_access_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
credential_name TEXT NOT NULL,
accessor TEXT NOT NULL,
accessed_at TIMESTAMPTZ DEFAULT NOW(),
context TEXT
);
-- Useful queries -- Recent accesses SELECT * FROM credential_access_log WHERE accessed_at > NOW() - INTERVAL '24 hours' ORDER BY accessed_at DESC;
-- Access frequency SELECT credential_name, COUNT(*), MAX(accessed_at) FROM credential_access_log GROUP BY credential_name ORDER BY COUNT(*) DESC;
-- Anomalous timing
SELECT * FROM credential_access_log
WHERE EXTRACT(HOUR FROM accessed_at) NOT BETWEEN 6 AND 23;
`
Rotation Procedures
Scheduled Rotation
`bash
#!/bin/bash
# rotate-credential.sh
CREDENTIAL_NAME=$1 NEW_VALUE=$2
# Update central repository curl -X PATCH "https://$SUPABASE_URL/rest/v1/api_credentials?name=eq.$CREDENTIAL_NAME" \ -H "apikey: $SUPABASE_SERVICE_KEY" \ -H "Authorization: Bearer $SUPABASE_SERVICE_KEY" \ -H "Content-Type: application/json" \ -d "{ \"credential_value\": \"$NEW_VALUE\", \"rotated_at\": \"$(date -Iseconds)\" }"
# Update Vercel projects for project in admin web staff client; do vercel env rm $CREDENTIAL_NAME production -y --cwd apps/$project 2>/dev/null echo $NEW_VALUE | vercel env add $CREDENTIAL_NAME production --cwd apps/$project done
# Trigger redeployments for project in admin web staff client; do vercel deploy --prod --cwd apps/$project done
echo "Rotation complete for $CREDENTIAL_NAME"
`
Rotation Monitoring
Automated check for overdue rotations:
`sql
SELECT
name,
tier,
rotation_schedule,
rotated_at,
CASE rotation_schedule
WHEN 'monthly' THEN rotated_at + INTERVAL '30 days'
WHEN 'quarterly' THEN rotated_at + INTERVAL '90 days'
WHEN 'annual' THEN rotated_at + INTERVAL '365 days'
END AS due_date,
CASE
WHEN rotation_schedule = 'monthly' AND rotated_at + INTERVAL '30 days' < NOW() THEN 'OVERDUE'
WHEN rotation_schedule = 'quarterly' AND rotated_at + INTERVAL '90 days' < NOW() THEN 'OVERDUE'
WHEN rotation_schedule = 'annual' AND rotated_at + INTERVAL '365 days' < NOW() THEN 'OVERDUE'
ELSE 'OK'
END AS status
FROM api_credentials
ORDER BY
CASE WHEN tier = 1 THEN 0 ELSE tier END,
due_date;
`
Weekly notification if any credentials are overdue.
Agent-Specific Considerations
AI agents introduce unique security considerations:
Context Window Exposure
Credentials appearing in agent context could be logged or exposed in debugging output.
Mitigation: Never inject credentials directly into prompts. Use placeholder references that are resolved at execution time.
Tool Use Risks
Agents with HTTP request capabilities could potentially exfiltrate credentials through crafted requests.
Mitigation: Input validation, URL allowlisting, credential injection after prompt processing.
Memory Persistence
Agents should not write credentials to persistent memory files.
Mitigation: Credentials are stored only by reference (credential name, not value) in agent memory systems.
Environment Separation
Development
- Separate credential sets with limited permissions
- Points to development/staging resources
- Can be rotated independently of production
Production
- Full-permission credentials
- Stricter access controls
- Enhanced monitoring
Mapping
`typescript
function getCredentialName(baseName: string): string {
const env = process.env.NODE_ENV || 'development';
if (env === 'production') {
return baseName;
}
return ${baseName}_${env};
}
// Usage
const apiKey = await credentials.get(getCredentialName('openai_api_key'));
// Returns 'openai_api_key' in production
// Returns 'openai_api_key_development' in development
`
Incident Response
Suspected Compromise
- 1.Immediate revocation — Don't investigate first; revoke the potentially compromised credential
- 2.Generate replacement — Create new credential from provider
- 3.Update all references — Central repository, environment variables, local files
- 4.Deploy updates — Ensure all systems use new credential
- 5.Audit access logs — Determine what was accessed with old credential
- 6.Investigate source — How did exposure occur?
- 7.Document incident — Record timeline, impact, remediation, prevention measures
Recovery Checklist
`markdown
## Credential Compromise Response
- [ ] Revoked compromised credential at provider
- [ ] Generated new credential
- [ ] Updated central repository
- [ ] Updated Vercel environment variables
- [ ] Updated local development files
- [ ] Triggered production redeployments
- [ ] Verified services functioning with new credential
- [ ] Reviewed access logs for unauthorized use
- [ ] Identified exposure vector
- [ ] Implemented prevention measures
- [ ] Created incident report
`
Naming Conventions
Consistent naming prevents confusion:
`
{provider}_{purpose}_{qualifier}
`
Examples:
- openai_api_key
- supabase_service_key_stepten_army
- google_service_account_workspace
- stripe_secret_key_production
Avoid:
- api_key — which API?
- prod_key — key for what?
- openai — production or test?
FAQ
Why not use a dedicated secrets manager like HashiCorp Vault?
For the current operational scale, Supabase with RLS provides adequate security with lower operational complexity. Vault adds infrastructure that requires its own maintenance, monitoring, and expertise. The approach scales to hundreds of credentials; at thousands, dedicated secrets management becomes more attractive.
How do you handle credentials in CI/CD?
GitHub Actions uses repository secrets. Vercel uses project environment variables. Credentials are never echoed in build logs—build scripts use secret masking.
What if a developer leaves the team?
All credentials they had access to are rotated. Their access to the central repository is revoked. Local credential files on their machines are assumed compromised and treated accordingly.
How do you test without exposing production credentials?
Test environments use separate credential sets pointing to sandbox/development resources. Production credentials never exist in development environments.
What about the Supabase service key that protects the credential table?
It's the most sensitive credential—Tier 1, monthly rotation, stored only in production environment variables and a secure backup location. Access is limited to systems that genuinely require administrative database access.
Security is cumulative. Each control adds to overall protection. The goal is defense in depth—no single point of failure can compromise the system.
