Best Practices for Securing API Keys and Credentials in Embabel

Embabel-Agent requires API keys to be injected via environment variables or Spring placeholders, filters sensitive metadata before sending to external MCP servers, and sanitizes logs to prevent credential leakage.

The embabel/embabel-agent framework implements a layered configuration model that strictly separates platform defaults from application secrets. Understanding how to properly inject and filter credentials ensures your AI agents never expose sensitive tokens to external tools or log files. This guide covers the proven patterns for securing API keys and credentials in Embabel production deployments.

Configuration Architecture and Secret Isolation

Embabel-Agent follows a layered configuration model that separates platform properties (managed by the library) from application properties (controlled by the developer). The framework loads these properties in the order defined by Spring Boot: programmatic overrides take precedence over application files, which override platform defaults shipped in agent-platform.properties.

This hierarchy ensures that secrets can be overridden per-deployment without touching the codebase. As documented in the API module’s README at /embabel-agent-api/README.md#L75-L88, the platform namespace uses the prefix embabel.agent.platform.* while application-specific settings—including model providers and API keys—use embabel.agent.*.

Runtime Secret Injection via Environment Variables

Most model-specific configuration classes expose an apiKey field that resolves values through a strict priority chain. The constructor in OpenAiModelsConfig.kt resolves the value in this order:

  1. envApiKey (annotated with @Value("\${OPENAI_API_KEY:#{null}}"))
  2. properties.apiKey (populated from application.yml or application.properties)
  3. Failure if none is present—the framework aborts with a clear error message, preventing accidental runs with missing credentials.

According to the source at /embabel-agent-autoconfigure/models/embabel-agent-openai-autoconfigure/src/main/kotlin/com/embabel/agent/config/models/openai/OpenAiModelsConfig.kt#L61-L66 and lines 33-35, this dual-source approach allows flexibility while maintaining security boundaries.

Configuring OpenAI API Keys Securely

Define the API key in your application.yml using a placeholder that references an environment variable:

embabel:
  agent:
    models:
      provider: openai
      openai:
        apiKey: ${OPENAI_API_KEY}
        model: gpt-4

Then supply the actual secret via your deployment environment:

export OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxx
java -jar my-agent.jar

The OpenAiModelsConfig constructor picks up envApiKey automatically as shown in lines 13-17 of the source file.

Preventing Credential Leakage to External MCP Servers

When Embabel forwards a tool call to a remote MCP server, the entire ToolCallContext could be transmitted. To avoid exposing secrets, the MCP meta-converter filters the context before transmission. If no bean is provided, the default is a pass-through (all entries are sent), which is not recommended for production.

The filtering logic resides in ToolCallContextMcpMetaConverter.kt at /embabel-agent-api/src/main/kotlin/com/embabel/agent/tools/mcp/ToolCallContextMcpMetaConverter.kt#L30-L53.

Implementing a Deny-List Filter

Block known secret keys from crossing the process boundary:

@Configuration
class McpSecurityConfig {

    @Bean
    fun toolCallContextMcpMetaConverter(): ToolCallContextMcpMetaConverter =
        ToolCallContextMcpMetaConverter.denyKeys("apiKey", "authHeader", "secretToken")
}

This implementation (lines 24-30) guarantees that sensitive entries are stripped while safe metadata like tenantId or correlationId passes through.

Implementing an Allow-List Filter

When the set of safe keys is small and well-defined, use an allow-list for explicit control:

@Bean
fun toolCallContextMcpMetaConverter(): ToolCallContextMcpMetaConverter =
    ToolCallContextMcpMetaConverter.allowKeys("tenantId", "correlationId", "locale")

Sanitizing Logs and Error Messages

The framework sanitizes values that may be logged or exposed in error messages. In OpenAiModelsConfig.kt at lines 51-52, the initialization block logs the presence of a key without printing its raw value:

logger.info("OpenAI models are available: {}", properties)

This pattern ensures that even if logging levels are set to DEBUG or TRACE, the actual API key material never appears in log files or stack traces.

Universal Security Pattern Across Model Providers

All secret handling follows the same pattern across providers (Anthropic, Mistral, Gemini, etc.). Each provider’s configuration class in the embabel-agent-autoconfigure/models directory defines an apiKey property with identical env-var fallback logic, making the approach uniform and easy to audit. This consistency means that securing API keys and credentials in Embabel requires the same steps regardless of which LLM provider you integrate.

Summary

  • Inject secrets via environment variables or Spring placeholders like ${OPENAI_API_KEY} rather than hard-coding values in source control.
  • Configure ToolCallContextMcpMetaConverter with allow-lists or deny-lists to prevent credential transmission to external MCP servers, avoiding the insecure default pass-through behavior.
  • Verify logging hygiene by ensuring configuration classes log only the presence of keys, not their values, as implemented in OpenAiModelsConfig.kt lines 51-52.
  • Apply consistent patterns across all model providers (OpenAI, Anthropic, Mistral, Gemini) using the same env-var resolution and property injection strategy.

Frequently Asked Questions

How does Embabel prioritize conflicting API key sources?

According to the source code in OpenAiModelsConfig.kt, the constructor resolves values in a strict hierarchy: first checking the environment variable (envApiKey), then falling back to properties.apiKey from configuration files, and failing explicitly with a clear error if neither is present. This prevents the framework from initializing with empty or default credentials.

What is the default behavior if I don't configure an MCP meta-converter?

Without a custom bean, the framework uses a pass-through converter that transmits all context entries to external servers, which is insecure for production. You must explicitly define a ToolCallContextMcpMetaConverter bean using either allowKeys or denyKeys to guarantee that only vetted metadata leaves your process, as implemented in /embabel-agent-api/src/main/kotlin/com/embabel/agent/tools/mcp/ToolCallContextMcpMetaConverter.kt.

Does Embabel log the actual values of API keys during initialization?

No. The framework intentionally masks sensitive values in logs. For example, OpenAiModelsConfig.kt at lines 51-52 logs only the configuration object’s presence marker, ensuring that raw API key material never appears in application logs regardless of the logging level configured.

Can I use different environment variable names for different model providers?

Yes. While the examples show standard names like OPENAI_API_KEY, each provider's autoconfigure module follows the same pattern of accepting an environment variable fallback. You can define provider-specific variables (e.g., ANTHROPIC_API_KEY, MISTRAL_API_KEY) while maintaining uniform security auditing across the framework, as each config class implements the same envApiKey resolution logic.

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 →