Secrets

Configure HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, 1Password Connect, and OS Keyring as secret sources for agentsh. Secrets are addressed via a URI scheme and consumed by HTTP services and other features that need credentials.

Overview#

The agentsh secrets layer is a unified abstraction over multiple secret stores. It lets operators reference credentials by URI — vault://kv/github#token, aws-sm://prod/api-key, op://Personal/GitHub#token — without embedding secrets in policy files or environment variables.

The most common consumer is declared HTTP services with credential substitution, which use a secret.ref field on a services: entry to bind a destination host to a secret URI. When a session starts, agentsh fetches the real secret from the upstream provider, generates a paired fake credential, and stores both in a per-session substitution table. The agent only ever sees the fake; the daemon swaps in the real value at the network edge.

When to use the secrets layer instead of plain environment variables:

URI Scheme#

Every secret reference in agentsh is a URI of the form <provider>://<host>[/<path>][#<field>]. The provider scheme determines which provider handles the lookup; the host and path together identify the secret within that provider (interpretation varies per provider — for Vault it's mount point + key path, for AWS Secrets Manager it's a path-style secret name); the optional fragment selects a field within a structured secret (e.g., the token field of a multi-field Vault KV entry). The host component is required — a URI like vault:///github with no host is rejected at parse time.

ProviderURI prefixExample#field support
HashiCorp Vaultvault://vault://kv/github#tokenYes (KV multi-field entries)
AWS Secrets Manageraws-sm://aws-sm://prod/github-tokenYes (JSON-shaped secrets)
GCP Secret Managergcp-sm://gcp-sm://github-tokenYes (JSON-shaped secrets)
Azure Key Vaultazure-kv://azure-kv://github-tokenYes (JSON-shaped secrets)
1Password Connectop://op://Personal/GitHub#tokenYes (item field selector)
OS Keyringkeyring://keyring://agentsh/github-tokenNo (rejected — entries are scalar)

Resolution dispatches to the corresponding provider's lookup function based on the URI scheme. Providers are registered in the policy file under the top-level providers: key (see each provider section below for the schema). The secrets providers: key is unrelated to proxy.providers in config.yml, which configures Anthropic and OpenAI API base URLs.

Providers#

agentsh supports six provider backends. Each provider has its own schema, authentication methods, and URI format. The sub-sections below cover each in turn.

HashiCorp Vault#

HashiCorp Vault is the most flexible backend agentsh supports. Use it when your organization already runs Vault or when you need fine-grained policies on individual secrets.

Schema

providers:
  vault:                       # provider name (referenced by URIs)
    type: vault
    address: https://vault.example.com:8200
    namespace: my-namespace    # optional, Vault Enterprise only
    auth:
      method: token            # token | approle | kubernetes
      # --- for method: token ---
      token: hvs.CAESI...
      # token_ref: keyring://agentsh/vault_token  # chained alternative
      # --- for method: approle ---
      # role_id: my-role-id
      # secret_id: my-secret-id
      # role_id_ref: ...        # chained alternative
      # secret_id_ref: ...      # chained alternative
      # --- for method: kubernetes ---
      # kube_role: agentsh-prod
      # kube_mount_path: kubernetes        # default: "kubernetes"
      # kube_token_path: /var/run/secrets/kubernetes.io/serviceaccount/token
      #                                    # default shown

Authentication methods

URI format

vault://<mount>/<path>[#<field>]

Where <mount> is the Vault KV mount point (e.g., kv, secret) and <path> is the path to the secret within that mount. The optional fragment #<field> selects a single field from the secret's JSON body.

Example

providers:
  vault:
    type: vault
    address: https://vault.corp.internal:8200
    auth:
      method: kubernetes
      kube_role: agentsh-prod
      kube_mount_path: kubernetes

# Reference the secret in a credential-substitution services entry:
services:
  - name: github
    match:
      hosts: ["api.github.com"]
    secret:
      ref: vault://kv/github#token
    fake:
      format: "ghp_{rand:36}"
    inject:
      header:
        name: Authorization
        template: "Bearer {{secret}}"

HashiCorp Vault documentation →

AWS Secrets Manager#

AWS Secrets Manager is the right backend when agentsh runs in AWS and your secrets already live in Secrets Manager.

Schema

providers:
  awssm:
    type: aws-sm
    region: us-east-1

The only required field is region. There is no auth: block — authentication is delegated to the AWS SDK default credential chain.

Authentication

The AWS-SM provider calls awsconfig.LoadDefaultConfig, so it picks up credentials from the standard AWS SDK chain in this order:

  1. Environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN)
  2. The shared credentials file (~/.aws/credentials), respecting AWS_PROFILE
  3. EC2 instance profile credentials
  4. ECS task role credentials (when running under ECS)
  5. EKS service account credentials (IRSA, when running under EKS with a service account annotation)

