hushsecrets your AI agent can use but never readGitHub

How hush is put together

A map for someone about to change hush: how one command flows, where each part lives, and the properties the tests hold it to. It is short on purpose. The source files open with a comment saying what they are for and why; this doc tells you which one to open.

One hush run, end to end

hush run -- npm test
   │
   ▼
 cli.ts ─────────────── picks the command (src/commands/run.ts)
   │
   ▼
 cli/context.ts ──────── which project: .hush/ found upward from here, or HUSH_VAULT
   │                     which vault file, which policy (.hush/policy.json ∪ ~/.hush/policy.json floor)
   ▼
 identity.ts ─────────── your private key: keychain / DPAPI / file / age plugin / Secure Enclave
   │
   ▼
 vault.ts (+ vault-core, vault-values, vault-files)
   │   open the file ──► integrity.ts: is this the vault this machine accepted?
   │                     (pinned roster, key commitments, signed header — header.ts)
   │   unwrap the data key for you (crypto.ts; age.ts / enclave.ts for hardware)
   ▼
 library.ts + vault-values.ts ── resolve the sets this project uses, library first,
   │                              later sets winning, each value opened with its AAD
   ▼
 policy.ts ───────────── may this command run at all? (deny list, allowed sets)
   │
   ▼
 approval.ts ─────────── does a person have to say yes? cached grant → fingerprint
   │                     (biometry.ts) → dialog (dialogs.ts) → paired device (relay.ts)
   │                     → otherwise refuse
   ▼
 run.ts ──────────────── spawn with the values in its environment, nothing on disk;
   │                     stdout/stderr through the streaming redactor (redact.ts)
   ▼
 audit.ts ────────────── one hash-chained line in .hush/audit.log: what ran, which keys,
                         how it was approved — never a value

The agent's path is the same pipeline behind a different front door. mcp.ts speaks the protocol, mcp-tools.ts implements each tool, and every tool that uses a credential goes through the same policy, approval, run and redaction steps. None of them returns a value.

Where things live

AreaFiles
The command linecli.ts (dispatch), cli/ (arguments, context, output, prompts, help), commands/<command>.ts (one per command)
The schemecrypto.ts (sealing values, wrapping data keys, signing), header.ts (the signed vault header), merge.ts (three-way merge of vault files)
The vaultvault-files.ts (the file format, finding it, locking) → vault-core.ts (keys, members, trust) → vault-values.ts (sets and values) → vault.ts (the class you use)
Trustintegrity.ts (what this machine has seen of each vault, and what it refuses), audit.ts (the chained log), freshness.ts (value age, exposure)
Your keyidentity.ts (where it lives), age.ts (hardware keys through age plugins), enclave.ts + native/hush-enclave.swift (Secure Enclave), swift.ts (building the macOS helpers safely)
Asking a personapproval.ts, dialogs.ts (one backend per desktop), biometry.ts + native/hush-touchid.swift, relay.ts + relay-server.ts + qr.ts (a paired device)
Using a secretrun.ts + redact.ts, request.ts (an HTTP call with the secret substituted inside hush), materialize.ts (a secret that has to be a file)
Rulespolicy.ts (the policy and its floor), schema.ts (.env.schema), posture.ts + secure.ts (the security ladder)
Getting secrets inimport.ts (dotenv, JSON, 1Password), scan.ts (what a codebase reads), services.ts (known providers), start.ts (the first run)
Agentsmcp.ts, mcp-tools.ts, agents.ts (writing each agent's config), skills/hush/SKILL.md
The appui.ts (server), ui-api.ts (endpoints), ui-state.ts (what the page is told), ui-page.ts + ui/*.ts (the page)
Platformsplatform.ts (what differs on Windows), which.ts, clipboard.ts
Distributionassets.ts, scripts/build-binaries.mjs, scripts/install.sh, scripts/install.ps1, scripts/package-manifests.mjs, action.yml

The invariants, and the tests that hold them

These are the properties hush exists for. Each one is guarded by tests that fail when it breaks. That is checked by breaking it on purpose, as CONTRIBUTING.md describes.

InvariantGuarded in
No tool returns a value. The MCP server has no getter, and every output is redacted.test/mcp.test.ts ("never returns a secret value, only its effects"), test/fuzz-surfaces.test.ts
A ciphertext is bound to its set, key and generation (AAD). It cannot be moved or replayed into another slot.test/crypto-properties.test.ts, test/scheme-conformance.test.ts (checked against an independent WebCrypto implementation)
A vault this machine did not accept is refused, not used. Members, data-key commitments and the header signature are all pinned.test/trust.test.ts (the V-1 reproduction), test/signed.test.ts
Only an admin's signature changes who can read.test/signed.test.ts, test/merge.test.ts (the merge matrix)
The policy floor only tightens. A repo's policy cannot drop below ~/.hush/policy.json.test/floor.test.ts, test/policy.test.ts
No file answers an approval. An approval comes from a dialog, a fingerprint or a paired device's signature, never from anything the caller can write.test/commands/policy.test.ts ("a grant file cannot stand in for an approval…"), test/approval.test.ts, test/relay.test.ts
Nothing in the environment makes an approval easier. Test seams are parameters, not variables.test/biometry.test.ts, test/approval.test.ts
Values never touch disk in a run.test/commands/run.test.ts ("while the command runs, no file under the project, HUSH_HOME or tmp holds the value"), test/materialize.test.ts (the one deliberate exception, removed afterwards)
The audit log cannot be edited quietly.test/audit.test.ts
Revocation re-keys. Removing a member rotates what they could read, and marks it exposed.test/hush.test.ts, test/freshness.test.ts
The docs match the code. Every command is in the help, every MCP tool is in the README, and nothing is exported unused.test/consistency.test.ts

test/invariants.test.ts is worth reading first. It is a list of properties that once turned out not to hold.

Conventions that are not obvious from one file