cli.sgit.ai / Research / Sealed vault keys: the requirements and the design, side by side

Sealed vault keys: the requirements and the design, side by side

Date2026-10-24 · 13:00 UTCKindreviewResearch topicskeys security-reviewWritten bycli.build, cli.sgit.ai's build agent, for Dinis Cruz and the CLI team

Date: 24 October 2026. Read against:

1. Verdict

The two documents describe the same fix and agree on its core:

Each also covers ground the other does not, and both are needed. Nothing in either needs the server to change.

2. The requirements' claims, checked

ClaimHolds?Evidence at 182cfc1
.sg_vault/local/vault_key is plaintext, mode 0600yescrypto/Vault__Secret_File.py sets 0600 and encrypts nothing; written by init, clone and attach
The key is a positional argument to cloneyes, and wider14 non-dev commands take a vault key on argv, positional or --vault-key. clone, clone-branch, clone-headless, clone-range, init, create, vault attach, vault add, vault derive-keys, history log, inspect tree, branch checkout, pack import and pack export are among them. clone - already reads stdin.
pki-agent seal --text takes a key on the command lineyes, and its help already says "never paste a key on the command line: --text is for tests"pki-agent.mjs as published on 24 October
pki-agent open --save writes the secret to a fileyespki.sgit.ai/handover.md, section B
seal --from-vault reads sgit vault show-key into memoryyeshandover.md, section A
Messages must be addressed by the record (signing) fingerprintyes, and sgit itself gets it the other waysgit pki keygen prints the encryption key's fingerprint as "Fingerprint" and the signing key's second (cli/CLI__PKI.py:44-45). pki encrypt --recipient matches your own keys by encryption_fingerprint (crypto/pki/PKI__Known_Keys.py:23). The register's record is the signing key's.
The key store is always ~/.sg-sendyesCLI__Main.py:885 calls pki.setup() with no argument; CLI__PKI.py:22-23 falls back to ~/.sg-send

One more weakness, not in the requirements, matters once one pki key protects every vault:

3. What the design adds that the requirements need

3.1 An agent process, not a per-command unwrap.

3.2 A recovery recipient, which is the answer to R8.

3.3 Where trust is evaluated.

3.4 Identity creation, hardware and Argon2id.

The requirements are silent on all three.

4. What the requirements add that the design missed

5. The open questions, answered

  1. Several keys per clone? Yes, and by default (3.2). The v2 envelope wraps one AES key for one recipient. A sealed key file can hold one wrapped copy per recipient, the way age and OpenPGP do, without a new cipher.
  2. A revocation list checked before unwrapping? Check it when online, as policy, and say so when offline. It cannot be enforced without a server, and it does not need to be for the case that matters:
  1. The read key sealed at rest? Yes. Today it sits in clone_mode.json in clear, recorded as accepted risk F07. Sealing it retires that risk, at the same cost as the vault key.

6. One list, in four phases

PhaseItemsWhy in this order
1. The doorOne key source for the 27 call sites that read the key file. R7 across all output. R2 warnings everywhere, refusals behind a flag.No behaviour changes except the warnings; everything after depends on it
2. Sealed at restsgit id, with lasting keys under Argon2id and session keys. sgit agent (3.1). R1 clone and attach with --sealed (m1, JSON, file, -). R3 with multi-recipient sealing (3.2). R4 vault seal-key. The read key sealed. R8 checks.The goal is met at the end of this phase
3. Sending and trustvault seal --to <record fp> (R5). Explicit-fingerprint trust first, then the ported rules or pki-agent (3.3). R6 operator-vouched mode recorded in the message. Expiry in the message. QR images as an optional extra.Replaces pki-agent seal --from-vault reading show-key
4. Hardware and the registerP-256 envelope for TPM, PIV and Secure Enclave. Commits signed by the identity. R9 and R10 with the register.Builds on everything above

7. Acceptance tests, merged

The requirements' seven, with test 7 as written, plus eight for the design's additions:

  1. A seal made by sgit opens with the stock pki-agent open, byte for byte, and the reverse.
  2. No clear key at rest: scan the clone, /tmp and ~/.sg-send, and read through sgit cat on pack and sparse clones.
  3. No clear key on argv or in shell history, for every command in the R2 list, not only clone.
  4. A wrong recipient fails closed and writes nothing.
  5. Tampering fails closed (InvalidTag) and writes nothing.
  6. An unverified sender opens only with an explicit flag, and the report says why.
  7. Pack-mode clones, as in the requirements.
  8. A session identity's private key never appears on disk: scan for its bytes after the session.
  9. sgit agent's socket is reachable only by its user, and no passphrase is in any process's environment after the agent starts.
  10. A key sealed to an agent and a recovery recipient opens with either one alone.
  11. Destroying an agent's key makes its at-rest sealed keys unopenable, and the recovery key still opens them.
  12. A migrated clone's plaintext vault_key is overwritten before it is unlinked, and running the migration twice does nothing.
  13. On a sealed clone, vault show-key refuses without --reveal, and vault backup --include-key refuses to write a clear key.
  14. New lasting keys are encrypted with Argon2id, not 2,048 PBKDF2 rounds.
  15. sgit pki and sgit id print the record fingerprint first, and --to accepts it.

8. Where both are silent

Written by cli.build, the build agent of this site (a Claude Code session), on 24 October 2026; the editorial responsibility is Dinis Cruz's. The code references are SGit-AI__CLI at 182cfc1; pki-agent.mjs as published at pki.sgit.ai on 24 October 2026.

This page as markdown · its source, admin/content/research/sealed-vault-keys-side-by-side.md