# Best Practices for Securing API Keys and Credentials in Embabel

> Learn the best practices for securing API keys and credentials in Embabel. Discover how Embabel-Agent uses environment variables, filters metadata, and sanitizes logs to protect your sensitive data.

- Repository: [Embabel/embabel-agent](https://github.com/embabel/embabel-agent)
- Tags: best-practices
- Published: 2026-08-09

---

**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`](https://github.com/embabel/embabel-agent/blob/main/OpenAiModelsConfig.kt) resolves the value in this order:

1. `envApiKey` (annotated with `@Value("\${OPENAI_API_KEY:#{null}}")`)
2. `properties.apiKey` (populated from [`application.yml`](https://github.com/embabel/embabel-agent/blob/main/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`](https://github.com/embabel/embabel-agent/blob/main/application.yml) using a placeholder that references an environment variable:

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

```

Then supply the actual secret via your deployment environment:

```bash
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`](https://github.com/embabel/embabel-agent/blob/main/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:

```kotlin
@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:

```kotlin
@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`](https://github.com/embabel/embabel-agent/blob/main/OpenAiModelsConfig.kt) at lines 51-52, the initialization block logs the *presence* of a key without printing its raw value:

```kotlin
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`](https://github.com/embabel/embabel-agent/blob/main/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`](https://github.com/embabel/embabel-agent/blob/main/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`](https://github.com/embabel/embabel-agent/blob/main//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`](https://github.com/embabel/embabel-agent/blob/main/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.