Skip to main content

AI Agent Integration

secretctl provides first-class support for AI coding assistants through the Model Context Protocol (MCP). This guide covers integration with Claude Code and other MCP-compatible tools.

Overview

The Challenge

AI coding assistants need access to secrets for tasks like:

  • Running tests that require API keys
  • Deploying to cloud services
  • Accessing databases
  • Authenticating with external services

The Problem with Traditional Approaches

Exposing raw secrets to AI agents creates risks:

  • Non-deterministic behavior - LLMs may inadvertently expose secrets
  • Prompt injection - Malicious prompts could extract secrets
  • Logging exposure - Secrets may appear in conversation logs
  • No revocation - Hard to invalidate exposed credentials

secretctl's Solution: AI-Safe Access

secretctl follows the "Access Without Exposure" principle:

FeatureTraditionalsecretctl MCP
Raw secret accessYesNo
Command executionManualAutomated
Output sanitizationNoYes
Audit loggingNoYes
Policy controlNoYes

The AI agent can use secrets without seeing them.

Claude Code Setup

1. Configure MCP Server

Add secretctl to your Claude Code configuration:

// ~/.claude.json
{
"mcpServers": {
"secretctl": {
"command": "/path/to/secretctl",
"args": ["mcp-server"],
"env": {
"SECRETCTL_PASSWORD": "your-master-password"
}
}
}
}

2. Create MCP Policy

Configure allowed operations:

# ~/.secretctl/mcp-policy.yaml
version: 1
default_action: deny

# Commands the AI can execute with secrets
allowed_commands:
- npm
- node
- python
- go
- cargo
- aws
- gcloud
- kubectl
- curl
- ./deploy.sh
- ./test.sh

# Commands never allowed
denied_commands:
- rm
- sudo
- chmod
- chown

# Environment aliases
env_aliases:
dev:
- pattern: "db/*"
target: "dev/db/*"
prod:
- pattern: "db/*"
target: "prod/db/*"

3. Set File Permissions

# Policy file must have secure permissions
chmod 600 ~/.secretctl/mcp-policy.yaml

4. Verify Setup

Restart Claude Code and verify the MCP server is connected.

Available MCP Tools

secret_list

List all secret keys (values not exposed):

Claude: "What secrets do I have available?"
→ Calls secret_list
→ Returns: ["API_KEY", "DATABASE_URL", "AWS_ACCESS_KEY"]

secret_exists

Check if a specific secret exists:

Claude: "Do I have AWS credentials configured?"
→ Calls secret_exists("aws/access_key")
→ Returns: true/false

secret_get_masked

Get metadata and masked value:

Claude: "Show me info about the API key"
→ Calls secret_get_masked("API_KEY")
→ Returns: {
key: "API_KEY",
maskedValue: "****xyz",
tags: ["api", "prod"],
notes: "OpenAI API key"
}

secret_run

Execute commands with secrets injected:

Claude: "Run the tests with the API key"
→ Calls secret_run({
keys: ["API_KEY"],
command: "npm test"
})
→ Executes with API_KEY in environment
→ Output sanitized (secrets redacted)

Practical Scenarios

Running Tests

You: "Run the integration tests"

Claude:
1. Calls secret_list → finds TEST_API_KEY, TEST_DATABASE_URL
2. Calls secret_run({
keys: ["TEST_API_KEY", "TEST_DATABASE_URL"],
command: "npm run test:integration"
})
3. Returns sanitized test output

Deploying to AWS

You: "Deploy to production"

Claude:
1. Calls secret_exists("aws/*") → confirms AWS credentials exist
2. Calls secret_run({
keys: ["aws/*"],
command: "aws s3 sync ./dist s3://my-bucket"
})
3. Returns deployment result (credentials never visible)

Database Queries

You: "Check the user count in the database"

Claude:
1. Calls secret_run({
keys: ["DATABASE_URL"],
command: "psql $DATABASE_URL -c 'SELECT COUNT(*) FROM users'"
})
2. Returns query result

API Calls

You: "Fetch my GitHub repos"

