Managing Secrets
This page covers where Solace Agent Mesh reads secret material from, the substitution syntax that wires those values into YAML without embedding them in the file, the secret-bearing fields you track per deployment, and how to rotate each one.
Where Agent Mesh Reads Secrets From
Every runtime process reads its secrets from the process environment. On Kubernetes, that environment is populated from a Secret; in a shell, it comes from exported variables. A few fields can instead read a secret from a file mounted at a path, which suits Secret volumes and Vault agent sidecars.
Never embed a secret value directly in YAML or commit one to version control. Keep the secret in the environment or a mounted file, and reference it from YAML with ${VAR} substitution.
| Source | Mechanism | Best for |
|---|---|---|
| Process environment | Variables exported into the process, or a Kubernetes Secret projected onto the pod as environment variables |
Every deployment |
| File-mounted secret | A *_file field on the model: or artifact_service: block points at a mounted path |
Kubernetes Secret volumes, Vault agent sidecars |
.env file (sam CLI only) |
The sam CLI loads the nearest .env into the environment before startup |
Local development |
The .env file is a convenience of the sam CLI. When you run Agent Mesh locally, the CLI walks up from the working directory to the nearest .env and loads it into the environment before it starts the runtime. The standalone server processes do not read .env; they use whatever environment the orchestrator gives them.
The .env loader overrides variables already set in the environment, so a value in .env wins over an exported shell variable of the same name. To skip .env loading and use only the process environment, start the CLI with the --system-env flag.
Substitution Syntax
The configuration loader expands ${VAR} placeholders against the process environment before it parses the YAML, so an expanded value is typed by the parser exactly as if you had written it inline. Four forms are recognized:
| Form | Behavior |
|---|---|
${VAR}
|
The value if VAR is set, even when empty; otherwise the empty string. |
${VAR, default}
|
The value if VAR is set, even when empty; otherwise the expanded default. |
${VAR:-default}
|
The value if VAR is set and non-empty; otherwise the expanded default. |
${VAR:+alt}
|
The expanded alt if VAR is set and non-empty; otherwise the empty string. |
A variable that is set but empty satisfies ${VAR} and ${VAR, default} (both yield the empty value) but not ${VAR:-default}, which falls through to the default. Whitespace is allowed around the comma in ${VAR, default} but not around :- or :+.
Defaults and alts are themselves expanded, so a chained default works to a depth of one: ${A, ${B, fallback}} resolves, but the innermost default in a triple nesting is not substituted.
A typical broker block:
# agent runtime config
broker:
broker_url: "${SOLACE_BROKER_URL}"
broker_username: "${SOLACE_BROKER_USERNAME}"
broker_password: "${SOLACE_BROKER_PASSWORD}"
broker_vpn: "${SOLACE_BROKER_VPN, default}"
An unset ${VAR} does not fail when the configuration is loaded. It substitutes the empty string and lets the downstream consumer reject it (for example, the OAuth2 client-credentials consumer fails with client_secret is required). When you want a hard failure at boot, set the variable to a known-bad sentinel rather than leaving it unset, and let the consumer reject the sentinel.
File-Mounted Secrets
Two configuration blocks can read a secret from a file instead of an inline value. This is the recommended pattern on Kubernetes: mount a Secret volume at a fixed path and point the field at that path.
The model: block reads any field named <key>_file from disk and uses the file contents as <key>. For example, api_key_file supplies api_key:
# agent runtime config model: model: anthropic/claude-sonnet-4-5 api_key_file: /etc/secrets/anthropic-api-key
The fields most often mounted this way:
| Field | Supplies | Block |
|---|---|---|
api_key_file
|
LLM provider API key (api_key) |
model:
|
auth_credentials_file
|
Vertex AI service-account JSON (auth_credentials) |
model:
|
oauth_client_secret_file
|
OAuth2 client secret (oauth_client_secret) |
model:
|
credentials_file
|
GCS service-account JSON | artifact_service: when type: gcs |
Within the model: block the pattern is general: oauth_client_id_file and oauth_token_url_file behave the same way, as does any other <key>_file. Outside the model: block, the GCS artifact store's credentials_file is the one file-mount field; every other block takes its secrets from the environment through ${VAR} substitution.
The Secret-Bearing Surfaces
Each row below is one piece of secret material Agent Mesh consumes. The YAML reads the value through ${VAR} substitution, the file-mount alternative, or a direct environment fallback.
| Surface | How it is supplied | YAML field | Consumed by |
|---|---|---|---|
| Broker password | SOLACE_BROKER_PASSWORD environment variable |
broker.broker_password
|
Every component |
| LLM provider key | <PROVIDER>_API_KEY environment variable, for example ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY, GROQ_API_KEY |
model.api_key or model.api_key_file |
Every agent that calls an LLM |
| Vertex AI service account | mounted file | model.auth_credentials_file
|
Vertex agents |
| Bedrock credentials | inline values, or an IAM role | model.access_key / model.secret_key |
Bedrock agents |
| S3 artifact store credentials | AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY |
artifact_service.aws_access_key_id / aws_secret_access_key |
S3 artifact store |
| GCS service account | GCS_CREDENTIALS_JSON (inline JSON), a path in credentials_file, or GOOGLE_APPLICATION_CREDENTIALS (file path) |
artifact_service.credentials_file
|
GCS artifact store |
| Azure Blob credentials | AZURE_STORAGE_CONNECTION_STRING, or AZURE_STORAGE_ACCOUNT_NAME + AZURE_STORAGE_ACCOUNT_KEY |
artifact_service.connection_string (or account_name + account_key) |
Azure artifact store |
| Database password | embedded in the connection URL; DATABASE_URL fallback |
session_service.database_url
|
Entrypoints and agents with persistent sessions |
| Session cookie signing key | SESSION_SECRET_KEY
|
session_secret_key
|
HTTP/SSE entrypoint |
| OIDC client secret | referenced as ${OIDC_CLIENT_SECRET} |
providers.<name>.client_secret
|
Entrypoint with OIDC login |
| LLM-gateway OAuth2 client secret | referenced as ${VAR}, or mounted via oauth_client_secret_file |
model.oauth_client_secret
|
Agents behind an OAuth2-secured LLM gateway |
| Slack tokens | referenced as ${SLACK_BOT_TOKEN} / ${SLACK_APP_TOKEN} |
slack_bot_token / slack_app_token |
Slack entrypoint |
| Email IMAP password | referenced as ${VAR} |
imap_password
|
Email entrypoint |
The surfaces wire in two ways. Some are read directly by Agent Mesh as an environment fallback when the YAML value is absent: the broker password, the per-provider LLM key, the S3, GCS, and Azure storage credentials, the database URL, and the session key. The rest are substitution-only: the YAML field references a variable through ${VAR} and the loader expands it before parsing, so the environment-variable name is yours to choose. The names in the table are the ones the shipped configurations reference.
The Slack and email tokens live in the values: map of the Slack and email entrypoint definitions, not in a separate block. The email entrypoint is gated behind the SAM_FEATURE_EMAIL_GATEWAY feature flag, and imap_password is its basic-authentication mode (an OAuth2 alternative exists).
Individual tools and connectors can carry their own credentials, such as a search-API or speech-API key. Those are configured on the tool or connector, wired the same way through ${VAR} substitution or a *_file mount.
Generating a Session-Cookie Key
The HTTP/SSE entrypoint signs its session cookies with session_secret_key. Generate a value with 32 bytes of entropy:
openssl rand -hex 32
When OIDC login is enabled, the entrypoint refuses to start unless session_secret_key (or the SESSION_SECRET_KEY environment variable) is set. When OIDC is not enabled, an empty or short value is accepted at startup, and the length is never validated. Set it regardless: it signs the cookies that carry each user's access token, and a guessable key lets an attacker forge cookies for any user. Treat it like a database password.
Rotation Procedures
Rotation always has the same shape: change the underlying source, then restart the consuming process. The mechanics differ by deployment.
Local Development
Edit .env and restart the local runtime. The CLI re-reads .env on startup.
Kubernetes
Configuring Agent Mesh covers the initial wiring, where extraSecretEnvironmentVars maps each environment variable to a Secret name and key. To rotate, update the underlying Secret:
kubectl create secret generic agent-mesh-secrets \
--from-literal=ANTHROPIC_API_KEY="${ANTHROPIC_API_KEY}" \
--dry-run=client -o yaml \
| kubectl apply -f -
Then roll the Deployments that consume the value, so the pods pick up the new environment variables. The consuming workloads are the entrypoint process and the Agent-Workflow Executor process:
kubectl rollout restart deployment/<deployment-name>
For file-mounted secrets the kubelet propagates new contents to the volume without a restart, typically within a minute. A kubectl rollout restart is still the safest path, because a consumer that reads the credential once at startup does not otherwise pick up the new value.
Per-Surface Notes
-
Broker password — Rotate at the broker, then restart the components or let them reconnect. New connections authenticate with the new credential.
-
LLM provider key — Issue a new key, deploy it, then revoke the old one at the provider. Most providers keep both keys live during an overlap window.
-
Object-storage credentials — Prefer IAM roles through the cloud provider's default credential chain (
AWS_ROLE_ARN, GCP workload identity, Azure managed identity). Those rotate transparently and need no application-side change. -
Database password — A rolling restart of the entrypoint and the agents that hold persistent sessions is enough; the new connection pool uses the new credential.
-
Session cookie signing key — Changing this invalidates every active session, because existing cookies were signed with the old key. Every signed-in user is redirected to log in again on the next request. Plan for a brief sign-in surge or pick a low-traffic window.
-
OIDC client secret — Rotate at the identity provider, update the variable, then restart the entrypoint. In-flight logins fail during the restart window. Existing logged-in sessions stay valid, because cookies are signed with
SESSION_SECRET_KEY, not the identity provider secret. -
LLM-gateway OAuth2 client secret — The client caches the access token until it expires, so rotation does not take effect until the next refresh. Restart the agents to force an immediate fetch.
Secrets in Logs
The logging surface is structured to avoid leaking secret material:
-
Database connection URLs are redacted before they reach the log handler. A URL such as
postgres://user:PASSWORD@host/dbis emitted aspostgres://user:xxxxx@host/db. SQLite URLs, which carry no credentials, and URLs that fail to parse are handled defensively so the raw string is never logged. -
OAuth2 token-endpoint error bodies have two distinct treatments. The LLM-gateway OAuth2 client-credentials path caps the logged body at 200 bytes and extracts only the RFC 6749
erroranderror_descriptionfields. The OIDC user-login refresh path reads the full response body with no size cap and embeds it in the returned error, so a downstream handler that logs that error logs the full body. Filter token-endpoint response bodies at log ingest as a catch-all rather than relying on either path's behavior. -
Audit records carry structured, security-relevant fields and no request material. Each event emits fields identifying the user, task, tool, agent, session, and outcome (for example
userID,taskID,agentName,sessionID, andduration_ms); the exact key set depends on the event type.Authorizationheaders, cookies, and request bodies are never part of an audit record. See Managing Audit and Compliance for the full event catalog.
The structured logger does not emit known secret-bearing fields by name. As an additional guard against a future change that logs a raw error chain wrapping a sensitive value, enforce a generic regex at log ingest that redacts password=, client_secret=, and bearer-token shapes.
What Next?
Secrets are wired in and the rotation procedure is in place. The companion topic is the certificate side of the same security boundary, covered in Configuring TLS.