VAULTS
A vault is a named secret storage backend. Vaults store and retrieve sensitive
values — API keys, passwords, tokens — used in behavior definitions and
workflows. Vault configurations live in vaults/{vault-type}/{id}.yaml within a
Swamp repository. Encrypted secret data lives in .swamp/secrets/.
swamp repo init creates a local_encryption vault named default and sets
defaultVault to it,
unless run with --no-vault. A new repository can store a secret straight away
with swamp vault put default KEY=value and read it as
${{ vault.get(default, KEY) }}. See
The default vault.
File Structure
vaults/
local_encryption/
0dd4c691-0b7b-4fae-9741-31e96a4efc69.yaml
.swamp/secrets/
local_encryption/
default/
.key
api-key.enc
db-password.encThe vaults/ directory stores vault configurations (tracked in git). The
.swamp/secrets/ directory stores encrypted secret data (not tracked in git,
not included in datastore sync — see
Local Storage and Datastore Sync below).
Local Storage and Datastore Sync
local_encryption vault secrets always live in .swamp/secrets/ on the local
machine. They are never included in datastore migration or ongoing sync,
regardless of the datastore backend (filesystem, S3, GCS, or any other
extension-provided backend).
This exclusion enables a shared workflow, per-user local credentials
pattern: a vault's definition (vaults/local_encryption/*.yaml) is
git-tracked and shared across teammates, but each person's actual secret values
stay local to their machine even when the rest of the repository's state syncs
through a shared datastore. Each teammate runs swamp vault put to store their
own credentials locally.
When a team needs shared access to the same secret values, migrate the vault to a cloud backend such as AWS Secrets Manager, Azure Key Vault, or 1Password — see Vault Migration and the Collaborate guide.
Per-machine storage
The default vault that swamp repo init creates follows the same rule. Its
config is committed, but its key and secrets are per machine:
- A clone of the repository starts with an empty
defaultvault, and generates its own key when it writes its first secret. - Sensitive outputs a behavior stores in
defaulton one machine cannot be read on another.
swamp datastore setup extension warns about this when defaultVault names a
local_encryption vault, since other machines sharing the datastore cannot read
what it holds:
The default vault 'default' is a local_encryption vault: its key and secrets stay on this machine, so other machines using this datastore cannot read secrets or sensitive outputs stored in it. To share them, migrate it with 'swamp vault migrate default', or create a vault with a shared provider and set it as defaultVault in .swamp.yaml.Migrating default keeps its name, so vault.get(default, ...) references and
defaultVault keep working.
Vault Configuration
Each vault is defined by a YAML file with the following fields:
id: 0dd4c691-0b7b-4fae-9741-31e96a4efc69
name: my-secrets
type: local_encryption
config:
auto_generate: true
createdAt: "2026-04-07T16:59:59.039Z"id
Unique identifier for the vault configuration.
| Property | Value |
|---|---|
| Type | string (UUID v4) |
| Required | Yes |
| Default | Auto-generated on create |
| Format | xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx |
name
Human-readable name used to reference the vault in CLI commands and CEL expressions.
| Property | Value |
|---|---|
| Type | string |
| Required | Yes |
| Default | None |
Must be unique within the repository. Must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens.
type
Vault provider type.
| Property | Value |
|---|---|
| Type | string |
| Required | Yes |
| Default | None |
See Vault Types for available values.
config
Provider-specific configuration.
| Property | Value |
|---|---|
| Type | Record<string, unknown> |
| Required | No |
| Default | {} |
Available keys depend on the vault type. See the configuration section for each type below.
createdAt
ISO 8601 timestamp of when the vault was created.
| Property | Value |
|---|---|
| Type | string |
| Required | Yes |
| Default | Set on create |
| Format | ISO 8601 (Z) |
auditReads
Enables read-operation auditing for the vault. When true, secret reads are
recorded in the vault audit trail alongside write operations. Write operations
(put, delete, annotate) are always audited when an audit repository
exists, regardless of this setting.
| Property | Value |
|---|---|
| Type | boolean |
| Required | No |
| Default | false |
| Set via | --audit-reads on create, or edit the vault YAML directly |
Vault Types
local_encryption
Stores encrypted secrets in local files using AES-GCM encryption. This is the built-in vault type available in every Swamp repository.
Configuration
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
ssh_key_path |
string | No | ~/.ssh/id_rsa |
Path to an unencrypted SSH private key |
auto_generate |
boolean | No | true |
Auto-generate an encryption key if no SSH key |
key_file |
string | No | secrets/.key |
Custom path for the auto-generated key file |
base_dir |
string | No | Repository root | Base directory for secret storage. Not stored by default; resolved to the repository root at runtime |
When no ssh_key_path is provided and auto_generate is true (the default),
Swamp creates a random 72-character key (two concatenated UUIDs) at
.swamp/secrets/local_encryption/{vault-name}/.key.
vault create, vault migrate and swamp repo init leave base_dir out of
the stored config. It resolves to the repository root whenever the vault loads,
so the committed file works in every clone and on every host. A config that
already stores an absolute base_dir keeps using it; Swamp never rewrites it.
When ssh_key_path is provided, the SSH private key is used as key material for
PBKDF2 derivation. The key must be unencrypted — passphrase-protected SSH keys
(both legacy PEM and OpenSSH format) are rejected.
# Auto-generated key (default)
$ swamp vault create local_encryption my-secrets
# SSH key-backed
$ swamp vault create local_encryption infra \
--config '{"ssh_key_path":"~/.ssh/id_ed25519"}'Note
The key-source fields ssh_key_path, auto_generate, key_file and
base_dir can only be chosen by a local invocation, run on the host that
stores the secrets. Over --server, vault create and vault migrate use
the server's defaults and refuse any other value: they store
auto_generate: true and leave base_dir unset, so it resolves to the serve
host's repository root when the vault loads. A base_dir equal to the serve
repository is accepted and stored unset. vault edit must keep the stored
values. A remote create fails like this:
Cannot set ssh_key_path for vault 'infra': a local_encryption vault created remotely uses the server's key source. Leave these fields out, or run the command on the host running swamp.Leftover storage
swamp vault create local_encryption {vault-name} and
swamp vault migrate {vault-name} --to-type local_encryption refuse when
.swamp/secrets/local_encryption/{vault-name}/ already holds anything: a key,
secrets, annotations, or a file or symlink in the directory's place. An empty
directory is accepted. The directory is left behind when an earlier vault of the
same name lost its config but kept its storage — a config deleted by hand, a
vault remove whose push failed, or a removal pulled into another checkout.
$ swamp vault create local_encryption old
Error: Cannot create vault 'old': .swamp/secrets/local_encryption/old already holds a key or secrets left by an earlier vault named 'old'. Restore that vault's config to keep using them, or remove the directory to start this vault empty.vault migrate ends the same message with
Remove the directory to migrate into an empty vault.
To keep the earlier vault's secrets, restore its config file under
vaults/local_encryption/. To start empty, delete the directory and run the
command again.
Encryption Details
| Property | Value |
|---|---|
| Algorithm | AES-GCM (authenticated encryption) |
| Key size | 256-bit |
| IV size | 96-bit (random per secret) |
| Salt size | 128-bit (random per secret) |
| Key derivation | PBKDF2 with SHA-256, 100,000 iterations |
Each encrypted file is stored as JSON:
{
"iv": "ZhURGq+UA75P0Ydb",
"data": "OQayYKhVm6AcQXpzukNcSueagt1b3OQvtxg9it8=",
"salt": "sCvS1h08k+lFZ37PIfjSLQ==",
"version": 2
}All values are base64-encoded. version tracks the encrypted data format.
File Permissions
| Path | Mode |
|---|---|
.swamp/secrets/local_encryption/{vault-name}/ |
700 |
.swamp/secrets/local_encryption/{vault-name}/.key |
600 |
.swamp/secrets/local_encryption/{vault-name}/{key}.enc |
600 |
SSH key files are validated for 600 or stricter permissions on Unix systems.
An existing .key or configured key_file wider than 600 is still used, but
on Unix Swamp logs a warning naming the file, its mode and the fix:
[WRN] vaults·local-encryption: Key file for local vault "alpha": "'/repo/.swamp/secrets/local_encryption/alpha/.key' has insecure permissions (0o644). Expected permissions no wider than 0o600. Run 'chmod 600 /repo/.swamp/secrets/local_encryption/alpha/.key' to fix."The key file's permissions are not checked on Windows.
Vaults can be migrated between types using swamp vault migrate. The vault name
is preserved, so all existing vault.get() references continue to resolve
without modification. See Vault Migration and the
swamp vault migrate CLI command for
details.
Extension Vault Types
Additional vault types are available through extensions. Every Swamp command
needs a swamp-club.com account; creating a vault with an extension type
additionally requires a collective token to carry the vault:* scope.
local_encryption needs no scope.
| Type | Extension | Description |
|---|---|---|
@swamp/aws-sm |
@swamp/aws-sm |
AWS Secrets Manager |
@swamp/azure-kv |
@swamp/azure-kv |
Azure Key Vault |
@swamp/1password |
@swamp/1password |
1Password integration |
Legacy type names (aws, aws-sm, azure, azure-kv, 1password) are
automatically mapped to their scoped equivalents.
Custom Vault Types
Custom vault providers can be added via extension files in
extensions/vaults/*.ts. Each file must export a vault object implementing
the Vault Provider interface. See
the Create and Publish guide for
packaging extensions.
Custom vault type names must use the @collective/name format. Reserved
collectives (swamp, si) are not allowed.
$ swamp vault type search{
"query": "",
"results": [
{
"type": "local_encryption",
"name": "Local Encryption",
"description": "Store encrypted secrets in local files using AES-GCM encryption. Uses SSH private key or auto-generated key for encryption."
}
]
}Key Storage and Retrieval
Storing Secrets
$ swamp vault put my-secrets api-key
Enter value for api-key: ********17:00:04 INF vault·put Stored secret "api-key" in vault "my-secrets"The value can be provided in several ways:
| Method | Command |
|---|---|
| Interactive | swamp vault put <vault> <key> (hidden prompt) |
| Piped stdin | echo "$SECRET" | swamp vault put <vault> <key> |
| Third argument | swamp vault put <vault> <key> <value> |
| Inline | swamp vault put <vault> KEY=VALUE |
Use --yes (-y) to overwrite an existing key without confirmation.
Piping via stdin is recommended for scripts and CI. The third-argument and
inline KEY=VALUE forms expose the secret in shell history, process tables, and
system logs — use them only for non-sensitive test data.
Metadata labels can be attached at store time with --label (repeatable).
Labels are stored as annotation labels — the same as
vault annotate --label — and are visible via vault inspect.
$ swamp vault put my-secrets api-key --label owner=platform-team --label env=prod
Enter value for api-key: ********17:00:04 INF vault·put Stored secret "api-key" in vault "my-secrets"Retrieving Secrets
Secrets can be retrieved in two ways:
- CEL expressions in behavior definitions and workflows — see CEL Integration below.
- CLI via
swamp vault read-secret— reads a single secret value, with a confirmation prompt for safety.
Listing Keys
$ swamp vault list-keys my-secrets17:00:29 INF vault·list-keys Vault "my-secrets" ("local_encryption"): 2 key(s)
17:00:29 INF vault·list-keys - "api-key"
17:00:29 INF vault·list-keys - "db-password"Lists key names only. Values are never displayed.
Annotations
Annotations attach provenance metadata to a vault secret — a source URL, free-text notes, and key=value labels. Annotations are stored alongside the secret and do not affect the secret value itself.
Fields
| Field | Type | Description |
|---|---|---|
url |
string (optional) |
URL associated with this secret |
notes |
string (optional) |
Free-text notes about this secret |
labels |
Record<string, string> |
Key=value metadata labels |
updatedAt |
string (ISO 8601) |
Timestamp of the last annotation update |
Merge Semantics
Annotations use merge semantics: only the fields you specify are updated,
existing fields are preserved. For example, updating notes leaves url and
labels unchanged. Labels are merged additively — new labels are added to
existing ones.
Use --clear to remove all annotations from a secret.
Annotating Secrets
$ swamp vault annotate my-secrets api-key \
--url https://console.aws.com/iam \
--notes "Production API key" \
--label env=prod --label team=infraINF vault·annotate Annotated "api-key" in vault "my-secrets" (fields: "url, notes, labels")Inspecting Metadata
$ swamp vault inspect my-secrets api-keyINF vault·inspect Metadata for "api-key" in vault "my-secrets":
INF vault·inspect size: 42 chars (42 bytes)
INF vault·inspect url: "https://console.aws.com/iam"
INF vault·inspect notes: "Production API key"
INF vault·inspect label: "env"="prod"
INF vault·inspect label: "team"="infra"
INF vault·inspect updated: "2026-05-22T23:53:23.906Z"
INF vault·inspect refresh-hook: (none)JSON output:
$ swamp vault inspect my-secrets api-key --json{
"vaultName": "my-secrets",
"secretKey": "api-key",
"vaultType": "local_encryption",
"sizeBytes": 42,
"sizeChars": 42,
"valueType": "string",
"supportsAnnotations": true,
"hasAnnotation": true,
"annotation": {
"updatedAt": "2026-05-22T23:53:23.906Z",
"url": "https://console.aws.com/iam",
"notes": "Production API key",
"labels": {
"env": "prod",
"team": "infra"
}
},
"supportsRefreshHooks": true,
"hasRefreshHook": false,
"refreshHook": null
}When no annotation exists:
$ swamp vault inspect my-secrets db-passwordINF vault·inspect Metadata for "db-password" in vault "my-secrets":
INF vault·inspect size: 12 chars (12 bytes)
INF vault·inspect annotation: (none)
INF vault·inspect refresh-hook: (none)Provider Support
Annotation support is opt-in per vault provider. Providers that implement the
VaultAnnotationProvider
interface support annotations. The built-in local_encryption provider and the
official extension providers (@swamp/aws-sm, @swamp/azure-kv,
@swamp/1password) support annotations. Custom vault providers may or may not —
check the extension's documentation. If a provider does not support annotations,
swamp vault annotate returns an error.
Refresh Hooks
A refresh hook is a shell command attached to a secret that re-fetches the
secret value when it becomes stale. Refresh hooks are configured per-secret
using flags on swamp vault put.
Configuration
| Flag | Description |
|---|---|
--refresh-from |
Shell command to run to produce a new secret value (stdout is captured) |
--refresh-ttl |
Duration before the secret is considered stale (e.g. 30s, 50m, 1h) |
--clear-refresh |
Remove the refresh hook from this secret |
Both --refresh-from and --refresh-ttl are required together. Providing one
without the other is an error.
$ swamp vault put my-vault GCP_TOKEN \
--refresh-from "gcloud auth print-access-token" \
--refresh-ttl 50m
Enter value for GCP_TOKEN: ********INF vault·put Stored secret "GCP_TOKEN" in vault "my-vault"To remove a refresh hook from a secret:
$ swamp vault put my-vault GCP_TOKEN --clear-refresh --yesINF vault·put Stored secret "GCP_TOKEN" in vault "my-vault"Refresh-on-Read Behavior
When vault.get() or swamp vault read-secret accesses a secret with a refresh
hook, the vault checks whether the TTL has elapsed since the last refresh. If
the secret is stale, the refresh command runs and the returned value replaces
the stored secret.
INF vaults Refreshed secret "GCP_TOKEN" in vault "my-vault"Failure Semantics
When the refresh command fails (non-zero exit code), the stale value is returned and a warning is logged. The secret is not updated.
WRN vaults Refresh command failed for "GCP_TOKEN" in vault "my-vault": "". Returning stale value.Inspecting Refresh Hooks
swamp vault inspect shows refresh hook configuration alongside annotations.
$ swamp vault inspect my-vault GCP_TOKENINF vault·inspect Metadata for "GCP_TOKEN" in vault "my-vault":
INF vault·inspect size: 13 chars (13 bytes)
INF vault·inspect annotation: (none)
INF vault·inspect refresh:
INF vault·inspect command: "echo refreshed-token"
INF vault·inspect ttl: "50m"
INF vault·inspect last refreshed: neverAfter a successful refresh:
INF vault·inspect Metadata for "GCP_TOKEN" in vault "my-vault":
INF vault·inspect size: 15 chars (15 bytes)
INF vault·inspect annotation: (none)
INF vault·inspect refresh:
INF vault·inspect command: "echo refreshed-token"
INF vault·inspect ttl: "1s"
INF vault·inspect last refreshed: "2026-06-08T21:41:01.488Z"JSON output includes size, capability, and refresh hook fields:
$ swamp vault inspect my-vault GCP_TOKEN --json{
"vaultName": "my-vault",
"secretKey": "GCP_TOKEN",
"vaultType": "local_encryption",
"sizeBytes": 13,
"sizeChars": 13,
"valueType": "string",
"supportsAnnotations": true,
"hasAnnotation": false,
"annotation": null,
"supportsRefreshHooks": true,
"hasRefreshHook": true,
"refreshHook": {
"command": "echo refreshed-token",
"ttlMs": 3000000,
"ttl": "50m",
"lastRefreshedAt": null
}
}When no refresh hook is configured, hasRefreshHook is false and
refreshHook is null.
CEL Integration
Vault secrets are accessed in CEL expressions using vault.get() and
vault.put().
vault.get(vault_name, key)
Retrieves a secret value from the named vault.
globalArguments:
secret: "${{ vault.get('my-secrets', 'api-key') }}"Supported quoting styles for vault name and key arguments:
# Single quotes
secret: "${{ vault.get('my-secrets', 'api-key') }}"
# Double quotes
secret: "${{ vault.get(\"my-secrets\", \"api-key\") }}"
# Backticks
secret: "${{ vault.get(`my-secrets`, `api-key`) }}"
# Unquoted (when names contain only alphanumeric characters and hyphens)
secret: "${{ vault.get(my-secrets, api-key) }}"vault.put(vault_name, key, value)
Stores a value in the named vault. Used in expressions where a computed value needs to be persisted as a secret.
globalArguments:
store: "${{ vault.put('my-secrets', 'generated-key', self.data.attributes.token) }}"Expression Context
vault.get() expressions are deferred to runtime — they are not evaluated
during definition validation or persistence. This means the vault and key must
exist when the behavior method or workflow step executes.
The vault context variable is available in:
globalArgumentsvalues in behavior definitionsmethods.*.argumentsvalues in behavior definitions- Step task
inputsin workflows forEach.inexpressions in workflows
See the CEL Expressions reference for the full expression language.
Secrets and Inputs
A vault.get() value is redacted as *** in step data and run logs, and
evaluated definitions keep the expression rather than the value. Pass a vault
reference through an input, the environment, or stdin, never the secret value:
| What is passed | Secret persisted in cleartext |
|---|---|
A secret value via swamp workflow run --input, --input-file, --stdin |
Yes: run record, history index, child runs, evaluated definitions, data of behaviors that record arguments |
A secret value via swamp workflow resume --input, --input-file, --stdin |
Yes: the same places once merged; the run record's resumeInputs lists only key names |
A secret value via ${{ env.* }} |
Yes: data of behaviors that record arguments (not redacted) |
${{ vault.get(...) }} via swamp behavior run --input, --input-file, --stdin |
No: resolved at run time and redacted |
A vault or key name via ${{ env.* }}, as in vault.get(prod-secrets, env.KEY_NAME) |
No |
A vault or key name via a workflow input, as in vault.get(inputs.vaultName, inputs.secretKey) |
No |
A ${{ vault.get(...) }} passed as a swamp workflow run or resume input
value is not evaluated; it reaches the step as literal text (see
Where Expressions Are Evaluated).
Pass the vault name and key as inputs and resolve with
vault.get(inputs.vaultName, inputs.secretKey). See
Input values are inert and recorded.
If a secret has been passed through an input, rotate it. swamp run gc removes
finished runs older than its cutoff (never suspended or running ones), and
swamp workflow delete removes a workflow's runs. Neither removes evaluated
definitions under .swamp/definitions-evaluated/ or behavior data; remove those
with swamp data delete.
Behavior Run Inputs Are Evaluated
swamp behavior run treats its --input, --input-file, and --stdin values
as operator-written. A ${{ vault.get(...) }} or ${{ env.* }} inside them is
resolved, and the method receives the secret in plaintext. Vault values are
redacted in stored output; env values are not. To pass untrusted text (an issue
body, a ticket, a scraped page) as a behavior run input, add
--literal-inputs:
every input then reaches the method verbatim. A workflow input or behavior data
is never evaluated either, even when it matches an expression the author wrote.
See
Evaluated and Inert Sources.
Environment Variable Mounting
When a command/shell step's arguments contain a secret, Swamp passes it to the
executing process as an environment variable rather than in the command string.
This applies to two sources:
vault.get()expressions.- Sensitive fields read through
data.latest(),steps.<name>.outputsormodel.<name>.resource.
Inside an expression a sensitive field is plaintext, so guards, comparisons and
size() see the real value. Only the result spliced into the command is
replaced.
Resolution Process
- During expression evaluation, each
vault.get()call, and each sensitive field value inside an expression's result, is replaced with an internal sentinel token. - Before shell execution, each sentinel is replaced with a shell variable
reference (
${__SWAMP_VAULT_N}), whereNis a sequential index. - The raw secret values are passed as environment variables to the process.
- The shell expands the variables after command parsing, so metacharacters in secret values are never interpreted as shell syntax.
Quoting Behavior
Sentinel replacement is quoting-aware. Each occurrence of a vault.get()
reference takes its form from the quote context it sits in:
| Context | Replacement |
|---|---|
| Inside double quotes | ${__SWAMP_VAULT_N} |
| Outside double quotes | "${__SWAMP_VAULT_N}" |
For example, given a behavior argument:
methods:
execute:
arguments:
run: "curl -H \"Authorization: Bearer ${{ vault.get('my-secrets', 'api-key') }}\" https://api.example.com"The shell receives:
curl -H "Authorization: Bearer ${__SWAMP_VAULT_0}" https://api.example.com
# with env: __SWAMP_VAULT_0=sk-test-12345A secret containing shell metacharacters like pass;rm -rf / is treated as
literal data because variable expansion happens after command parsing.
A secret used more than once is placed at each use, and every use shares one variable:
methods:
execute:
arguments:
run: |
echo 'literal ${{ vault.get('secrets', 'tok') }}'
printf '[%s]\n' "${{ vault.get('secrets', 'tok') }}"
printf '[%s]\n' ${{ vault.get('secrets', 'tok') }}The shell receives:
echo 'literal "${__SWAMP_VAULT_0}"'
printf '[%s]\n' "${__SWAMP_VAULT_0}"
printf '[%s]\n' "${__SWAMP_VAULT_0}"The shell does not expand a reference inside single quotes, so a single-quoted use prints the literal reference. If any use is single-quoted, the step warns:
Warning Vault secret expression inside single quotes will not be expanded by the shell — use double quotes insteadIn PowerShell, placement is per occurrence too: a vault.get() reference
becomes $env:__SWAMP_VAULT_N inside double quotes and "$env:__SWAMP_VAULT_N"
elsewhere.
Sensitive Field Placement
A sensitive field value is placed per occurrence, by the shell context it sits in. A value that appears several times shares one variable.
| Context | Replacement |
|---|---|
| Unquoted | "${__SWAMP_VAULT_N}" |
| Inside double quotes | ${__SWAMP_VAULT_N} |
Here-document body, unquoted delimiter (<<EOF) |
${__SWAMP_VAULT_N} |
| Inside single quotes | '"${__SWAMP_VAULT_N}"' (closes and reopens) |
Inside ANSI-C quotes ($'...') |
'"${__SWAMP_VAULT_N}"$' (reopens as $'...') |
Here-document body, quoted delimiter (<<'EOF', <<"EOF", <<\EOF) |
The value itself, with a warning |
An unquoted value is wrapped in double quotes, so it is not word-split or glob-expanded.
Given a definition that reads a sensitive token field in each context:
methods:
execute:
arguments:
run: |
a=${{ data.latest('writer', 'main').attributes.token }}
b="x ${{ data.latest('writer', 'main').attributes.token }}"
c='x ${{ data.latest('writer', 'main').attributes.token }}'
d=$'x ${{ data.latest('writer', 'main').attributes.token }}\t'
cat <<EOF
${{ data.latest('writer', 'main').attributes.token }}
EOFThe shell receives:
a="${__SWAMP_VAULT_0}"
b="x ${__SWAMP_VAULT_0}"
c='x '"${__SWAMP_VAULT_0}"''
d=$'x '"${__SWAMP_VAULT_0}"$'\t'
cat <<EOF
${__SWAMP_VAULT_0}
EOFNo environment reference expands inside a quoted-delimiter here-document, so the
value stays in the command line and the step emits a
sensitive_value_in_command_line warning:
Warning Sensitive data value inside a quoted here-document or a single-quoted PowerShell string is passed on the command line — use an unquoted here-document delimiter or double quotes insteadIn PowerShell a sensitive field value becomes $env:__SWAMP_VAULT_N inside
double quotes and "$env:__SWAMP_VAULT_N" elsewhere. Inside a single-quoted
string it stays in the command line, with the same warning.
Limits
- The variable keeps the value out of the command line Swamp spawns. A program
the command starts with the value as an argument (
sh -c,ssh) receives it in its own command line. - Sensitive field values shorter than three characters are not tracked, as with output redaction, and reach the command line as written.
- A step dispatched to a remote worker is sent resolved arguments, so the value is in the command line on the worker, for both sources.
- A
manual_approvalprompt that reads a sensitive field shows the value to the approver.
Sensitive Field Processing
Behavior types can mark resource output fields as sensitive. When a method
produces sensitive output, the values are automatically stored in a vault and
replaced with vault.get() reference expressions before persistence.
Vault Resolution Order
The vault used for storing sensitive fields is resolved in this order:
- Field-level
vaultNamefrom schema metadata (set by extension authors via.meta({ sensitive: true, vaultName: "..." })in TypeScript) - Spec-level
vaultNamefrom theResourceOutputSpec— set by extension authors in TypeScript, overridden per-definition via theresources.<specName>.vaultNamefield in the definition YAML, or overridden per workflow step viadataOutputOverridesvaultName. For the same spec, the step'svaultNamewins over the definition'sresourcesoverride. - Repo-level
defaultVaultfrom.swamp.yaml— applies to behavior execution,swamp serveOAuth token storage, and device authorization token storage - The first user vault, ordered by vault type, then by vault id — the order
swamp vault listshows
Each level takes precedence over the levels below it. If sensitive fields exist but no vault is resolved at any level, an error is thrown.
The step 4 fallback is not creation order, and not alphabetical by vault name:
vault ids are generated UUIDs, and an @-scoped vault type such as
@swamp/1password sorts before local_encryption. In a repository with more
than one vault, set
defaultVault so
sensitive outputs that name no vault go where you expect.
swamp doctor vaults warns when a
behavior's sensitive outputs rely on this fallback.
Sensitive Method Arguments
Sensitive method arguments (parameters marked as sensitive in the behavior type
schema) are the user's responsibility to vault. The runtime enforces that
sensitive argument values must be ${{ vault.get(...) }} expressions, but does
not automatically route argument values into a vault — unlike sensitive resource
output fields, which are stored automatically.
Pre-flight Vault Validation
Before a mutating method executes, Swamp checks whether the behavior has sensitive resource output fields. If it does and no vault is configured, the method is rejected immediately — before any method logic runs.
Error: Model "my-keypair" has sensitive resource output fields but no vault is
configured. Create a vault before running this method:
swamp vault create <type> <name>This is distinct from the persist-time error that occurs during data writing. The pre-flight check prevents the method from starting at all, avoiding partial execution that would fail when attempting to store sensitive output.
A repository created by swamp repo init has a default vault, so this error
only occurs when the repository was initialized with --no-vault, predates the
default vault, or had its vaults deleted. Create one and make it the default:
swamp vault create local_encryption defaultThen set defaultVault: default in .swamp.yaml.
Use swamp doctor vaults to scan all
definitions for this condition before running methods.
Auto-Generated Vault Keys
When no custom vault key is specified, the key is generated from the behavior context:
{sanitized-model-type}-{model-id}-{method-name}-{field-path}Sanitization rules: @ is removed, / and \ are replaced with -.
Example: behavior type @user/aws/ec2-keypair, field KeyMaterial becomes
user-aws-ec2-keypair-{id}-createKeyPair-KeyMaterial.
Persisted Format
After processing, the persisted data contains vault references instead of raw values:
keyMaterial: "${{ vault.get('my-secrets', 'user-aws-ec2-keypair-abc-createKeyPair-KeyMaterial') }}"Non-string values are JSON-stringified before vault storage.
Values Read Through Expressions
A sensitive field read through data.latest(), steps.<name>.outputs or
model.<name>.resource resolves to its plaintext value inside the expression.
Swamp tracks each resolved value with the vault and key it came from, and never
writes it in plaintext:
| Where the value lands | What is stored or shown |
|---|---|
A command/shell step's command |
An environment variable; see Environment Variable Mounting |
| Evaluated definition and workflow caches, run records and their index, deferred binding snapshots, auto-created definitions | The ${{ vault.get('<vault>', '<key>') }} reference |
swamp behavior evaluate and swamp workflow evaluate output, report arguments, completed-event arguments |
*** |
| Tags, labels and data-name suffixes | sensitive-<vault>.<key> |
forEach step names |
sensitive-<index>, as in each-sensitive-0 |
Each file that holds such a reference lists it in writtenReferences and
carries a sensitiveFormat marker. An evaluated definition cache looks like:
methods:
execute:
arguments:
run: >
a=${{ vault.get('secrets',
'demo-secret-writer-8f0f6531-3341-4437-adcd-7c1d086355d8-write-creds-main-token')
}}
authoredExpressions:
- '${{ data.latest(''writer'', ''main'').attributes.token }}'
writtenReferences:
- path:
- methods
- execute
- arguments
- run
occurrence: 0
vaultName: secrets
key: >-
demo-secret-writer-8f0f6531-3341-4437-adcd-7c1d086355d8-write-creds-main-token
encoding: raw
dataOrigin: false
sensitiveFormat: 1--last-evaluated and swamp workflow resume restore only the references
listed in writtenReferences. Reference-shaped text that arrives as data is
left as text.
Because tags and labels hold a placeholder, a tag filter on a sensitive value does not match. An auto-created definition holds the reference, so a later run reads the vault directly.
--last-evaluated refuses a cache with no sensitiveFormat marker whose
expressions read data.* or steps.*, since an older Swamp wrote such caches
with the value in plaintext. swamp behavior run reports it with the
evaluated_cache_may_hold_secrets error code:
Error: The evaluated definition cached for "consume" was written by an older swamp and may hold sensitive values in plaintext, so it is not replayed. Run the method without --last-evaluated to evaluate it again; that rewrites the cache.Running once without --last-evaluated rewrites the cache in the current
format.
Vault Migration
The swamp vault migrate command migrates a vault to a different backend type
in-place. The vault name stays the same, so all existing vault.get() and
vault.put() expressions continue to work without modification.
How It Works
- Lists all secret keys in the source vault
- Copies each secret value from the current backend to a new provider instance
- Updates the vault configuration file to point to the new backend type (new config is written before the old one is removed)
- The vault name is preserved — all existing
vault.get('name', 'key')expressions resolve identically after migration
Behavior
| Condition | Result |
|---|---|
| Source and target type match | Rejected with an error |
| Target type is a legacy name | Rejected with a message naming the scoped equivalent (e.g. @swamp/aws-sm) |
| Copy fails mid-migration | Source vault is unchanged; no configuration is swapped |
| Config delete fails after swap | Orphaned config file remains; vault operates on the new backend |
--dry-run flag |
Reports secret count and type change; makes no changes |
| Required config field missing | Named with a re-run example, or prompted for in a terminal (details) |
Secrets are copied to the target backend before the configuration file is updated. The new configuration file is written before the old one is removed.
CLI Commands
swamp vault create <type> [name]
Create a new vault configuration. Like every Swamp command, it needs a
swamp-club.com account. Non-local vault types (extension-provided) additionally
require a collective token to carry the vault:* scope. local_encryption
needs no scope.
| Option | Description |
|---|---|
--config |
Provider configuration as JSON. Missing required fields are named, or prompted for in a terminal — see Missing config fields |
--audit-reads |
Enable read-access audit trail for this vault |
--repo-dir |
Repository directory (default .) |
--server |
Run against a remote swamp serve instance (env: SWAMP_SERVE_URL) |
--token |
Server token in <name>.<secret> format; only with --server (overrides stored credentials and SWAMP_SERVER_TOKEN) |
$ swamp vault create local_encryption my-secrets17:00:00 INF vault·create Created vault: "my-secrets" ("Local Encryption")$ swamp vault create local_encryption infra \
--config '{"ssh_key_path":"~/.ssh/id_ed25519"}'17:00:36 INF vault·create Created vault: "infra" ("Local Encryption")ssh_key_path and the other key-source fields can only be
set by a local invocation. With --server, leave them out and the server's
defaults apply.
Missing config fields
When the vault type requires fields that --config does not supply,
vault create and vault migrate name each one with its description and print
the command to re-run. This works for a type that is not installed yet:
$ swamp vault create @swamp/1password op --json{
"error": "Invalid config for vault type '@swamp/1password': missing required field 'op_vault' (The 1Password vault to use). Re-run with: swamp vault create @swamp/1password op --config '{\"op_vault\":\"<op_vault>\"}'"
}A field of the wrong type and an unknown key are explained the same way:
{
"error": "Invalid config for vault type '@swamp/1password':\n - field 'op_vault' expects string, got number\n - unknown field 'bogus'; accepted fields: op_vault, op_account\nRe-run with a corrected --config."
}In an interactive terminal, the command prompts for each missing required string field instead of failing:
$ swamp vault create @swamp/1password op
@swamp/1password needs these config fields. Press Enter to leave one unset.
op_vault (The 1Password vault to use): Privatevault·create Created vault: "op" ("1Password")To fail instead of prompting, pass --json. vault migrate also fails without
prompting under --yes, and both commands do under --server.
swamp vault put <vault_name> <key> [value]
Store a secret in a vault.
| Option | Description |
|---|---|
-y, --yes |
Skip confirmation when overwriting (--force / -f also accepted) |
--refresh-from |
Command to run to refresh the secret value when the TTL expires |
--refresh-ttl |
How long before the secret is considered stale (e.g. 50m, 1h, 30s) |
--clear-refresh |
Remove the refresh hook from this secret |
--label |
Attach a metadata label to the secret (key=value, repeatable). Stored as annotation labels — same as vault annotate --label |
--server |
Run against a remote swamp serve instance (env: SWAMP_SERVE_URL) |
--token |
Server token in <name>.<secret> format; only with --server (overrides stored credentials and SWAMP_SERVER_TOKEN) |
$ swamp vault put my-secrets api-key
Enter value for api-key: ********17:00:04 INF vault·put Stored secret "api-key" in vault "my-secrets"The third-argument (<key> <value>) and inline (KEY=VALUE) forms expose the
secret in shell history, process tables, and system logs. Prefer the interactive
prompt or piped stdin for real credentials.
See Refresh Hooks for details on --refresh-from,
--refresh-ttl, and --clear-refresh. See Annotations for
details on --label.
swamp vault read-secret <vault_name> <key>
Read a secret value from a vault.
| Option | Description |
|---|---|
-y, --yes |
Skip confirmation prompt (--force / -f also accepted) |
--json |
Output structured JSON |
--server |
Run against a remote swamp serve instance (env: SWAMP_SERVE_URL) |
--token |
Server token in <name>.<secret> format; only with --server (overrides stored credentials and SWAMP_SERVER_TOKEN) |
In interactive (log) mode, prompts for confirmation before revealing the secret
unless --yes is set. In --json mode, outputs the value directly without
prompting.
$ swamp vault read-secret my-secrets api-key --yes19:53:23 INF vault·read-secret "sk-test-12345"$ swamp vault read-secret my-secrets api-key --json{
"vaultName": "my-secrets",
"secretKey": "api-key",
"vaultType": "local_encryption",
"value": "sk-test-12345"
}swamp vault list-keys <vault_name>
List all secret key names in a vault. Values are not shown.
| Option | Description |
|---|---|
--server |
Run against a remote swamp serve instance (env: SWAMP_SERVE_URL) |
--token |
Server token in <name>.<secret> format; only with --server (overrides stored credentials and SWAMP_SERVER_TOKEN) |
$ swamp vault list-keys my-secrets17:00:29 INF vault·list-keys Vault "my-secrets" ("local_encryption"): 2 key(s)
17:00:29 INF vault·list-keys - "api-key"
17:00:29 INF vault·list-keys - "db-password"swamp vault annotate <vault_name> <key>
Annotate a vault secret with metadata. Attaches provenance metadata (URL, notes,
labels) to an existing secret. Uses merge semantics: only the fields you specify
are updated, existing fields are preserved. Use --clear to remove all
annotations.
| Option | Description |
|---|---|
--url |
URL associated with this secret |
--notes |
Free-text notes about this secret |
--label |
Key=value label (repeatable) |
--remove-label |
Remove a label by key (repeatable) |
--clear |
Remove all annotations from this secret |
--server |
Run against a remote swamp serve instance (env: SWAMP_SERVE_URL) |
--token |
Server token in <name>.<secret> format; only with --server (overrides stored credentials and SWAMP_SERVER_TOKEN) |
$ swamp vault annotate my-secrets api-key \
--url https://console.aws.com/iam \
--notes "Production API key" \
--label env=prod --label team=infraINF vault·annotate Annotated "api-key" in vault "my-secrets" (fields: "url, notes, labels")$ swamp vault annotate my-secrets api-key --clearINF vault·annotate Cleared annotation for "api-key" in vault "my-secrets"swamp vault inspect <vault_name> <key>
Show metadata for a vault secret. Displays size, annotations, refresh hook configuration, and provider capability flags.
| Option | Description |
|---|---|
--json |
Output structured JSON |
--server |
Run against a remote swamp serve instance (env: SWAMP_SERVE_URL) |
--token |
Server token in <name>.<secret> format; only with --server (overrides stored credentials and SWAMP_SERVER_TOKEN) |
$ swamp vault inspect my-secrets api-keyINF vault·inspect Metadata for "api-key" in vault "my-secrets":
INF vault·inspect size: 42 chars (42 bytes)
INF vault·inspect url: "https://console.aws.com/iam"
INF vault·inspect notes: "Production API key"
INF vault·inspect label: "env"="prod"
INF vault·inspect label: "team"="infra"
INF vault·inspect updated: "2026-05-22T23:53:23.906Z"
INF vault·inspect refresh-hook: (none)$ swamp vault inspect my-secrets api-key --json{
"vaultName": "my-secrets",
"secretKey": "api-key",
"vaultType": "local_encryption",
"sizeBytes": 42,
"sizeChars": 42,
"valueType": "string",
"supportsAnnotations": true,
"hasAnnotation": true,
"annotation": {
"updatedAt": "2026-05-22T23:53:23.906Z",
"url": "https://console.aws.com/iam",
"notes": "Production API key",
"labels": {
"env": "prod",
"team": "infra"
}
},
"supportsRefreshHooks": true,
"hasRefreshHook": false,
"refreshHook": null
}swamp vault delete <vault_name> <key>
Delete a secret from a vault. Removes the secret and any associated metadata (annotations, refresh hooks).
Arguments
| Argument | Required | Description |
|---|---|---|
vault_name |
Yes | Name of the vault |
key |
Yes | Secret key to delete |
Flags
| Option | Description |
|---|---|
--repo-dir |
Repository directory (default .) |
-y, --yes |
Skip confirmation prompt and ignore missing keys (--force / -f also accepted) |
--server |
Run against a remote swamp serve instance (env: SWAMP_SERVE_URL) |
--token |
Server token in <name>.<secret> format; only with --server (overrides stored credentials and SWAMP_SERVER_TOKEN) |
Use --yes to skip the confirmation prompt and to treat non-existent keys as a
no-op. When using --server, the confirmation prompt is not available — use
--yes to suppress the error on missing keys.
$ swamp vault delete my-secrets api-keyDelete secret "api-key" from vault "my-secrets"? [y/N] y
INF vault·delete Deleted secret "api-key" from vault "my-secrets"$ swamp vault delete my-secrets api-key --yesINF vault·delete Deleted secret "api-key" from vault "my-secrets"swamp vault migrate <vault_name>
Migrate a vault to a different backend type. Copies all secrets from the current backend to a new one, then updates the vault configuration. The vault name stays the same, so all existing vault references continue to work.
| Option | Description |
|---|---|
--to-type |
Target vault type (required; always required with --server) |
--config |
Provider-specific configuration as JSON. Missing required fields are named, or prompted for in a terminal — see Missing config fields |
--dry-run |
Preview migration without making changes (not available with --server) |
-y, --yes |
Skip confirmation prompt (--force / -f also accepted) |
--repo-dir |
Repository directory (default .) |
--server |
Run against a remote swamp serve instance (env: SWAMP_SERVE_URL). Requires --to-type; --dry-run is not available. |
--token |
Server token in <name>.<secret> format; only with --server (overrides stored credentials and SWAMP_SERVER_TOKEN). |
A local_encryption target migrated over --server gets the server's default
key source; --config cannot set ssh_key_path,
auto_generate, key_file or base_dir to anything else. Run the migration
locally on the serve host to choose them.
$ swamp vault migrate my-secrets --to-type @swamp/aws-sm \
--config '{"region":"us-east-1"}' --dry-run14:23:09 INF vault·migrate Vault ""my-secrets"" ("local_encryption") has 2 secret(s).
14:23:09 INF vault·migrate Target: "AWS Secrets Manager" ("@swamp/aws-sm")
14:23:09 INF vault·migrate Dry run — no changes made.$ swamp vault migrate my-secrets --to-type @swamp/aws-sm \
--config '{"region":"us-east-1"}' --dry-run --json{
"dryRun": true,
"vaultName": "my-secrets",
"currentType": "local_encryption",
"currentTypeName": "Local Encryption",
"targetType": "@swamp/aws-sm",
"targetTypeName": "AWS Secrets Manager",
"secretCount": 2
}swamp vault search [query]
Search for vaults in the repository. Opens an interactive picker when run in a
terminal, or returns JSON with --json.
| Option | Description |
|---|---|
--server |
Run against a remote swamp serve instance (env: SWAMP_SERVE_URL) |
--token |
Server token in <name>.<secret> format; only with --server (overrides stored credentials and SWAMP_SERVER_TOKEN) |
$ swamp vault search --json{
"query": "",
"results": [
{
"id": "3870058a-0008-48d3-84c1-031405ce9ebf",
"name": "infra",
"type": "local_encryption",
"createdAt": "2026-04-07T17:00:36.582Z"
},
{
"id": "0dd4c691-0b7b-4fae-9741-31e96a4efc69",
"name": "my-secrets",
"type": "local_encryption",
"createdAt": "2026-04-07T16:59:59.039Z"
}
]
}swamp vault get <vault_name_or_id>
Show details of a vault configuration (type, config, storage path). Does not
reveal secret values — use
read-secret to retrieve a secret.
| Option | Description |
|---|---|
--type |
Vault type (narrows search) |
--server |
Run against a remote swamp serve instance (env: SWAMP_SERVE_URL) |
--token |
Server token in <name>.<secret> format; only with --server (overrides stored credentials and SWAMP_SERVER_TOKEN) |
$ swamp vault get my-secrets{
"id": "0dd4c691-0b7b-4fae-9741-31e96a4efc69",
"name": "my-secrets",
"type": "local_encryption",
"config": {
"auto_generate": true
},
"createdAt": "2026-04-07T16:59:59.039Z",
"storagePath": "vaults/local_encryption/0dd4c691-0b7b-4fae-9741-31e96a4efc69.yaml"
}swamp vault describe <vault_name_or_id>
Show detailed vault information including configuration.
| Option | Description |
|---|---|
--type |
Vault type (narrows search) |
--server |
Run against a remote swamp serve instance (env: SWAMP_SERVE_URL) |
--token |
Server token in <name>.<secret> format; only with --server (overrides stored credentials and SWAMP_SERVER_TOKEN) |
swamp vault edit [vault_name_or_id]
Open a vault configuration file in an editor, or replace it with YAML piped on
stdin. Opens an interactive picker when called without arguments and without
--server.
| Option | Description |
|---|---|
--type |
Vault type (narrows search) |
--repo-dir |
Repository directory (default .) |
--server |
Run against a remote swamp serve instance (env: SWAMP_SERVE_URL). Takes the new YAML on stdin only, and needs the vault name. |
--token |
Server token in <name>.<secret> format; only with --server (overrides stored credentials and SWAMP_SERVER_TOKEN) |
$ cat my-vault.yaml | swamp vault edit my-vaultA stdin update keeps the vault's id and createdAt, and cannot change its
type. Under
managedConfig, the
edit applies to the vault config in the datastore config tier.
With --server, stdin is the only edit mode, because the editor cannot run on
the server. Running it with nothing piped fails:
'swamp vault edit' with --server needs the new YAML piped on stdin, because the editor cannot run on the server. For example: cat edited.yaml | swamp vault edit <name> --server <url>Over --server, a local_encryption vault must keep its stored
key-source fields. A vault whose stored YAML no longer
parses can be repaired remotely only with the server's defaults for them.
Renaming a vault
Changing name renames the vault, whether in the editor, on stdin, or over
--server. For a local_encryption vault, the stored secrets and the
auto-generated key move to .swamp/secrets/local_encryption/{new-name}/:
Renamed vault "old-secrets" to "my-secrets"; its stored secrets moved with it- The rename is refused when secrets are already stored under the new name:
Secrets are already stored under vault name 'my-secrets'. Remove them before renaming vault 'old-secrets' to 'my-secrets'. - In the editor, Swamp sees the rename only when the editor waits for the file to close: terminal editors (vim, nano, emacs, helix and others), VS Code, Zed and Sublime Text. A refused rename in the editor is changed back, and your other edits are kept.
- Repairing a vault whose previous YAML did not parse never moves secrets.
- Definitions and workflows still reference the vault by name. Update every
vault.get('old-secrets', ...)to the new name, or those lookups fail.
swamp vault type search [query]
Search for available vault types (built-in and from extensions).
| Option | Description |
|---|---|
--server |
Run against a remote swamp serve instance (env: SWAMP_SERVE_URL) |
--token |
Server token in <name>.<secret> format; only with --server (overrides stored credentials and SWAMP_SERVER_TOKEN) |
$ swamp vault type search --json{
"query": "",
"results": [
{
"type": "local_encryption",
"name": "Local Encryption",
"description": "Store encrypted secrets in local files using AES-GCM encryption. Uses SSH private key or auto-generated key for encryption."
}
]
}swamp vault audit-trail
View the vault audit trail. Write operations (put, delete, annotate) are
always recorded; read operations (get) are recorded when the vault has
auditReads: true.
| Option | Description |
|---|---|
--vault |
Filter by vault name |
--key |
Filter by secret key |
--action |
Filter by action: get, put, delete, annotate |
--since |
Start date, e.g. 2026-07-01 or ISO 8601 (default: 7 days ago) |
--until |
End date, e.g. 2026-07-01 or ISO 8601 (default: now) |
--limit |
Maximum entries to return (default: 100) |
--server |
Run against a remote swamp serve instance (env: SWAMP_SERVE_URL) |
--token |
Server token in <name>.<secret> format; only with --server (overrides stored credentials and SWAMP_SERVER_TOKEN) |
$ swamp vault audit-trailINF vault·audit-trail 3 vault operation(s):
INF vault·audit-trail "2026-07-13T20:53:58.112Z" put "my-secrets" ("local_encryption")/"api-key" by "cli:vault-put-secret"
INF vault·audit-trail "2026-07-13T20:54:21.586Z" get "my-secrets" ("local_encryption")/"api-key" by "cli:vault-read-secret"
INF vault·audit-trail "2026-07-13T20:55:31.276Z" get "my-secrets" ("local_encryption")/"api-key" by "expression:vault-resolve"$ swamp vault audit-trail --vault my-secrets --action getINF vault·audit-trail 2 vault operation(s):
INF vault·audit-trail "2026-07-13T20:54:21.586Z" get "my-secrets" ("local_encryption")/"api-key" by "cli:vault-read-secret"
INF vault·audit-trail "2026-07-13T20:55:31.276Z" get "my-secrets" ("local_encryption")/"api-key" by "expression:vault-resolve"$ swamp vault audit-trail --since 2026-07-14INF vault·audit-trail No vault audit entries found.Vault Audit Trail
The vault audit trail records operations against vault secrets to
.swamp/audit/vault-audit-YYYY-MM-DD.jsonl. Write operations (put, delete,
annotate) are always recorded when an audit repository exists. Read operations
(get) are recorded only for vaults with auditReads: true.
Old audit files using the vault-reads- prefix are still read for backwards
compatibility.
Each line is a JSON object with the following fields:
| Field | Type | Description |
|---|---|---|
timestamp |
string | ISO 8601 timestamp of the operation (Z suffix) |
action |
string | Operation type: get, put, delete, or annotate |
vaultName |
string | Name of the vault |
vaultType |
string | Vault provider type (e.g. local_encryption) |
secretKey |
string | Key that was operated on |
callerContext |
string | What triggered the operation (see values below) |
callerContext values
| Value | Trigger |
|---|---|
cli:vault-read-secret |
swamp vault read-secret |
expression:vault-resolve |
vault.get() CEL expression during behavior or workflow execution |
The set of callerContext values is extensible — new integration points and
write operations may introduce additional values.
Example entry
{
"timestamp": "2026-07-13T20:54:21.586Z",
"action": "get",
"vaultName": "my-secrets",
"vaultType": "local_encryption",
"secretKey": "api-key",
"callerContext": "cli:vault-read-secret"
}Complete Example
Create a vault, store secrets, and reference them in a behavior definition:
swamp vault create local_encryption prod-secrets
echo "$API_KEY" | swamp vault put prod-secrets api-key
echo "$DB_PASSWORD" | swamp vault put prod-secrets db-passwordReference the secrets in a behavior definition:
type: command/shell
typeVersion: 2026.02.09.1
id: 53274ce2-c390-412b-93ac-48b205a13f4e
name: api-caller
version: 1
globalArguments:
api_key: "${{ vault.get('prod-secrets', 'api-key') }}"
methods:
execute:
arguments:
run: "curl -H \"Authorization: Bearer ${{ vault.get('prod-secrets', 'api-key') }}\" https://api.example.com"
env:
DB_PASSWORD: "${{ vault.get('prod-secrets', 'db-password') }}"At execution time, vault.get() expressions resolve to the stored secret
values. Shell commands receive secrets as environment variables — the raw values
never appear in the command string.
Remote execution
When a workflow step runs on a remote worker, vault secrets are resolved on the orchestrator before the step is dispatched. The resolved values are transmitted to the worker over the encrypted data plane for the duration of the dispatch. Workers never receive vault credentials or connection details — they see only the resolved plaintext for secrets referenced by the current step. See the Security reference for the full isolation model.