# tsafe — Explicit runtime authority for secrets

> **`tsafe exec --contract <name> -- <command>`** — named **authority contract** in `.tsafe.yml` / `.tsafe.json`; **`tsafe audit` / `tsafe audit --explain`** for receipts.

**Docs:** [quick-start](docs/quick-start.md) · [golden paths](docs/golden-paths.md) (incl. **coding agents** §6) · [export-and-exec](docs/features/export-and-exec.md) · [cli-reference](docs/cli-reference.md) · [security](docs/features/security.md)

Most teams still do some version of this: keep passwords in `.env`, copy them from a password manager into a terminal, export a few secrets into the shell, forget what is still live, then hope nothing leaks into the wrong process, shell history, CI log, or git diff.

tsafe is better because it replaces that ambient mess with one explicit flow: secrets stay encrypted at rest, `tsafe set` prompts securely for values, and `tsafe exec` gives one process exactly the secrets it is authorized to receive (via a contract or, as a narrow escape hatch, flags — see the reference). **Operator visibility:** `tsafe audit` and `tsafe audit --explain` answer “what happened?”; flag details and JSON in **[docs/cli-reference.md](docs/cli-reference.md)**.

## 30-second use

If you use `.env` today, the shortest mental model is this:

```bash
tsafe init
tsafe set DATABASE_URL
tsafe exec -- docker compose up
```

- `tsafe init` creates your encrypted local vault
- `tsafe set` stores a secret in the vault and securely prompts for the value
- `tsafe exec -- ...` runs one command with **every non-alias secret in the vault** injected into that process. Parent ambient credentials are stripped first; for true "least authority" use a contract or `--keys` / `--ns` (below).

