Skip to main content

Secrets resolver

Synopsis

The secrets resolver replaces ${secrets:<ref>} placeholders in config values with actual secret material at runtime. It walks a precedence chain of backends until one returns a value. If none do, the placeholder stays unresolved and the dependent component fails with an actionable error.

Source: packages/types/src/secrets.ts (the SecretsResolver interface). Implementations: packages/storage-fs/src/secrets.ts, packages/storage-fs/src/env-secrets.ts, extensions/secrets-aws/src/index.ts.

Interpolation syntax

${secrets:<ref>}

<ref> is a forward-slash-delimited path. Examples:

PlaceholderResolved ref
${secrets:providers/anthropic/apiKey}providers/anthropic/apiKey
${secrets:channels/telegram/default/botToken}channels/telegram/default/botToken

The resolver strips ${secrets: and the trailing }, then passes the ref to each backend in order.

Resolver precedence

Backends are tried in this order. The first backend that returns a non-null value wins.

PriorityBackendSourceWhen active
1.env file~/.ethos/.envAlways (if file exists)
2Process environmentprocess.envAlways
3AWS Secrets ManagerAWS APIWhen aws.secrets.enabled: true in config
4On-disk files~/.ethos/secrets/<ref>Always (if file exists)

If all backends return null for a ref, the resolver returns null. The calling code decides whether null is fatal -- provider wiring throws, optional integrations skip.

Backend behavior

.env file

Reads ~/.ethos/.env as KEY=VALUE pairs. The ref is converted to an env-style key: slashes become underscores, the whole string is uppercased. providers/anthropic/apiKey becomes PROVIDERS_ANTHROPIC_APIKEY.

OperationSupportedNotes
getYesReads from parsed .env contents
setNo.env is operator-managed
deleteNo
listYesReturns all keys in the file

Process environment

Same key conversion as .env. Reads from process.env directly.

OperationSupportedNotes
getYesprocess.env[KEY]
setNo
deleteNo
listNoNot enumerable in a meaningful way

AWS Secrets Manager

Active only when aws.secrets.enabled: true. The full secret name in AWS is <prefix>/<ref>, where <prefix> comes from aws.secrets.prefix in config.

OperationSupportedNotes
getYesGetSecretValue API call
setNoSecrets are provisioned out-of-band by the operator
deleteNo
listYesListSecrets filtered by prefix

Uses the default AWS credential chain (instance role, ECS task role, environment variables, ~/.aws/credentials). The aws.secrets.region config field sets the client region.

On-disk files

Reads the file at ~/.ethos/secrets/<ref>. The entire file content (trimmed of trailing newline) is the secret value.

OperationSupportedNotes
getYesreadFile on the path
setYeswriteAtomic to the path
deleteYesRemoves the file
listYesDirectory listing under ~/.ethos/secrets/

Configuration

All fields in config.yaml:

FieldTypeDefaultDescription
aws.secrets.enabledbooleanfalseEnable the AWS Secrets Manager backend
aws.secrets.regionstringAWS region for the Secrets Manager client (required when enabled)
aws.secrets.prefixstringPrefix prepended to every ref before calling AWS (required when enabled)

Cache behavior

The resolver caches resolved values in memory. There is no TTL -- once a secret is fetched, it stays cached until one of:

  • SIGHUP -- clears the entire cache and re-fetches all refs on next access.
  • Process restart -- cache is in-memory only, not persisted.

There is no background polling. Cache invalidation is operator-driven. See Secrets architecture for why.

Failure modes

AWS Secrets Manager errors and their resolver behavior:

AWS errorResolver behaviorUser-visible effect
ResourceNotFoundExceptionReturns null, falls through to next backendSilent if a lower-priority backend has the value; unresolved placeholder if none do
AccessDeniedExceptionThrows immediatelyStartup fails with message naming the ref and suggesting IAM policy review
ThrottlingExceptionThrows immediatelyStartup fails with message naming the ref and suggesting retry or request-limit increase
Credential failure (no role, expired token)Throws immediatelyStartup fails with actionable message: "No AWS credentials found -- attach an IAM role or set AWS_ACCESS_KEY_ID"
Network timeoutThrows immediatelyStartup fails with message suggesting region check and network connectivity

The design: missing secrets are recoverable (fall through); permission and infrastructure errors are not (throw early with a fix).

See also