To use a specific profile or assumed role, set the corresponding env vars or shared-config entries outside agentsh before invocation — the SDK will discover them. agentsh itself does not have a profile or role-arn field.

URI format

aws-sm://<secret-name>[#<json-field>]

Where <secret-name> is the Secrets Manager secret ID (the name or full ARN). The optional fragment selects a single field from a JSON-shaped secret. If omitted, the entire secret value is returned as a string.

Example

providers:
  awssm:
    type: aws-sm
    region: us-east-1

services:
  - name: github
    match:
      hosts: ["api.github.com"]
    secret:
      ref: aws-sm://prod/github-token
    fake:
      format: "ghp_{rand:36}"
    inject:
      header:
        name: Authorization
        template: "Bearer {{secret}}"

AWS Secrets Manager documentation →

GCP Secret Manager#

Google Cloud Secret Manager is the right backend when agentsh runs in GCP and your secrets live in Secret Manager.

Schema

providers:
  gcpsm:
    type: gcp-sm
    project_id: my-gcp-project

The only required field is project_id. There is no auth: block — authentication is delegated to Google Application Default Credentials (ADC).

Authentication

The GCP-SM provider calls secretmanager.NewClient, which uses Application Default Credentials. ADC discovers credentials from, in order:

  1. The GOOGLE_APPLICATION_CREDENTIALS environment variable, if set, pointing at a service account JSON key file
  2. The user credentials configured by gcloud auth application-default login
  3. The attached service account on a GCE/GKE/Cloud Run/Cloud Functions instance
  4. GKE Workload Identity, if configured

To use a specific service account, set GOOGLE_APPLICATION_CREDENTIALS in the environment before invoking agentsh. agentsh itself does not have a service-account-file field.

URI format

gcp-sm://<secret-name>[#<field>]

Where <secret-name> is the Secret Manager secret ID (just the name; the project comes from project_id in the provider config). The provider always reads versions/latest. The optional #<field> fragment selects a single JSON key when the secret value is JSON-shaped.

Example

providers:
  gcpsm:
    type: gcp-sm
    project_id: my-prod-project

services:
  - name: github
    match:
      hosts: ["api.github.com"]
    secret:
      ref: gcp-sm://github-token
    fake:
      format: "ghp_{rand:36}"
    inject:
      header:
        name: Authorization
        template: "Bearer {{secret}}"

GCP Secret Manager documentation →

Azure Key Vault#

Azure Key Vault is the right backend when agentsh runs in Azure and your secrets live in Key Vault.

Schema

providers:
  azurekv:
    type: azure-kv
    vault_url: https://my-vault.vault.azure.net

The only required field is vault_url. There is no auth: block — authentication is delegated to DefaultAzureCredential.

Authentication

The Azure-KV provider calls azidentity.NewDefaultAzureCredential, which discovers credentials from the standard DefaultAzureCredential chain in order:

  1. Environment variables (AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET for a service principal; or AZURE_USERNAME/AZURE_PASSWORD for a user)
  2. Workload identity (when running on AKS with workload-identity federation enabled)
  3. Managed identity (when running on a VM, App Service, Container Instance, or Function App with a managed identity)
  4. Azure CLI (az login) credentials
  5. Azure PowerShell (Connect-AzAccount) credentials
  6. Azure Developer CLI (azd) credentials

To use a service principal explicitly, set the corresponding env vars outside agentsh. agentsh itself does not have a tenant-id/client-id/client-secret field set.

URI format

azure-kv://<secret-name>[#<field>]

Where <secret-name> is the Key Vault secret identifier (alphanumerics and hyphens only). The vault itself is determined by vault_url in the provider config, not by the URI. Path components are not allowed — a URI like azure-kv://name/extra is rejected. The optional #<field> fragment selects a single JSON key when the secret value is JSON-shaped.

Example

providers:
  azurekv:
    type: azure-kv
    vault_url: https://prod-secrets.vault.azure.net

services:
  - name: github
    match:
      hosts: ["api.github.com"]
    secret:
      ref: azure-kv://github-token
    fake:
      format: "ghp_{rand:36}"
    inject:
      header:
        name: Authorization
        template: "Bearer {{secret}}"

Azure Key Vault documentation →

1Password Connect#

1Password Connect is the right backend for teams that use 1Password as their secrets store. agentsh talks to a self-hosted Connect server, not directly to the 1Password cloud. The provider type string is op, matching the URI scheme.

Schema

providers:
  op:
    type: op
    server_url: https://op-connect.internal:8080
    api_key: eyJhbGciOi...
    # Or, recommended: chain api_key_ref to another provider
    # api_key_ref: keyring://agentsh/op_api_key

Required fields: server_url and exactly one of api_key or api_key_ref. The two are mutually exclusive — the parser rejects a config that sets both.