**Repeatable / team workflows:** define a contract in `.tsafe.yml` and run `tsafe exec --contract <name> -- ...` (see [export-and-exec](docs/features/export-and-exec.md#authority-contracts) and [quick-start](docs/quick-start.md)). **Pre-flight:** `tsafe exec --plan` / `--dry-run` for “what would run” before you execute (same reference and [cli-reference](docs/cli-reference.md)).

That is the whole product: encrypted at rest, no plaintext `.env` file, no ambient shell leak, audit receipt written locally.

tsafe is a local-first secret manager for the problem behind most `.env` workflows: secrets leak through ambient shell state, copied plaintext files, and inherited parent credentials. It keeps secrets encrypted at rest, injects them only into the process you intend to run, and records what happened in an append-only audit log.

**Authority model in one line:** with an authority contract or `--keys` / `--ns`, a process receives exactly the secrets it is authorized to receive — no ambient access, no leaked parent credentials, no silent empty-string injections. **Without one of those flags**, `tsafe exec` injects every non-alias secret from the active vault into the child env (parent ambient credentials are still stripped; the dangerous-name guard is still on). See [authority contracts](docs/features/export-and-exec.md#authority-contracts) for the recommended pattern.

## Problems tsafe solves

- Ambient credential leaks: parent shell credentials like `GITHUB_TOKEN`, `AZURE_CLIENT_SECRET`, or `VAULT_TOKEN` reach child processes you never meant to authorize.
- Plaintext secret sprawl: `.env` files, copied values, and shell exports leave secrets on disk, in shell history, and in git accidents.
- Missing authority review: scripts say "run the app with secrets" without declaring which process should receive which keys.
- No operator receipts: you cannot answer which secrets were accessed, by which profile, and when, on a developer machine.

If that is the problem, the smallest working loop is still three commands:

```bash
tsafe init
tsafe set STRIPE_API_KEY
tsafe exec -- npm start
```

> The bare `tsafe exec --` form above injects every non-alias secret in the vault. That is the right onboarding shape (one secret, one command), but for repeatable scripts and CI use a named **authority contract** (`tsafe exec --contract <name> -- …`) so the authorisation set is declared, not implicit.

For repeatable workflows, use a named **authority contract** (`tsafe exec --contract <name> -- ...`); example shape and pre-flight are in **[docs/features/export-and-exec.md](docs/features/export-and-exec.md)** and **[docs/quick-start.md](docs/quick-start.md)** (avoid duplicating long YAML here so docs stay the source of truth).

## Default Core Profile

This is the current **default-on core** product story and the safest mental model for operators and release notes:

- `tsafe exec --contract <name> -- <command>` — recommended default for scripts and CI; contracts bundle profile, namespace, allowed/required keys, and trust posture into a single reusable, auditable surface
- `tsafe exec -- <any command>` — ad-hoc injection; escape-hatch flags (`--ns`, `--keys`, `--require`, `--mode`) can **narrow** a contract but are secondary to named policy — see [export-and-exec](docs/features/export-and-exec.md) and [cli-reference](docs/cli-reference.md)
- MCP/editor wrappers are a **thin consumer pattern** over `tsafe exec` (and sometimes `tsafe agent unlock`), not a shipped `tsafe mcp` product surface
- `tsafe agent unlock` — local session unlock for repeated commands; sets `TSAFE_AGENT_SOCK` so inherited `tsafe` calls skip password prompts for the TTL when the install channel actually includes the companion `tsafe-agent` binary
- Encrypted at rest (XChaCha20-Poly1305 + Argon2id) — no plaintext `.env` files to leak or commit
- Multi-profile vaults (dev/staging/prod) — profile is a security boundary, not just organization
- Append-only audit per profile: `tsafe audit`, `tsafe audit --explain`, and JSON via `--explain --json` (see [cli-reference](docs/cli-reference.md)); `tsafe doctor` for health checks
- Snapshots/restore, plain `.env` import, secret generation, TOTP/QR, and age-backed team-vault collaboration core
- Interactive TUI, biometric/keyring unlock, and Azure Key Vault pull (`tsafe kv-pull`) as the current default-on core provider path
- `tsafe explain exec|export|namespaces|agent|contracts|pull|vault-recovery` — in-terminal docs for authority concepts

## Gated Non-Core Features

These surfaces are implemented or in active repo scope, but they should be treated as **optional / gated non-core** rather than part of the default product tier:

- Additional cloud/provider pulls beyond AKV: AWS Secrets Manager/SSM, GCP Secret Manager, HashiCorp Vault, and 1Password
- Multi-source pull orchestration, extended password-manager/browser CSV import, OTS / one-time sharing, and git-helper convenience surfaces
- Browser extension and native-host flows, SSH key-management surfaces, plugins, and OpenTelemetry

Some repo/docs areas also describe **planned-only / post-core** surfaces such as VS Code, system tray, and hosted/service-style collaboration depth. Those are not part of the default release story and should not be read as active core features.

**Drop-in workflow integration:** your Dockerfiles, Makefiles, scripts, and CI pipelines stay unchanged. You wrap the invocation with `tsafe exec --contract … --` (preferred) or `tsafe exec --`.

---

## Two-minute quickstart

**Install:** every supported path (shell installer, GitHub Releases assets, Homebrew tap / Scoop / WinGet / AUR / Nix, MSI or portable ZIP where published, `cargo install` only when the matching crates.io dependency graph for that release is actually live, local **`just install`**) is spelled out in **[docs/quick-start.md](docs/quick-start.md)**. **Artifacts and naming** use `tsafe-<version>-default-core-full-<target>` when a tagged GitHub release channel actually exists for that line; tracked manifests align with **[packaging/README.md](packaging/README.md)** — use those docs for what ships on a given tag; do not assume a package URL or crates.io install path until that release matches it.

For the current frozen core-only release boundary, a truthful `cargo install tsafe-cli` claim requires crates.io to already contain the exact compiled standalone `tsafe` graph for the named release shape you are claiming. In practice, `default-core-cli` and `default-core-cli+agent` require the standalone CLI graph (`tsafe-cli`, `tsafe-core`, `tsafe-azure`), while `default-core-cli+tui` and `default-core-full` also require `tsafe-tui`. If that standalone graph is not live yet for the release line, use the tagged release archives/packages or a local workspace install instead of advertising `cargo install`.

A standalone `tsafe` install is enough for normal `exec` usage, but agent-backed local-session reuse still requires a separately shipped **`tsafe-agent`** install, and browser/native-host workflows require a separately shipped non-core **`tsafe-nativehost`** install. `cargo install tsafe-cli` alone should not be described as covering those companion runtimes or any broader provider stack.

```bash
tsafe init
tsafe set DATABASE_URL
tsafe exec -- docker compose up
# Recommended once you add a contract to .tsafe.yml:
# tsafe exec --contract <name> -- docker compose up
```

> **That's it.** The command you actually care about gets the secrets it needs in its environment. No `.env` files, no export juggling. The vault is encrypted at rest and access is audit-logged.

`tsafe set DATABASE_URL` prompts securely for the secret value, so you do not need to put the value on the same command line.

**Contracts** are the recommended default for scripts, CI, and team-shared commands: an authority ceiling you can narrow per invocation but never widen. Full examples, edge cases, and pre-flight (`--plan` / `--dry-run`) live in **[docs/features/export-and-exec.md](docs/features/export-and-exec.md#authority-contracts)** and **[docs/quick-start.md](docs/quick-start.md)**.

Not sure whether to use `exec`, `get`, or `export`? See [docs/features/exec-vs-get.md](docs/features/exec-vs-get.md).

## Where files live

tsafe now splits durable vault data, config, and local receipts across the platform directories exposed by `ProjectDirs`. On Linux that means XDG-style defaults instead of one shared app-data root.

| Artifact | Default location | Notes |
|---|---|---|
| Vault files | data dir `/vaults/<profile>.vault` | Encrypted vault per profile |
| Audit receipts | state dir `/audit/<profile>.audit.jsonl` | Append-only local receipts for vault access and exec activity |
| Global config | config dir `/config.json` | Default profile and persisted exec settings |
| TUI debug log | state dir `/tui-debug.log` | Written only when `TSAFE_TUI_DEBUG=1` |

Examples:

- macOS: data `~/Library/Application Support/tsafe/...`, config `~/Library/Application Support/tsafe/config.json`
- Linux: data `~/.local/share/tsafe/...`, config `~/.config/tsafe/config.json`, state `~/.local/state/tsafe/...`
- Windows: platform data/config/state defaults resolved by `ProjectDirs` for `tsafe`

If `TSAFE_VAULT_DIR` is set, that directory becomes the vault root, its parent holds `config.json`, and runtime receipts/state move under `<parent>/state/`.

See [docs/features/storage-paths.md](docs/features/storage-paths.md) for the full path matrix and override behavior.

---

## Table of Contents

- [Problems tsafe solves](#problems-tsafe-solves)
- [Two-minute quickstart](#two-minute-quickstart) · [Full install guide](docs/quick-start.md)
- [Where files live](#where-files-live)
- [Architecture](#architecture)
- [Repository Structure](#repository-structure)
- [Security](#security)
- [Developer Quick Start](#developer-quick-start)
- [CLI Reference](#cli-reference)
- [Git Integration](#git-integration)
- [Workflows and Automation](#workflows-and-automation)
- [Profiles](#profiles)
- [Browser Extension](#browser-extension)
- [Terminal UI (TUI)](#terminal-ui-tui)
- [External Integrations](#external-integrations)
- [Infrastructure](#infrastructure)
- [Pipelines](#pipelines)
- [Local Development](#local-development)
- [MSI Distribution](#msi-distribution)
- [Documentation](#documentation)

---

## Trust model

tsafe's core design principle is **declared authority over ambient access**. Every secret injection is intentional, scoped, and auditable — not a side effect of a shell variable that happens to be set.

> **Default scope (no contract / `--keys` / `--ns`):** `tsafe exec` injects every non-alias secret in the active vault into the child env. Parent ambient credentials are still stripped via the strip list and dangerous-name vault entries (`LD_PRELOAD`, `NODE_OPTIONS`, …) still abort, but the **vault scope** is the whole vault until you adopt one of the levers below. Use a contract, `--keys`, or `--ns` to narrow.

| Concept | What it does |
|---|---|
| **Profile** | A named, independently encrypted vault. A process can only receive secrets from the profile it is explicitly given (`--profile` or `TSAFE_PROFILE`). Profiles are a security boundary, not just an organisational one. |
| **Authority contract (`--contract`)** | A named policy loaded from the nearest `.tsafe.yml` / `.tsafe.json` that bundles profile, namespace, allowed secrets, required secrets, allowed targets, and trust posture into a single reusable, auditable surface. Flags can narrow a contract but cannot widen it. Prefer contracts in scripts and CI over ad-hoc flag re-composition. |
| **Namespace (`--ns`)** | Scopes injection to one sub-tree of keys. `--ns api` means the child process sees only `api/*` keys, with the prefix stripped. Everything else in the vault is withheld. |
| **Key selection (`--keys`)** | Narrows injection to a named subset. Fails fast if any selected key is missing — no silent empty-string injection. |
| **Require (`--require`)** | Asserts minimum key presence before the child starts. Useful in CI to catch misconfigured vaults before a deployment begins. |
| **Trust mode (`--mode`)** | `hardened` — minimal inherited env, redacted child output, dangerous-name deny. `standard` — compatibility preset. `custom` — persisted per-profile settings. |
| **Strip list** | `tsafe exec` removes parent process credentials (`TSAFE_PASSWORD`, `AZURE_CLIENT_SECRET`, `VAULT_TOKEN`, `GITHUB_TOKEN`, etc.) from the child environment before injecting vault secrets. Parent ambient credentials cannot reach the child. |
| **Audit log** | Every vault open, secret read, and exec invocation is appended to a per-profile JSONL log. Operators use `tsafe audit` and **`tsafe audit --explain`** for readable receipts; JSON consumers use `--explain --json`. Forwarding to a SIEM: `tsafe audit-export`. Details: [docs/cli-reference.md](docs/cli-reference.md). |
| **Session scope** | The agent holds a session token with a configurable idle TTL. Vault access requires a live session — there is no "always-on" ambient access mode. |

For the full trust model and threat analysis, see [docs/features/security.md](docs/features/security.md) and [docs/features/export-and-exec.md](docs/features/export-and-exec.md).

---

## Architecture

The repo is intentionally split into one compiled standalone `tsafe` graph plus
separate companion and gated non-core surfaces:

- The compiled standalone **`tsafe`** install path is the `tsafe-cli` graph
  over `tsafe-core`. In the frozen core-only line that graph includes
  `tsafe-azure`, and it includes `tsafe-tui` only for the named standalone
  shapes that ship the TUI.
- **`tsafe-agent`** is a separate companion binary for local-session reuse
  (`tsafe agent unlock`). A standalone `tsafe` install does not imply it.
- **`tsafe-nativehost`** is a separate non-core companion for browser/native-host
  workflows. It stays outside the core-only baseline unless a broader release
  channel explicitly ships it.
- Additional provider surfaces such as **AWS** and **GCP** remain gated
  non-core additions. They are not part of the baseline standalone `tsafe`
  claim, whether they ship as separate crates or as feature-gated inlined
  modules.

```text
compiled standalone tsafe graph
  tsafe-cli
    -> tsafe-core
    -> tsafe-azure   (core provider path in the frozen core-only line)
    -> tsafe-tui     (only when the named standalone shape includes TUI)

separate companion binary
  tsafe-agent        (required for agent-backed session reuse; not implied by tsafe alone)

separate non-core companion
  tsafe-nativehost   (browser/native-host workflow only when that channel ships it)

gated non-core provider surfaces
  AWS / GCP / ...   (only when a broader channel explicitly enables them)
```

All vault I/O still flows through `tsafe-core`, but install truth is
shape-specific: one `cargo install tsafe-cli` claim covers only the compiled
standalone `tsafe` graph for the named release shape, not the separate
companion binaries or gated non-core provider surfaces.

---

## Repository Structure

```
tsafe/
├── Cargo.toml                      # Rust workspace root (6 crates)
├── justfile                        # Common local tasks (build, test, docs, release)
├── rust-toolchain.toml             # Pinned stable toolchain
├── crates/
│   ├── tsafe-core/              # Vault engine, crypto, audit, snapshots, env, TOTP, gen
│   ├── tsafe-cli/               # tsafe binary (clap CLI, 30+ subcommands)
│   ├── tsafe-tui/               # Interactive terminal UI (ratatui)
│   ├── tsafe-agent/             # Background unlock/session agent
│   ├── tsafe-azure/             # Azure Key Vault integration
│   └── tsafe-nativehost/        # Browser extension native messaging host
├── contracts/
│   ├── events/                     # Event and CloudEvents schemas
│   ├── integrations/               # External integration contracts
│   ├── snap/                       # One-time secret (OTS) HTTP payload contracts
│   ├── vault/                      # Vault file JSON schema (v1) + example
│   └── errors/                     # Error envelope schema + reason codes
├── docs/                           # Guides, feature deep-dives, and generated man pages
│   ├── README.md                   # Documentation hub
│   ├── man/                        # Generated Unix man pages (`tsafe.1`, etc.)
│   └── features/                   # Detailed feature guides
├── extension/                      # TypeScript browser extension (Manifest V3)
├── packaging/                      # Tracked package-manager manifests (Homebrew, Scoop, WinGet, AUR, Nix)
├── scripts/
│   ├── __main__.py                 # Cross-platform task entry point (`python -m scripts ...`)
│   ├── build/                      # Release, extension, and MSI packaging helpers
│   ├── setup/                      # Local tooling/bootstrap helpers
│   └── proget/                     # ProGet publish helpers
├── terraform/                      # Azure Key Vault + Log Analytics IaC
├── .pipelines/                     # Azure DevOps: quality, build-and-publish, deploy
├── docker/                         # Multi-stage build + dev container
├── docker-compose.yml              # Dev environment with cargo-watch
├── .githooks/                      # Pre-commit (fmt) + pre-push (clippy, test, TF)
├── tests/                          # Integration and contract validation
└── assets/                         # Icons (ICO/PNG), MSI license (RTF)
```

---

## Security

| Property | Implementation |
|---|---|
| **Key derivation** | Argon2id — m=64 MiB, t=3, p=4 (OWASP-aligned; configurable 8–128 MiB) |
| **Cipher** | XChaCha20-Poly1305 — 256-bit key, 192-bit random nonce per secret |
| **Key verification** | Encrypted known-plaintext challenge — wrong password fails fast |
| **Memory safety** | `zeroize::ZeroizeOnDrop` on all key material; manual wipe in `Vault::drop` |
| **Vault atomicity** | Write to `.vault.tmp` then atomic `rename` — no partial writes on crash |
| **Audit trail** | Append-only JSONL receipt per profile (under the state `audit/` directory) |
| **Concurrent access** | Advisory file lock (`.vault.lock`); stale lock timeout at 10 min |
| **Auto-healing** | `Vault::open()` restores from latest snapshot if file is missing or corrupt |
| **Snapshot retention** | Encrypted copies under `snapshots/<profile>/` in the vault data dir; keeps last 10 |
| **Browser isolation** | Extension holds only a short-lived session token; all vault I/O stays in native host |
| **HTTPS enforcement** | AKV URLs and OTS (`TSAFE_OTS_BASE_URL`) must use `https://` |

For tracked security findings and their status, see [docs/security/findings.md](docs/security/findings.md).

---

## Installation

Supported formats and channels vary by release (e.g. Windows MSI / portable ZIP; macOS and Linux `.tar.gz` on GitHub Releases; optional package managers; `cargo install` only once the relevant crates are actually published to crates.io for that release line). For the frozen core-only Cargo path, crates.io must contain the exact compiled standalone `tsafe` graph for the release shape you are claiming: `default-core-cli` and `default-core-cli+agent` need `tsafe-cli`, `tsafe-core`, and `tsafe-azure`, while `default-core-cli+tui` and `default-core-full` additionally need `tsafe-tui`. Any `+agent` claim still needs separate `tsafe-agent` publish/install truth, and browser/nativehost or broader provider claims need their own non-core channel proof. **Canonical install matrix** — including shaped `tsafe-<version>-default-core-full-<target>` release archives when a tagged GitHub release channel actually exists, `TSAFE_INSTALL_*` overrides, private repos, **`just install`**, and companion binaries such as **`tsafe-agent`** / **`tsafe-nativehost`** when a release claims those workflows — is **[docs/quick-start.md](docs/quick-start.md)**. **Tracked packaging manifests and tap/bootstrap** notes: **[packaging/README.md](packaging/README.md)**. Use whatever install method matches the artifacts you actually publish for your tag.

---

## Developer Quick Start

### 1. Install tooling (once, for development)

```bash
just setup
# On Windows, follow any printed install-tooling.ps1 guidance for WiX/MSVC.
```

### 2. Install git hooks (once)

```bash
git config core.hooksPath .githooks
```

### 3. Build and run

```bash
cargo build --release --package tsafe-cli
cargo run -p tsafe-cli --bin generate-manpages -- docs/man
# Unix:    ./target/release/tsafe --help
# Windows: .\target\release\tsafe.exe --help
# Repo-local manual: man -l docs/man/tsafe.1
```

Or with Podman/Docker (no local Rust required):

```bash
podman compose up --build
```

### 4. Create your first vault

```powershell
tsafe init                              # create 'default' profile
tsafe set DB_URL "postgres://localhost/mydb"
tsafe set API_KEY --tag env=dev         # prompted securely for value
tsafe list
tsafe exec --dry-run                    # preview env var names only (no subprocess)
tsafe exec -- dotnet run                # inject secrets as env vars
```

### 5. Migrate from .env files

```powershell
tsafe import --from .env.local --overwrite
```

Broader password-manager/browser CSV imports are a gated lane that require a
build or release channel with `pm-import-extended`:

```powershell
tsafe import --from bitwarden --file ~/Downloads/export.csv
tsafe import --from chrome --file ~/Downloads/chrome_passwords.csv
```

For a full walkthrough, see [docs/getting-started.md](docs/getting-started.md).
For the full docs index, see [docs/index.md](docs/index.md).
For Unix manual generation and install notes, see [docs/man/README.md](docs/man/README.md).

---

## CLI Reference

```
tsafe [--profile <name>] <command>
```

The `--profile` flag selects a named vault (default: `default`). Override with the `TSAFE_PROFILE` env var.

### Core Commands

| Command | Description |
|---|---|
| `init` | Create a new encrypted vault for the current profile; on a TTY, optionally set up OS quick unlock (Touch ID / Hello / PIN) or defer with `biometric enable` later |
| `config show`, `config set-backup-vault …` | Global `config.json`: optionally copy each new vault's master password into a hub vault at `profile-passwords/<profile>` |
| `set <KEY> [VALUE]` | Store a secret (prompts securely if value omitted). `--tag KEY=VALUE` to attach metadata, `--overwrite` to skip confirmation |
| `get <KEY>` | Print a secret. `--copy` copies to clipboard and auto-clears after 30 s |
| `delete <KEY>` | Remove a secret (snapshot taken automatically) |
| `list` | List all secret keys. `--tag env=prod` to filter |
| `export` | Print secrets to stdout. `--format env\|dotenv\|powershell\|json\|github-actions`. `--tag` to filter |
| `exec -- <cmd>` | Run a command with secrets as env vars. Propagates exit code and Ctrl-C |
| `import` | Migrate from `.env` files by default. Broader CSV exports such as bitwarden, 1password, lastpass, chrome, edge, or firefox require a build/channel with `pm-import-extended`. `--overwrite` to replace existing |
| `rotate` | Re-encrypt all secrets with a new master password |

### Generation and TOTP

| Command | Description |
|---|---|
| `gen <KEY>` | Generate a CSPRNG random secret. `--length 64 --charset hex --print` |
| `totp add <KEY> <SECRET>` | Store a TOTP seed (base32 or `otpauth://` URI) |
| `totp get <KEY>` | Print current 6-digit TOTP code + seconds remaining |
| `qr <KEY>` | Render a secret as a QR code in the terminal |

### Profiles and Organization

| Command | Description |
|---|---|
| `profile list` | List all vault profiles |
| `profile delete <name>` | Delete a profile vault. `--force` to skip confirmation |
| `diff` | Show key-level changes since last snapshot |
| `compare <profile>` | Highlight missing/mismatched keys between two profiles |
| `pin <KEY>` / `unpin <KEY>` | Pin secrets to the top of lists |
| `alias <TARGET> <ALIAS>` | Create an alias (`tsafe get ALIAS` resolves to TARGET). `--list` to view all |

### External Integrations

| Command | Description |
|---|---|
| `kv-pull` | Pull secrets from Azure Key Vault. `--prefix MYAPP_ --overwrite` |
| `vault-pull` | Pull from HashiCorp Vault KV v2. `--addr`, `--token`, `--mount`, `--prefix` |
| `op-pull <ITEM>` | Pull fields from a 1Password item via the `op` CLI |
| `share-once <KEY>` | Share via a configurable OTS one-time link (`snap` alias). `--ttl` string is server-specific |

### Audit and Diagnostics

| Command | Description |
|---|---|
| `audit` | Show recent audit log entries. `--limit 100` |
| `audit-export` | Export audit log. `--format json\|splunk --output audit.jsonl` |
| `snapshot list` | List local vault snapshots |
| `snapshot restore` | Restore the most recent snapshot |
| `doctor` | Diagnose vault health, env vars, expired secrets, orphaned snapshots |

### Utilities

| Command | Description |
|---|---|
| `git <subcommand> [args]` | Gated non-core git-helper lane: run git with `ADO_PAT` injected as `http.extraHeader` |
| `hook-install` | Install a secret-scanning git pre-commit hook. Auto-detects nearest `.git` |
| `completions <SHELL>` | Print shell completions (powershell, bash, zsh, fish) |
| `browser-profile add\|list\|remove` | Map browser domains to vault profiles |
| `browser-native-host register\|unregister` | Register or remove the browser native messaging host bridge |
| `ui` | Launch the interactive terminal UI |

For the full reference with examples, see [docs/cli-reference.md](docs/cli-reference.md).

---

## Git Integration

`tsafe git` is an implemented **gated non-core** helper lane. When the current
binary and release channel actually include it, it wraps any git command and
injects vault credentials via `http.extraHeader` — no URL mutation, no temp
files, no `$env:` cleanup:

```powershell
# Push to Azure DevOps with ADO_PAT from vault
tsafe -p main git push ado main

# Or use the push script
python scripts/push.py
```

The agent + master-password bridge means zero prompts in your daily workflow
for commands that are actually present in your compiled build. For `tsafe git`,
that still requires the gated git-helper lane:

```powershell
tsafe agent unlock --ttl 8h    # once; copy the printed TSAFE_AGENT_SOCK line into your shell
tsafe exec -- cargo test       # any time
```

See [docs/features/git-integration.md](docs/features/git-integration.md) for the full reference.

---

## Workflows and Automation

tsafe is designed to replace every pattern that involves setting `$env:SECRET = ...` manually:

For repeated local automation, the feature you want is `tsafe agent unlock`, not `tsafe unlock`. `tsafe unlock` only removes a stale `.vault.lock` file after a crash. `tsafe agent unlock` starts the background session agent, prints a `TSAFE_AGENT_SOCK` export line, and every later `tsafe` command that inherits that env var can open the vault without re-prompting for the password.

```powershell
tsafe agent unlock --ttl 8h
# copy the printed line into the parent shell:
#   $env:TSAFE_AGENT_SOCK = "..."

tsafe exec -- cargo test
tsafe exec -- terraform apply
# If the gated git-helper lane is compiled in, this can use the same session:
# tsafe git push ado main
```

That is the right pattern for local scripts, long-lived dev shells, and parent processes that spawn several `tsafe` commands. Headless CI should still set `TSAFE_PASSWORD` from the platform secret store instead of relying on the agent socket.

```powershell
# Run anything with secrets injected
tsafe exec -- dotnet run
tsafe exec -- cargo test
tsafe exec -- docker-compose up
tsafe exec -- terraform apply

# Replace complex credential setup scripts
tsafe exec -- powershell -File ./deploy.ps1
```

See [docs/features/workflows.md](docs/features/workflows.md) for patterns covering:
- Session-based unlock (one password, all day)
- Replacing `$env:` patterns
- CI/CD pipelines with minimal secret surface
- Multi-profile automation

---

## Profiles

Each profile is an independent encrypted vault file under the platform data `vaults/` directory (`~/.local/share/tsafe/` on typical Linux installs).

```powershell
tsafe --profile dev  set DB_URL "postgres://dev-host/mydb"
tsafe --profile prod set DB_URL "postgres://prod-host/mydb"
tsafe compare prod       # show what dev has that prod doesn't
```

Override the vault directory for CI: `$env:TSAFE_VAULT_DIR = "C:\ci\vaults"`

---

## Observability

tsafe uses the `tracing` crate for structured logging. Controlled via the `TSAFE_LOG` environment variable:

```bash
# Disabled by default (zero overhead when unset)
tsafe get DB_PASSWORD

# Enable debug logging to stderr
TSAFE_LOG=debug tsafe get DB_PASSWORD

# Info-level only
TSAFE_LOG=info tsafe exec -- dotnet run

# Per-target granularity (tracing env-filter syntax)
TSAFE_LOG=tsafe_core=debug,tsafe_cli=info tsafe rotate

# JSON lines on stderr (aggregators / CI). Uses span-close records for `#[instrument]`d work (vault, KDF, …).
TSAFE_LOG=info TSAFE_LOG_FORMAT=json tsafe list
```

Key operations are instrumented with timing spans: vault open/save, Argon2id KDF, XChaCha20 encrypt/decrypt, secret set/get, rotation. Auto-heal events (snapshot restore on corruption) are logged at `warn` level.

---

## Browser Extension

tsafe includes a browser extension (Edge, Chrome, Firefox) that autofills credentials without any cloud relay.

This lane is **implemented but gated non-core** in the current product model. It
is not part of the default-on core story, and it depends on the separate native
host/runtime topology described in
[docs/features/browser-extension.md](docs/features/browser-extension.md).

```
Browser popup / content script
        ↕  Native Messaging (stdio JSON-RPC)
tsafe-nativehost  (registered by `tsafe browser-native-host ...`)
        ↕
`<vaults-dir>/<profile>.vault`
```

**Features (v1 — see [docs/features/browser-extension.md](docs/features/browser-extension.md) for full scope and out-of-v1 items):**

- Autofill on `<input type="password">` detection (user-initiated)
- Domain-to-profile mapping (`tsafe browser-profile add github.com`)
- HTTPS-only content script — autofill UI does not run on `http:` pages; advanced lookalike / homoglyph guards per roadmap, not a v1 bar
- Context menu: right-click any password field to fill
- Popup UI: search, copy, reveal, lock

**Build:**

```powershell
cd extension && npm install && npm run build
# or
python scripts/build/build_extension.py
```

---

## Terminal UI (TUI)

Launch with `tsafe ui` or `tsafe --profile prod ui`.

**Key bindings:**

| Key | Action |
|---|---|
| Arrow keys | Navigate secret list |
| Enter | Unlock vault / confirm action |
| `s` | Set (add/edit) a secret |
| Delete | Remove selected secret |
| `r` | Rotate master password |
| `/` | Search filter |
| `?` | Help overlay |
| Tab | Switch profiles |
| Ctrl-C | Quit |

Screens: Login, Dashboard, Edit Modal, Audit Log, Snapshot Restore, Help.

---

## External Integrations

Within the current product boundary, **Azure Key Vault** is the only
default-on core provider path here. The other provider bridges below remain
implemented but **gated non-core** and should not be read as part of the
default release tier.

### Azure Key Vault

```powershell
$env:TSAFE_AKV_URL = "https://myvault.vault.azure.net"
$env:AZURE_TENANT_ID = "..."
$env:AZURE_CLIENT_ID = "..."
$env:AZURE_CLIENT_SECRET = "..."

tsafe kv-pull --prefix MYAPP_ --overwrite
```

Falls back to IMDS managed identity when service principal credentials are not set (for Azure VMs / ACI).

### HashiCorp Vault

```powershell
$env:TSAFE_HCP_URL = "http://vault:8200"
$env:VAULT_TOKEN = "hvs.xxx"

tsafe vault-pull --mount secret --prefix myapp/
```

### 1Password

```powershell
tsafe op-pull "Database Credentials" --op-vault Personal
```

Requires the `op` CLI installed and authenticated.

---

## Infrastructure

Optional Azure infrastructure for audit log ingestion and cloud-backed secrets.

```powershell
# Plan
cd terraform
terraform init -backend-config=../envVars/backend-config.azurerm.hcl
terraform plan -var-file=../envVars/<your-tsafe-env>.tfvars

# Apply
terraform apply -var-file=../envVars/<your-tsafe-env>.tfvars
```

**Resources provisioned:**

- Azure Resource Group
- Azure Key Vault (RBAC, purge protection, soft delete)
- Log Analytics Workspace (90-day retention)
- Diagnostic settings routing KV audit events to LAW

---

## Pipelines

| Pipeline | Trigger | Purpose |
|---|---|---|
| `.pipelines/quality.yaml` | PR + main push | fmt, clippy, test, contract validation, TF validate (Ubuntu) |
| `.pipelines/build-and-publish.yaml` | Tag push (`tsafe-v*`) / manual | Multi-platform build (Windows/macOS/Linux tickboxes), MSI, ProGet |
| `.pipelines/deploy-infrastructure.yaml` | Manual | Terraform Plan / Apply / Destroy |
| `.github/workflows/release.yml` | Tag push (`tsafe-v*`) / manual | GitHub Actions: multi-platform build + GitHub Release |

---

## Local Development

```powershell
# Run all quality checks (same as CI)
just ci

# Regenerate Unix man pages from the live clap command tree
just manpages

# Watch and re-run tests on file change (Podman)
podman compose up

# Build a portable release archive for the current platform
python scripts/build/build_release.py --shape default-core-full

# Build browser extension packages
python scripts/build/build_extension.py

# Run secret scanner
python scripts/secret_scan.py
```

Windows MSI packaging and ProGet publish flows are wired through the build pipelines and helper scripts under `scripts/build/` and `scripts/proget/`.

### Git: fork and upstream

If you fork **tsafe**, add the canonical repo as **`upstream`** so you can sync `main`:

```bash
git remote add upstream https://github.com/0ryant/tsafe.git
git fetch upstream
```

Use SSH URLs if you prefer. Do **not** embed personal access tokens in `remote` URLs (rotate any token that was ever committed or copied into a URL). **`origin`** is your fork; **`upstream`** is the public source you pull from.

Homebrew tap packaging (separate public repo) is documented in [`packaging/README.md`](packaging/README.md); bootstrap with **`just homebrew-tap-bootstrap`**.

### Dev container

VS Code / GitHub Codespaces: open the repo in a container using [`.devcontainer/devcontainer.json`](.devcontainer/devcontainer.json) (Rust bookworm image, `rustfmt` + `clippy` installed on create).

### Docs vs code

When you change CLI behavior, env vars, or extension UX, keep **`README.md`**, **`docs/`**, and **`.env.example`** accurate. Cursor picks up [`.cursor/rules/docs-truth.mdc`](.cursor/rules/docs-truth.mdc) for touched paths.

### Crate versions and pre-push

Workspace crates (`tsafe-agent`, `tsafe-azure`, `tsafe-aws`, `tsafe-cli`, `tsafe-core`, `tsafe-gcp`, `tsafe-nativehost`, `tsafe-tui`) each have their own `[package].version` in `crates/<name>/Cargo.toml`. Release tarball and extension versioning follow **`tsafe-cli`** (see `scripts/_util.py`). Do not read that list as a claim that those crates are already live on crates.io for the current release line.

Crates.io publish/install truth follows the manifest graph, not just the top-level product tag. In the frozen core-only model, `cargo install tsafe-cli` is truthful only when the exact compiled standalone `tsafe` graph for the claimed release shape is live on crates.io: `default-core-cli` and `default-core-cli+agent` require `tsafe-cli`, `tsafe-core`, and `tsafe-azure`, while `default-core-cli+tui` and `default-core-full` additionally require `tsafe-tui`. That is enough for the compiled `tsafe` binary claim only. `tsafe-agent` and `tsafe-nativehost` remain separate published/runtime companions rather than transitive installs from `tsafe-cli`, and broader provider stacks remain outside that Cargo claim.

- **Bump helper:** `python3 scripts/version_set.py 1.0.2` (all crates to a target), `python3 scripts/version_set.py --crate tsafe-core --bump patch`, or `python3 -m scripts version …`.
- **Version policy:** see [`docs/release-versioning.md`](docs/release-versioning.md) for when to use patch vs minor (`0.6.0`, `0.7.0`, etc.) while the repo is still pre-`1.0`.
- **Pre-push:** With `git config core.hooksPath .githooks` (set by `just setup`), pushes to **`main`** require a version bump on every crate whose files changed versus the remote tip (or all six if the workspace root `Cargo.toml` changed). Override refs with **`TSAFE_VERSION_HOOK_REFS`** (comma-separated, `fnmatch` allowed, e.g. `refs/heads/main,refs/heads/release/*`).

### Docker

```powershell
# Dev container (cargo-watch, full toolchain)
podman compose up --build

# Production image (~50 MB, non-root)
podman build -f docker/Dockerfile.build -t tsafe .
podman run --rm tsafe --help
```

---

## MSI Distribution

`cargo-wix` currently generates the broader `gated-broader-dev` MSI artifact
`tsafe-<version>-x86_64.msi` that:

- Installs `tsafe.exe` and **`tsafe-nativehost.exe`** under `C:\Program Files\tsafe\` (see `crates/tsafe-cli/wix/main.wxs`)
- Ships **`tsafe-nativehost.exe`**, but browser/native-host registration is still a manual post-install step: run **`tsafe browser-native-host register --extension-id <chromium-id>`** after installing the matching Chromium-family browser extension on Windows
- Preserves an existing browser/native-host registration across in-place MSI upgrades; initial registration is still a manual post-install step
- Adds the install directory to `PATH`
- Creates Start Menu and desktop shortcuts
- Registers an uninstaller in Add/Remove Programs
- Upgrades in-place using a stable GUID

That Windows MSI path is a channel-specific broader install shape, not proof
that every default-core tag or `cargo install tsafe-cli` path includes
browser/nativehost coverage. The current default-core tagged release channel
remains portable ZIP-only.

Windows portable ZIP names are shape-specific. When a tagged default-core
release channel exists, it uses `tsafe-<version>-default-core-full-x86_64-pc-windows-msvc.zip`;
local Windows packaging helpers may also emit `tsafe-portable-<shape>-<target>.zip`
for copy-anywhere deployment without elevation.

The MSI is published to a ProGet Universal feed (e.g. `tsafe`) via the `build-and-publish` pipeline.

---

## Contracts

tsafe follows a **contracts-first** design. Schemas are defined before implementation.

- **Vault format** — [contracts/vault/vault.schema.json](contracts/vault/vault.schema.json) (`tsafe/vault/v1`)
- **Error envelope** — [contracts/errors/error-envelope.schema.json](contracts/errors/error-envelope.schema.json)
- **Reason codes** — [contracts/errors/reason-codes.json](contracts/errors/reason-codes.json) (13 machine-readable codes)

---

## Principles

- **Contracts first** — vault schema and error envelope defined before implementation
- **TDD** — every module has `#[cfg(test)]` tests; hooks enforce `cargo test` before push
- **DRY** — shared logic in `tsafe-core`; CLI is a thin dispatch layer
- **Idempotent** — `tsafe set` is safely re-runnable; vault rotation is atomic
- **Shift left** — pre-commit (fmt), pre-push (clippy + test + TF validate), CI quality gate
- **Right tool** — Rust core (memory safety, `zeroize`, static binary), Python/Pwsh automation, Terraform IaC
- **Extensible** — `tsafe-core` is a standalone crate; new workspace members bolt on cleanly

---

## Documentation

Feature index and docs hub: [docs/index.md](docs/index.md).

### Guides

| Document | Description |
|---|---|
| [Feature Index](docs/index.md) | Hub for guides, feature docs, and the fastest path to deeper product documentation |
| [Getting Started](docs/getting-started.md) | Prerequisites, install, first vault, CI integration |
| [CLI Reference](docs/cli-reference.md) | Full command reference with flags, env vars, and examples |
| [Man Pages](docs/man/README.md) | How to regenerate, install, and use `tsafe.1` and subcommand manuals |
| [Security Findings](docs/security/findings.md) | Tracked security review findings and their status |
| [Internal Pitch](docs/pitch.md) | Why tsafe + AKV, the PowerShell memory problem, addressing security team concerns |
| [Competitive Analysis](docs/competitive-analysis.md) | Feature and security comparison vs 12 alternatives, gap analysis |
| [Feature Maturity](docs/feature-maturity.md) | Every feature: current state, MVP path, AAA path |
| [Roadmap](ROADMAP.md) | Feature tracking — shipped and planned |

### Feature Deep-Dives

| Document | Description |
|---|---|
| [Vault Basics](docs/features/vault-basics.md) | Init, set, get, delete, list — with key naming rules and password management |
| [Export and Exec](docs/features/export-and-exec.md) | Output formats (env, dotenv, powershell, json, github-actions) and secret injection |
| [Import](docs/features/import.md) | Migrate from `.env` files by default; broader Bitwarden, 1Password, LastPass, and browser CSV imports require `pm-import-extended` |
| [Profiles](docs/features/profiles.md) | Multi-profile workflows, compare, and diff |
| [Secret Generation](docs/features/secret-generation.md) | CSPRNG random secrets with configurable length and charset |
| [TOTP](docs/features/totp.md) | Time-based one-time passwords — use tsafe as an authenticator |
| [Audit Logging](docs/features/audit.md) | Append-only JSONL audit trail with Splunk/JSON export |
| [Snapshots](docs/features/snapshots.md) | Automatic backups, manual restore, and auto-healing |
| [Cloud Integrations](docs/features/cloud-integrations.md) | Azure Key Vault core path plus gated HashiCorp Vault / 1Password / OTS surfaces |
| [Browser Extension](docs/features/browser-extension.md) | Gated browser/native-host flows — v1 scope and roadmap |
| [Terminal UI](docs/features/tui.md) | Interactive dashboard with keyboard shortcuts and screen reference |
| [Organization](docs/features/organization.md) | Tags, pins, and aliases for managing secrets at scale |
| [Developer Tools](docs/features/developer-tools.md) | Doctor, secret scanning hooks, shell completions, QR codes, SSH keys, rotation policies |
| [Security](docs/features/security.md) | Encryption model, memory safety, atomic writes, and threat model |
| [Team Vaults](docs/features/team-vaults.md) | Multi-recipient age encryption — shared vaults for teams |
| [Biometric Unlock](docs/features/biometric.md) | Touch ID, Windows Hello, Linux Secret Service — password-free vault access |

---

## About

tsafe is part of the [algol.cc](https://algol.cc) ecosystem — small, composable tools for explicit authority in software systems.
