Multi-Vault

Registering a single machine with multiple SikkerKey vaults.

A machine can be registered with multiple SikkerKey vaults. Each vault gets its own directory, keypair, and identity file under ~/.sikkerkey/vaults/. The SDK resolves which identity to use based on the vault ID you provide.

Directory Structure

After registering with two vaults, the filesystem looks like:

~/.sikkerkey/
  vaults/
    vault_a1b2c3d4e5f6g7h8/
      identity.json
      private.pem
    vault_x9y8z7w6v5u4t3s2/
      identity.json
      private.pem

Each vault has its own Ed25519 keypair. The machine authenticates as a different machine identity to each vault. There is no shared key material between vaults.

Registering with Multiple Vaults

Run the bootstrap command once per vault. Each vault owner generates their own token from their dashboard:

# Register with first vault
curl -sSL https://api.sikkerkey.com/v1/bootstrap/TOKEN_A | sh

# Register with second vault
curl -sSL https://api.sikkerkey.com/v1/bootstrap/TOKEN_B | sh

Each registration creates a separate directory. They do not interfere with each other.

SDK Usage

All five SDKs (Kotlin, Go, Python, Node, .NET) implement the same identity resolution and accept the same arguments. Only the constructor name differs by language idiom.

Specifying the Vault

When a machine is registered with multiple vaults, pass the vault ID at construction:

val production = SikkerKey("vault_a1b2c3d4e5f6g7h8")
val staging = SikkerKey("vault_x9y8z7w6v5u4t3s2")

val prodDb = production.getSecret("sk_db_prod")
val stagingDb = staging.getSecret("sk_db_staging")
production, _ := sikkerkey.New("vault_a1b2c3d4e5f6g7h8")
staging, _ := sikkerkey.New("vault_x9y8z7w6v5u4t3s2")

prodDb, _ := production.GetSecret("sk_db_prod")
stagingDb, _ := staging.GetSecret("sk_db_staging")
production = SikkerKey("vault_a1b2c3d4e5f6g7h8")
staging = SikkerKey("vault_x9y8z7w6v5u4t3s2")

prod_db = production.get_secret("sk_db_prod")
staging_db = staging.get_secret("sk_db_staging")
const production = SikkerKey.create("vault_a1b2c3d4e5f6g7h8")
const staging = SikkerKey.create("vault_x9y8z7w6v5u4t3s2")

const prodDb = await production.getSecret("sk_db_prod")
const stagingDb = await staging.getSecret("sk_db_staging")
var production = SikkerKeyClient.Create("vault_a1b2c3d4e5f6g7h8");
var staging = SikkerKeyClient.Create("vault_x9y8z7w6v5u4t3s2");

var prodDb = await production.GetSecretAsync("sk_db_prod");
var stagingDb = await staging.GetSecretAsync("sk_db_staging");

The SDK looks up ~/.sikkerkey/vaults/{vaultId}/identity.json for the specified vault ID. If the vault ID does not start with vault_, the SDK prepends it automatically.

Single Vault (Auto-detect)

If only one vault is registered, the SDK auto-detects it. You don't need to specify the vault ID:

val sk = SikkerKey()
val secret = sk.getSecret("sk_a1b2c3d4e5")
sk, _ := sikkerkey.NewAutoDetect()
// or equivalently: sk, _ := sikkerkey.New("")
secret, _ := sk.GetSecret("sk_a1b2c3d4e5")
sk = SikkerKey()
secret = sk.get_secret("sk_a1b2c3d4e5")
const sk = SikkerKey.create()
const secret = await sk.getSecret("sk_a1b2c3d4e5")
var sk = SikkerKeyClient.Create();
var secret = await sk.GetSecretAsync("sk_a1b2c3d4e5");

If multiple vaults are registered and you do not specify which one, the SDK throws an error listing the available vaults.

Explicit Path

You can point any SDK directly at an identity file. Anything starting with / or containing identity.json is treated as a path, bypassing the vault-ID lookup:

val sk = SikkerKey("/home/deploy/.sikkerkey/vaults/vault_a1b2c3d4e5f6g7h8/identity.json")
sk, _ := sikkerkey.New("/home/deploy/.sikkerkey/vaults/vault_a1b2c3d4e5f6g7h8/identity.json")
var sk = SikkerKeyClient.Create("/home/deploy/.sikkerkey/vaults/vault_a1b2c3d4e5f6g7h8/identity.json");

The same pattern works in Python and Node. Each SDK detects the path on the first character.

Environment Variable

Set SIKKERKEY_IDENTITY to the path of an identity file:

export SIKKERKEY_IDENTITY=$HOME/.sikkerkey/vaults/vault_a1b2c3d4e5f6g7h8/identity.json

The SDK checks this environment variable if no vault ID or path is provided.

You can also set SIKKERKEY_HOME to change the base directory (defaults to ~/.sikkerkey). All five SDKs and the CLI honor both variables.

Identity Resolution Order

The SDK resolves the identity file in this order:

  1. Explicit path: if the argument starts with / or contains identity.json, it is treated as a file path
  2. Vault ID: looks up ~/.sikkerkey/vaults/{vaultId}/identity.json
  3. Environment variable: reads SIKKERKEY_IDENTITY
  4. Auto-detect: if exactly one vault directory exists under ~/.sikkerkey/vaults/, uses it

If none of these resolve to a valid identity file, the SDK throws an error with the paths it checked.

Listing Registered Vaults

Each SDK exposes a static method that scans ~/.sikkerkey/vaults/ for directories containing an identity.json file and returns the vault IDs:

val vaults = SikkerKey.listVaults()
// ["vault_a1b2c3d4e5f6g7h8", "vault_x9y8z7w6v5u4t3s2"]
vaults := sikkerkey.ListVaults()
vaults = SikkerKey.list_vaults()
const vaults = SikkerKey.listVaults()
var vaults = SikkerKeyClient.ListVaults();

CLI Usage

The sikkerkey CLI handles multi-vault setups through connect. Each registered vault is reachable by its full ID or by an alias you set once and reuse.

# List every vault registered on this machine
sikkerkey connect --list

# Switch the CLI's default vault
sikkerkey connect vault_a1b2c3d4e5f6g7h8

# Set a friendly alias that future commands can use
sikkerkey connect vault_a1b2c3d4e5f6g7h8 --alias prod
sikkerkey connect vault_x9y8z7w6v5u4t3s2 --alias staging

# Show which vault is currently selected
sikkerkey connect

# Clear the default vault
sikkerkey connect --clear

Aliases are stored in ~/.sikkerkey/cli_global.json and resolved automatically by every subsequent CLI command:

sikkerkey unlock proj_xyz789 --alias prod
sikkerkey list secrets
sikkerkey get sk_a1b2c3d4e5

If only one vault is registered, the CLI auto-selects it and the connect step is unnecessary. See CLI overview for the complete command reference.

Aliases are a CLI-only concept. The SDKs identify vaults by their vault_* ID; if you want to use a friendly name in code, store the mapping in your application config and pass the underlying ID to the SDK constructor.

Isolation Between Vaults

Each vault registration is fully independent:

  • Separate Ed25519 keypair (different private key, different public key)
  • Separate machine ID
  • Separate approval status (must be approved in each vault)
  • Separate project memberships and secret grants
  • Separate encryption keys (each vault's projects have their own)

Compromising one vault's machine identity has zero effect on other vaults. The private keys are different and the machine IDs are different.