Authentication

1Password Connect uses a single API key (formerly called a Connect token) issued by your 1Password Connect server. The key authorizes agentsh to read items from the vaults the key has been granted access to. Two ways to provide it:

URI format

op://<vault>/<item>[#<field>]

Where <vault> is the 1Password vault name, <item> is the item title within that vault, and the optional #<field> fragment selects a single field within that item (e.g., password, token, or a custom field name). If the fragment is omitted, the default credential field for the item is returned.

Example

providers:
  kr:
    type: keyring
  op:
    type: op
    server_url: https://op-connect.corp.internal:8080
    api_key_ref: keyring://agentsh/op_api_key

services:
  - name: github
    match:
      hosts: ["api.github.com"]
    secret:
      ref: op://Engineering/GitHub#token
    fake:
      format: "ghp_{rand:36}"
    inject:
      header:
        name: Authorization
        template: "Bearer {{secret}}"

1Password Connect documentation →

OS Keyring#

The OS keyring provider reads secrets from the operating system's native secret store via the freedesktop.org Secret Service API on Linux, the macOS Keychain, or the Windows Credential Manager. It is the right backend for development and for cases where you want to bootstrap other providers without storing tokens in plaintext.

Schema

providers:
  kr:
    type: keyring

The keyring provider takes no configuration. It uses the OS-native secret store of the host running agentsh. There are no auth methods to configure — the OS handles authentication based on the user session.

URI format

keyring://<service>/<account>

Where <service> is the service name (typically agentsh or a sub-namespace) and <account> is the account or key name within that service. The mapping to the OS keyring depends on the platform:

Storing a secret in the keyring

The keyring provider only reads — secrets must be stored in the OS keyring out of band. Common ways to store:

# Linux (using secret-tool from libsecret)
echo "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" | secret-tool store \
    --label="agentsh GitHub token" service agentsh account github-token

# macOS
security add-generic-password -s agentsh -a github-token \
    -w "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# Windows (PowerShell)
cmdkey /generic:agentsh:github-token /user:agentsh \
    /pass:"ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Example

providers:
  kr:
    type: keyring

services:
  - name: github
    match:
      hosts: ["api.github.com"]
    secret:
      ref: keyring://agentsh/github-token
    fake:
      format: "ghp_{rand:36}"
    inject:
      header:
        name: Authorization
        template: "Bearer {{secret}}"

The keyring provider is also the canonical way to bootstrap other providers — see Provider Chaining for an example of using the keyring to source a Vault token.

freedesktop.org Secret Service specification →

Provider Chaining#

One provider can source its own credentials from another provider via _ref fields. Any field whose name ends in _ref is parsed as a secret URI and resolved before the parent provider is initialized. This lets you avoid putting bootstrap credentials in plaintext config files.

The canonical example: a Vault token sourced from the OS keyring.

providers:
  kr:
    type: keyring
  vault:
    type: vault
    address: https://vault.corp.internal:8200
    auth:
      method: token
      token_ref: keyring://agentsh/vault_token

How it resolves:

  1. kr is a keyring provider that needs no credentials of its own — it talks to the OS Secret Service.
  2. When agentsh initializes the vault provider, it sees token_ref instead of token and resolves the URI by calling the kr provider.
  3. The keyring returns the value stored under agentsh/vault_token, which is then passed to the Vault client as the auth token.

Cycle detection. Provider chains are validated at startup. If provider A's _ref resolves to provider B and B's _ref resolves back to A, agentsh refuses to start with an explicit cycle error. Self-references (a provider chained to itself) are also rejected.

What can be chained. Any field whose name ends in _ref on a provider's auth block. The full set of chained fields:

Lifecycle#

When secrets are fetched. Secrets are fetched at session start, before the agent process is launched. When a session loads a policy that declares any services: entries, the daemon constructs each provider in topological order (so chained *_ref providers build before their consumers), iterates over every declared service, fetches its referenced secret from the upstream provider, generates a paired fake credential, and stores both in a per-session substitution table. A secret declared in a policy file is fetched even if no rule ever uses it during the session — the bootstrap is unconditional. After each entry is added to the table, the temporary copy of the real secret is wiped from memory; the table itself retains the only copy.

Caching and rotation. Resolved secrets stay in the per-session substitution table for the lifetime of the session. They are not refreshed mid-session even if the upstream provider's value rotates — rotation takes effect on the next session. The table is zeroed at session cleanup.

Failure mode. Provider errors at bootstrap fail closed: if any fetch fails, any fake-generation fails, or any provider construction fails, all already-loaded entries are zeroed and session creation returns the underlying error. The agent process is never launched. There is no in-session "operation denied" path for a missing or unreachable secret — the entire session simply does not start. On success, agentsh emits a secrets_initialized log event with the session ID and the count of services bootstrapped.