Claude:
1. Calls secret_run({
keys: ["GITHUB_TOKEN"],
command: "curl -H 'Authorization: Bearer $GITHUB_TOKEN' https://api.github.com/user/repos"
})
2. Returns repo list (token never exposed)

Security Model

What AI Agents CAN Do

  • ✅ List available secret keys
  • ✅ Check if secrets exist
  • ✅ See masked values (last 4 characters)
  • ✅ Read metadata (tags, notes, URLs)
  • ✅ Execute allowed commands with secrets
  • ✅ Receive sanitized output

What AI Agents CANNOT Do

  • ❌ Read plaintext secret values
  • ❌ Execute denied commands
  • ❌ Bypass output sanitization
  • ❌ Access secrets outside policy
  • ❌ Modify or delete secrets

Output Sanitization

All command output is scanned for secret values:

# If API_KEY = "sk-abc123xyz"
# Original output: "Connected with key sk-abc123xyz"
# Sanitized output: "Connected with key [REDACTED:API_KEY]"

Audit Logging

All MCP operations are logged:

{
"timestamp": "2025-01-15T10:30:00Z",
"action": "secret.run",
"source": "mcp",
"keys": ["API_KEY"],
"command": "npm test",
"success": true
}

Environment Aliases

Use environment aliases for context-aware secret mapping:

Configuration

# ~/.secretctl/mcp-policy.yaml
env_aliases:
development:
- pattern: "db/*"
target: "dev/db/*"
- pattern: "api/*"
target: "dev/api/*"
staging:
- pattern: "db/*"
target: "staging/db/*"
production:
- pattern: "db/*"
target: "prod/db/*"

Usage

You: "Run tests against staging"

Claude:
→ Calls secret_run({
keys: ["db/*"],
command: "npm test",
env: "staging"
})
→ Uses staging/db/* secrets automatically

Policy Best Practices

Principle of Least Privilege

Only allow commands the AI actually needs:

# Good: Specific allowed commands
allowed_commands:
- npm test
- npm run build
- ./deploy.sh

# Bad: Too permissive
allowed_commands:
- "*"

Block Dangerous Commands

Always deny potentially dangerous operations:

denied_commands:
- rm
- sudo
- chmod
- chown
- mv
- dd
- mkfs

Environment Separation

Use separate secrets for different environments:

env_aliases:
dev:
- pattern: "*"
target: "dev/*"
prod:
- pattern: "*"
target: "prod/*"

Regular Audit Review

Monitor AI agent activity:

# Review MCP access
secretctl audit export | jq '.[] | select(.source == "mcp")'

# Check for failures
secretctl audit export | jq '.[] | select(.success == false)'

Troubleshooting

MCP Server Not Connecting

  1. Verify secretctl path in configuration
  2. Check SECRETCTL_PASSWORD is set
  3. Ensure vault exists and is accessible
  4. Check Claude Code logs for errors

"Command not allowed" Error

Add the command to allowed_commands in policy:

allowed_commands:
- your-command

"Secret not found" Error

  1. Verify secret exists: secretctl list
  2. Check key spelling (case-sensitive)
  3. Verify pattern matches: secretctl list | grep pattern

Output Not Sanitized

Sanitization only works for exact matches. If secrets appear:

  1. Check the secret value is stored correctly
  2. Verify the output format matches the stored value

Permission Denied

# Check policy file permissions
ls -la ~/.secretctl/mcp-policy.yaml
# Should be: -rw------- (600)

chmod 600 ~/.secretctl/mcp-policy.yaml

Comparison with Alternatives

vs. Hardcoded Secrets

AspectHardcodedsecretctl MCP
Security❌ Exposed in code✅ Never exposed
Audit❌ None✅ Full audit trail
Rotation❌ Code changes✅ Update vault only

vs. Environment Variables

AspectEnv Varssecretctl MCP
AI visibility❌ Visible✅ Hidden
Output safety❌ Can leak✅ Sanitized
Policy control❌ None✅ Full control

vs. Other Secret Managers

AspectOtherssecretctl MCP
AI-native❌ Bolted on✅ Built-in
Local-first❌ Cloud dependent✅ Offline capable
Zero exposure❌ API returns secrets✅ Run-only model

Next Steps