How to Handle Sensitive Data and Secrets in Claude Plugins

Claude plugins provide a built-in secret store that injects credentials via environment variables, allowing you to reference sensitive data with CLAUDE_SECRET_<NAME> without hardcoding values in your source code.

The anthropics/claude-plugins-community repository implements a secure-by-default pattern for managing API keys, passwords, and tokens. By declaring secrets in your plugin manifest and accessing them through runtime-injected environment variables, you ensure sensitive data never appears in logs or source control.

Declare Secrets in the Plugin Manifest

Every Claude plugin defines its interface through a manifest file—typically SKILL.md or .claude-plugin/plugin.json. To mark a field as sensitive, include the secret: true flag or use naming conventions that include secret, private, signing, or passphrase.

In tres-finance-plugin/skills/tres-wallets-upload/SKILL.md, the documentation explicitly instructs users to protect credentials:

"Please provide these values. Keep API secrets private — do not share them in public channels."

This instruction appears at line 347, demonstrating the repository's standard for user-facing secret documentation【SKILL.md line 347】.

Example manifest declaration:

fields:
  - name: api_key
    type: string
    description: "Your API key for Example Service"
    secret: true

When users configure your plugin through Claude's UI, fields marked as secrets render as masked inputs, preventing shoulder-surfing and clipboard leaks.

Access Secrets at Runtime via Environment Variables

The Claude runtime transforms declared secrets into environment variables using the pattern CLAUDE_SECRET_<NAME> in uppercase with underscores. Your plugin code must read these variables rather than containing literal values.

Python implementation:

import os

def get_api_key():
    """Retrieve API key from injected environment variable."""
    api_key = os.getenv("CLAUDE_SECRET_API_KEY")
    if not api_key:
        raise RuntimeError("API key not configured in Claude Secrets")
    return api_key

def call_external_service():
    headers = {"Authorization": f"Bearer {get_api_key()}"}
    # ... perform authenticated request

JavaScript/Node.js implementation:

function getApiKey() {
  const apiKey = process.env.CLAUDE_SECRET_API_KEY;
  if (!apiKey) {
    throw new Error("Missing CLAUDE_SECRET_API_KEY environment variable");
  }
  return apiKey;
}

async function fetchData() {
  const response = await fetch("https://api.example.com/data", {
    headers: { Authorization: `Bearer ${getApiKey()}` },
  });
  return response.json();
}

Never cache secret values in global variables or write them to temporary files. Access os.getenv() or process.env immediately before use to minimize exposure.

Prevent Exfiltration with CI Scanning

The repository enforces secret safety through automated policy scanning. The .github/actions/scan-plugins/policy/prompt.md file contains detection rules that flag any code attempting to exfiltrate live secrets from system keystores:

"Flag credential / secret EXFILTRATION specifically. This is distinct from hardcoded secrets — look for code that reads the user's live secrets from a … secret-tool lookup, Windows cmdkey, keytar/keyring), ~/.aws/credentials"

This policy appears at lines 22-23 and blocks pull requests that attempt to access external credential stores【policy prompt line 22-23】.

The scanning action also validates that secrets declared in manifests are not hardcoded in source files. As shown in .github/actions/scan-plugins/README.md at line 62, the CI pipeline injects the ANTHROPIC_API_KEY secret for testing purposes, demonstrating the canonical injection pattern while verifying no plaintext credentials exist in the codebase【scan-plugins README line 62】.

Encrypt Data at Rest (Optional)

For plugins that persist sensitive configuration between sessions, store encrypted .secenv files using keys maintained in Claude's secret store. The encryption key itself should be declared as a secret in .claude-plugin/plugin.json, ensuring that:

  1. At-rest data remains encrypted when the plugin is not running
  2. Decryption only occurs when the runtime injects the key via CLAUDE_SECRET_ENCRYPTION_KEY
  3. No plaintext keys reside in the file system

Summary

  • Declare secrets explicitly in SKILL.md or .claude-plugin/plugin.json using the secret: true flag or sensitive naming conventions.
  • Access via environment variables using the CLAUDE_SECRET_<NAME> pattern in uppercase; never hardcode credentials in Python, JavaScript, or configuration files.
  • Validate through CI by ensuring the scan-plugins GitHub Action passes; this blocks exfiltration attempts and hardcoded secrets.
  • Log safely by never printing secret values to stdout, stderr, or error messages—Claude's runtime masks these values automatically, but your code must avoid manual logging of credential variables.

Frequently Asked Questions

How do I declare multiple secrets for one plugin?

Declare each sensitive field as a separate entry in the fields array of your .claude-plugin/plugin.json or SKILL.md manifest. Each field with secret: true becomes a distinct environment variable prefixed with CLAUDE_SECRET_. For example, fields: [{name: "stripe_key", secret: true}, {name: "webhook_secret", secret: true}] injects CLAUDE_SECRET_STRIPE_KEY and CLAUDE_SECRET_WEBHOOK_SECRET.

What happens if a secret is not configured?

If a user has not provided a value for a declared secret, the corresponding CLAUDE_SECRET_<NAME> environment variable will be unset or empty. Your code should validate this with os.getenv() or process.env checks and raise clear runtime errors indicating which credential is missing, as shown in the Python and JavaScript examples above.

Can I rotate secrets without redeploying the plugin?

Yes. Because secrets are stored in Claude's built-in secret store and injected at runtime, updating a secret value in the Claude UI immediately affects subsequent plugin invocations. You do not need to modify source code or restart the plugin server—the new value propagates through the environment variable on the next execution.

How does the CI scanner distinguish between hardcoded secrets and legitimate secret access?

The policy in .github/actions/scan-plugins/policy/prompt.md specifically looks for exfiltration patterns—code that reads from system keystores like secret-tool, Windows Credential Manager, or ~/.aws/credentials. Legitimate plugins only access CLAUDE_SECRET_* environment variables injected by the runtime. Hardcoded literals matching high-entropy patterns (API key formats) are flagged separately by the same scanning action.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →