# Managing host credentials

For a pinned repository task using selected env/file credentials, see [Private tracked runs](/lakeshore/private-runs.md), including CLI/Python submission, recovery and current availability.

For an agent-led checkout and credential provisioning workflow, see
[Setting Up Remote Agent Environment](/lakeshore/hosts/remote-agent-environment.md).

**Available in CLI 0.13.0 and Python 0.10.0 or later.** The hosted vault uses managed AWS KMS without requiring your own cloud account. Entries, scoped access keys, selected SSH/TOTP import and TOTP generation are released; [installed-client production checks](https://github.com/dreamlake-ai/dreamlake-workspace/pull/307) passed. The web Vault supports entry and scoped-key management, expiry and write recovery. See the [runtime note](/dev/notes/vault-runtime) for deployment evidence and remaining acceptance gaps.

Host enrollment saving is available in CLI **0.16.0**, including its npm wrapper, and Python **0.13.0** with a compatible backend. [Hosted staging acceptance](https://github.com/dreamlake-ai/dreamlake-workspace/blob/main/infra/vault/hosted-host-saving-acceptance.md) passed; production host saving is a separate rollout. Saving follows enrollment, so saving failure does not undo an online host. The published npm 0.16 wrapper passed target/jump password-descriptor saving; upgrade from the affected 0.15 wrapper. Published CLI 0.18.0/Python 0.15.0 key rotation has [hosted staging acceptance](https://github.com/dreamlake-ai/lakeshore-examples/pull/34). Password lifecycle, broader remote failure coverage and production KMS/retention obligations remain separately tracked in [issue #241](https://github.com/dreamlake-ai/dreamlake-workspace/issues/241).

## Guides and deliverables

- **[Runnable CLI/Python examples](https://github.com/dreamlake-ai/lakeshore-examples/blob/main/15-vault/README.md):** checkout setup, entry operations, scoped keys and credential imports.
- **[Isolated server](https://github.com/dreamlake-ai/lakeshore-examples/blob/main/15-vault/server/README.md):** explicit backend/Mongo/KMS configuration for synthetic accounts.
- **[Live smoke runner](https://github.com/dreamlake-ai/lakeshore-examples/blob/main/15-vault/live_smoke.py)** and **[results](https://github.com/dreamlake-ai/lakeshore-examples/blob/main/15-vault/LIVE_RESULTS.md):** bos14 AWS KMS roundtrip, client suites, failure handling and cleanup evidence.
- **[Terraform test setup](https://github.com/dreamlake-ai/lakeshore-examples/blob/main/15-vault/infra/README.md):** isolated plan/apply/test/cleanup commands. Includes reviewed plan/apply, readiness and guarded cleanup. Historical acceptance retained billable infrastructure; check the current state before reuse.
- **[Kubernetes fixture](https://github.com/dreamlake-ai/lakeshore-examples/blob/main/15-vault/kubernetes/README.md)** and **[Pod Identity IAM setup](https://github.com/dreamlake-ai/lakeshore-examples/blob/main/15-vault/kubernetes/iam/README.md):** merged in [examples PR #14](https://github.com/dreamlake-ai/lakeshore-examples/pull/14); see the [EKS results](https://github.com/dreamlake-ai/lakeshore-examples/blob/main/15-vault/kubernetes/RESULTS.md).
- **[Vault design and remaining work](https://github.com/dreamlake-ai/dreamlake-workspace/blob/main/design/rfcs/vault.md):** host credential selection, sync, lifecycle and KMS policy requirements.

The linked results record synthetic-account tests on bos14 and a Terraform-managed EKS cluster with real AWS KMS, client regression and failure checks, and fixture cleanup. These historical runs are separate from hosted production acceptance and do not establish live GCP readiness.

Personal entries live under your owner name, such as `ge/`. Account permissions control access; no device pairing is required. Customer KMS and BYOC backend setup are optional operator concerns.
## Inspect the owner-only policy tree

The policy tree shows metadata for your owner prefix, including the governing
key and migration state, without reading secret values. Sign in as the owner;
scoped access keys cannot use this view.

**Python 0.17.0 is published on PyPI; CLI 0.22.0 publication is incomplete.**
Use the reviewed CLI candidate or published Python package with a backend that
supports the tree route. A missing route is an error, not an empty tree. See the
[runtime development note](/dev/notes/vault-runtime) for compatibility and acceptance evidence.

**CLI**

```shell
# Explicit personal owner prefix; a scoped key cannot use this policy view.
dreamlake vault list -p ge/training --tree
# One metadata page for scripts; ordinary vault list JSON is unchanged.
dreamlake vault list -p ge/training --tree --to-json --limit 100
```

**Python**

```python
from dreamlake import RemoteClient

# Uses the securely saved login for this API origin.
client = RemoteClient("https://api.dreamlake.ai")
page = client.vault.tree(prefix="ge/training", limit=100)
if page["nextCursor"]:
    next_page = client.vault.tree(
        prefix="ge/training", limit=100, cursor=page["nextCursor"],
    )
```

In the terminal use arrows to select, collapse or expand, `n` for the next page,
and `q` to quit. Pages are fetched explicitly and do not form a frozen whole-tree
snapshot. JSON preserves detail truncated by terminal width. Prefixes and entries
remain distinct; governing policy/key metadata does not prove which key encrypted
historical ciphertext. Unknown policy or migration state remains unknown.
This view does not reveal values, change policies, provision credentials or preview
cross-boundary moves. `--include-deleted` is unsupported.

[Runnable tree example and local HTTP/Mongo/PTY evidence](https://github.com/dreamlake-ai/dreamlake-cli/tree/07bae34322612475a4093012375a4039f90e336d/examples/vault-tree),
[CLI artifact receipt](https://github.com/dreamlake-ai/dreamlake-cli/tree/07bae34322612475a4093012375a4039f90e336d/docs/releases/0.22.0)
and [Python archive receipt](https://github.com/fortyfive-labs/dreamlake/tree/d86e0be2cdb0d3777ed50345a548352683c29ab3/docs/releases/0.17.0)
record exact tested versions. [Python publication](https://github.com/dreamlake-ai/dreamlake-workspace/blob/87e2caf7760d9f0f6be4126fd4aced7a17f0f87e/infra/vault/production-tree-20260915/pypi-reconciliation.json) was separately verified against both frozen archives; a [fresh no-cache PyPI install and production metadata-only tree check](https://github.com/dreamlake-ai/dreamlake-workspace/blob/cc23e87431be8335c204b560cc18edae8f79534e/infra/vault/production-tree-20260915/python-public-verification.json) passed. The initial read-only install check failed without an established cause; its successful retry is recorded separately. CLI npm/native publication remains unproven.

## Save one file in the web Vault

Open your profile's **Vault** tab, choose **New entry** (or **Replace value**),
keep **String**, and choose one UTF-8 file. Selection reads that file locally.
Review its basename, byte count, destination and expiry, then explicitly save.
Manual input stays masked until **Reveal secret input**. No adjacent SSH keys,
aliases or jump-host credentials are discovered, and saving does not enroll a
host or install a remote key.

The file must be nonempty, valid UTF-8, no larger than 64 KiB, and contain no
NUL or unsupported control bytes. Its JSON-encoded value must also fit 64 KiB.
BOM, CRLF and trailing whitespace remain unchanged; revealing alone does not
rewrite them. Editing visible text uses browser newline handling. Closing,
leaving, changing accounts or canceling discards pending reads. Unknown writes
use the existing receipt recovery flow without automatically retrying uploads.
Web export/download and full remote setup review remain separate work.

[Staging and production evidence](https://github.com/dreamlake-ai/dreamlake-workspace/blob/main/infra/vault/hosted-ui-files-acceptance.md)
records desktop/mobile saving and cleanup. Equivalent explicit client operations:

**CLI**

```shell
# The selected CLI input here is UTF-8 without a BOM.
dreamlake vault add -n ge/remote-agent/key \
  --stdin --file-name id_ed25519 < ./id_ed25519
dreamlake vault show -n ge/remote-agent/key
```

**Python**

```python
from pathlib import Path

source = Path("./id_ed25519")
# read_bytes avoids newline normalization; no implicit prompt or secret logging.
entry = client.vault.add(
    name="ge/remote-agent/key",
    value=source.read_bytes().decode("utf-8"), file_name=source.name,
)
metadata = client.vault.show(name="ge/remote-agent/key")
```

## Read and compose

`list` returns metadata; `get` explicitly returns plaintext values. Python uses an authenticated `client` and never prompts automatically. Keep retrieved values out of logs and agent transcripts.

**CLI**

```shell
dreamlake vault list -p ge/training
dreamlake vault get -n ge/training/storage --to-json
dreamlake vault get -n ge/training/storage.access-key

# Compose entries using their saved environment names.
dreamlake vault -p ge/training get \
  -n ssh-key -n tracking-token --to-envs
```

**Python**

```python
entries = client.vault.list(prefix="ge/training")
storage = client.vault.get(name="ge/training/storage")
access_key = client.vault.get(name="ge/training/storage.access-key")
exports = client.vault.get(
    prefix="ge/training", name=["ssh-key", "tracking-token"], to_envs=True,
)
```

Use `path.field` for field selection and repeated `-n` for composition. `-p`/`--prefix` abbreviates paths for one invocation and can appear before or after subcommands. Leading `/` bypasses abbreviation; conflicting prefixes fail. `--to-json` and `--to-envs` are exclusive. Resolve the whole request before returning values; failures emit no partial secret output.

An entry can save output metadata:

```json
{
  "name": "ge/training/ssh-key",
  "type": "string",
  "keyName": "ssh_private_key",
  "env": "SSH_PRIVATE_KEY",
  "fileName": "id_ed25519",
  "value": "<illustrative secret; encrypted at rest>"
}
```

Ordinary secrets are strings or named string fields. `totp` adds code-generation behavior; reading a seed and generating a code are separate operations. CLI 0.16/Python 0.13 support [HOTP](/dev/plans/vault-hotp) with a compatible backend; hosted staging acceptance and production rollout are separate. `keyName` names a value in composed JSON, `env` names a single-value entry's environment variable, and `fileName` is a suggested basename for explicit file export. It never writes a file automatically.

For env output, an explicit alias such as `-n TOKEN=ge/tracking.value` wins. A selected field uses its normalized field name; a whole scalar entry uses saved `env`, then its normalized basename. Reading a whole map exports its field names, not the entry-level `env`. `--env-prefix` changes output names only. Reject invalid names/collisions rather than silently renaming them. Structured entries use field mappings, not one ambiguous entry-level env name.

## Supply values to a program

`--to-envs` emits quoted POSIX shell exports. It cannot change the parent shell itself. Check retrieval before evaluating output and use a subshell when variables should live only for the job. Do not enable shell tracing around secrets.

**CLI**

```shell
(
  exports=$(dreamlake vault get -n ge/tracking-token --to-envs) || exit
  eval "$exports"
  unset exports
  python train.py
)

# When the consumer accepts stdin; use a shell supporting pipefail.
set -o pipefail
dreamlake vault get -n ge/tracking-token | client-tool --token-stdin

# For an argv-only consumer, check lookup failure first.
token=$(dreamlake vault get -n ge/tracking-token) || exit
legacy-client --token "$token"
unset token
```

**Python**

```python
import os
import subprocess

token = client.vault.get(name="ge/tracking-token")
subprocess.run(
    ["python", "train.py"],
    env={**os.environ, "TRACKING_TOKEN": token}, check=True,
)
subprocess.run(["client-tool", "--token-stdin"], input=token, text=True, check=True)
subprocess.run(["legacy-client", "--token", token], check=True)
```

Consumer programs here are illustrative. Argv may be visible to process inspection; stdin is preferable when supported. Command substitution strips trailing newlines, so use JSON/stdin or Python for exact multiline values. A receiving process can copy or log secrets; delivery is not a secrecy boundary against that process.

## Save values and import selected TOTP entries

`add` uses a masked prompt on a TTY; noninteractive callers provide stdin explicitly. Python receives the securely collected value and never prompts. `show` returns metadata only. Host enrollment can save selected credentials; see [Enrollment](/lakeshore/hosts/enroll.md). Saving and remote key installation are separate operations.

**CLI**

```shell
# Collect a value through a masked TTY prompt, never as an argument.
dreamlake vault add -n ge/bos14-login
dreamlake vault show -n ge/bos14-login

# Preview only: use an explicit canonical store path.
dreamlake vault import --pass-otp -p ge --dry-run --store /absolute/pass-store
```

**Python**

```python
# Caller supplies securely collected secret; no implicit prompts.
client.vault.add(name="ge/bos14-login", value=secret)
metadata = client.vault.show(name="ge/bos14-login")
preview = client.vault.import_entries(
    source="pass-otp",
    store="/absolute/pass-store", prefix="ge", dry_run=True,
)
```

Preview uses noninteractive GPG and an explicit canonical store path. It decrypts locally but uploads nothing, excludes adjacent passwords, and never generates codes or advances HOTP counters. Paths must not traverse symlinks.

Choose canonical pass paths without `.gpg` from the preview. Only selected OTP registrations are uploaded; adjacent passwords are excluded. TOTP registrations can generate codes; HOTP registrations are inactive until DreamLake explicitly takes counter ownership. On a TTY, review and confirm the upload. Explicit `--select` supplies consent for noninteractive use; Python requires selection and never prompts.

**CLI**

```shell
dreamlake vault import --pass-otp -p ge --store /absolute/pass-store \
  --select accounts/example

dreamlake vault otp -n ge/accounts/example
```

**Python**

```python
result = client.vault.import_entries(
    source="pass-otp", prefix="ge", store="/absolute/pass-store",
    select=["accounts/example"],
)
code = client.vault.otp(name="ge/accounts/example")
```

OTP generation requires owner authentication; scoped keys cannot generate codes. Treat generated codes as secrets. Import does not overwrite existing OTP entries. If an upload outcome is unknown, stop and inspect it; do not blindly repeat the import.

For HOTP, stop every other generator before activation. Import is inactive by default; inspect the entry revision before activating it. The CLI example below uses revision `1` for a newly imported entry; substitute the actual inspected revision. Python uses the returned revision directly. Activation does not generate a code or update the local pass store.

**CLI**

```shell
dreamlake vault import --pass-otp -p ge --store /absolute/pass-store \
  --select accounts/hotp-example
dreamlake vault show -n ge/accounts/hotp-example
dreamlake vault otp -n ge/accounts/hotp-example \
  --activate --counter-owner dreamlake --if-match 1

mkdir -m 700 -p ./otp-requests
dreamlake vault otp -n ge/accounts/hotp-example \
  --request-file ./otp-requests/hotp-example-001.json
```

**Python**

```python
client.vault.import_entries(
    source="pass-otp", prefix="ge", store="/absolute/pass-store",
    select=["accounts/hotp-example"],
)
metadata = client.vault.show("ge/accounts/hotp-example")
client.vault.activate_otp(
    "ge/accounts/hotp-example", counter_owner="dreamlake", if_match=metadata["revision"],
)
from pathlib import Path
Path("otp-requests").mkdir(mode=0o700, exist_ok=True)
code = client.vault.otp(
    "ge/accounts/hotp-example",
    request_file="./otp-requests/hotp-example-001.json",
)
```

Run these examples in a writable, user-owned directory. The `otp-requests` directory must be private (mode 0700), including when it already exists. Each new HOTP issuance uses a new private request file. After an uncertain response, reuse the **same file** to recover that issuance; never substitute a fresh file. The durable intent contains identity and revision metadata, not the seed or code. Recovery expires after 24 hours; a missing or expired receipt does not prove that no code was issued. Initial activation supports SHA1 with at least a 128-bit seed; unsupported algorithms remain inactive. See the [HOTP ownership and recovery reference](https://cli.dreamlake.ai/vault-operations). These commands are supported by published CLI 0.20.1 and Python 0.16.1; they require the corresponding server capabilities.

<span id="ssh-sync-select-entries-before-uploading--proposed" />

<span id="ssh-sync-select-entries-before-uploading" />

### SSH import: select entries before uploading

Run from an authenticated interactive terminal:

```shell
dreamlake vault import --ssh -p ge/remote-agent
```

The checklist starts empty. Select profiles and private keys separately, then
review the server and vault destinations before confirming upload. Selecting a
profile does not select its key or jump-host entries. Empty selection or
cancellation before upload writes nothing; cancellation during upload preserves
completed entries and stops further uploads.

Preview discovery without reading private keys or contacting the vault:

```shell
dreamlake vault import --ssh -p ge/remote-agent --dry-run --json
```

For noninteractive use, pass the item IDs shown by discovery explicitly.
Python uses explicit selection and never opens a checklist:

**CLI**

```shell
dreamlake vault import --ssh -p ge/remote-agent \
  --select profile:dev-box --select key:dev-box:1 --json
```

**Python**

```python
preview = client.vault.import_entries(source="ssh", prefix="ge/remote-agent", dry_run=True)
result = client.vault.import_entries(
    source="ssh", prefix="ge/remote-agent",
    select=["profile:dev-box", "key:dev-box:1"],
)
```

Use `--config` for a specific SSH config file. Discovery does not expand `Include`
files. Compatibility aliases remain `vault ssh sync` and `vault pass sync --otp`.

Unknown writes are reconciled only within the same bounded retry call. Do not blindly start a new import after an unknown outcome.

Host enrollment and explicit password saving are available in the published clients; see the [enrollment guide](/lakeshore/hosts/enroll.md). Remote password mutation, rollback and destination revocation require separate acceptance; a saved credential or a revoked Vault key does not prove access was removed from the remote host. Enrollment must succeed independently of saving; interactive saving defaults to `[y/N]`, and quiet mode never grants consent.

## Access keys and retirement

Access keys authenticate scripts to selected vault entries; they are not SSH credentials or agent objects. Reuse appropriate keys instead of creating permanent entries per worker. Renewal is policy-checked, and one-time means one atomic successful retrieval request. Expired/revoked keys cannot renew themselves; one-time keys are nonrenewable.

**CLI**

```shell
dreamlake vault keys create -n ge/training/storage --ttl 1h --renewable \
  --request-id training-key-1 --token-file ./training-vault.key
dreamlake vault keys create -n ge/bootstrap --ttl 10m --one-time \
  --request-id bootstrap-key-1 --token-file ./bootstrap-vault.key

dreamlake vault delete -n ge/bos14-login
dreamlake vault restore -n ge/bos14-login
```

**Python**

```python
# Results redact bearer material in repr; provision .token securely once.
key = client.vault.keys.create(name="ge/training/storage", ttl="1h", renewable=True,
    request_id="training-key-1")
once = client.vault.keys.create(name="ge/bootstrap", ttl="10m", one_time=True,
    request_id="bootstrap-key-1")
client.vault.delete(name="ge/bos14-login")
client.vault.restore(name="ge/bos14-login")
```

Token files must be new and are created with mode 0600. Retain the request ID when retrying uncertain issuance: replay returns metadata without re-revealing the token. Revoke and replace a key whose token was lost. Public GC commands and remote `destroy` remain proposed.

Deletion sets `deleteAt`, blocks new reads, and retains encrypted data until `purgeAt`. Restore during retention does not renew expiry or reinstall a revoked host key. Internal purge rechecks lifecycle state before hard deletion. Scheduled cleanup of terminal access-key metadata is deployed; it does not purge entry ciphertext or revoke remote host access. Scheduled entry/binding cleanup has [isolated EKS acceptance](https://github.com/dreamlake-ai/lakeshore-examples/pull/38). The [hosted retention fixture](https://github.com/dreamlake-ai/lakeshore-examples/pull/47) retains its captured October 15 deadlines; hosted physical purge and backup coverage remain unproven. Intentionally unbound entries are not garbage.

Keep four lifetimes distinct: host access, vault availability, account/client cache, and retained storage. Vault expiry cannot invalidate a copied static password. Rotate by verifying new access before retiring old access. The proposed future `destroy` flow would combine remote revocation with soft retirement and later purge, keeping unreachable hosts pending; it is not an available command. Never claim deletion recalls downloaded secrets or ends existing SSH sessions.

## Web management and uncertain writes

In the personal Vault page, create or replace entries, reveal/hide values, set or
remove UTC expiry, and confirm retirement or restore. Scoped-key controls support
selected-field issuance, renewal and confirmed revocation. Revealed entry values
clear after 30 seconds or at expiry, whichever is first. Expiry does not retire
an entry or revoke a copied remote credential.

If a create/replacement response is lost, **Entry write recovery** retains the
request ID across reload. **Check result** reads the historical receipt;
**Open current entry metadata** shows later changes separately. Forgetting an ID
only removes the browser reference. A missing receipt does not prove a write
failed. CLI/Python can look up a retained request ID without revealing values:

**CLI**

```shell
dreamlake vault write-status --request-id saved-write-id
```

**Python**

```python
receipt = client.vault.write_status(request_id="saved-write-id")
```

Use the original ID from the uncertain write. Do not start a new write to test
whether the old one committed. See [recovery behavior and hosted evidence](/dev/notes/vault-runtime#2026-09-13--entry-write-recovery).

## Prefix policies and agent behavior

Prefix KMS management and migration have implementation and bounded live cloud evidence; see the [Prefix KMS note](/dev/notes/vault-prefix-kms) for verified scope and remaining hosted/operator acceptance, and the [operator setup reference](https://github.com/dreamlake-ai/dreamlake-workspace/blob/main/design/rfcs/vault-cloud-setup.md). Ordinary hosted users use managed KMS without cloud setup.

When using this guide as a skill, check installed capabilities and the target endpoint first. Keep secrets out of transcripts. Report provisioning, login verification, enrollment, saving and remote cleanup separately; do not infer credential export or removal of existing access from a setup request. Report unsupported operations rather than simulating them with plaintext files.
