cli.sgit.ai / Articles / A vault key that never lands: native PKI in sgit, so a key arrives sealed, stays sealed on disk and is opened only in memory

A vault key that never lands: native PKI in sgit, so a key arrives sealed, stays sealed on disk and is opened only in memory

By Dinis Cruz · 2026-10-24 · published · site v0.2.9

Summary: pki.sgit.ai has made something work that I did not expect to feel this good. An agent gives me a QR code holding its public key; I open it on the website, check it, and write a message only that agent can read. Agents now hand each other vault keys the same way, with me as the transport: I carry a QR image from one session to the other and never see what is inside. But the last step undoes it. The receiving agent opens the sealed message, writes the vault key to a file, passes it to sgit clone on the command line, and sgit keeps it in clear in the clone's folder, next to a signing key that is also in clear. The key is protected while it travels and exposed the moment it arrives. So I asked how sgit itself could take PKI natively, on an agent running in a Linux container that has its own key pair, so that the clear vault key is never written to disk and exists only in memory. The answer is that most of the pieces exist. sgit already makes the right keys (RSA-OAEP 4096 and ECDSA P-256, the bundle pki.sgit.ai uses), already speaks the same sealed envelope, and already reads a vault key from standard input. What is missing is to keep the key sealed at rest, to open it inside a small agent process the way ssh-agent holds SSH keys, and to let sgit make and show its own identity. Opening a sealed key costs about two milliseconds, so this is not a performance question. The real work is that 27 places in the CLI read the key file directly and need to go through one door. This article maps that out, phase by phase.

pki keys agents qr-codes pki-sgit-ai sealed-keys key-storage security proposal

Where a vault key exists in clear, today and with native PKI in sgit. On the left, today: the key is sealed to the receiving agent while it travels as a QR code, then written to a file when the agent opens it, passed to sgit clone as a command-line argument, and kept in the clone's folder in clear, next to the clone's signing key. On the right, the proposal: sgit makes the agent's key pair, the sealed key is given to sgit clone as it is, the clone keeps it sealed to the agent's own key, and each command asks a small sgit agent process to open it in memory, in about two milliseconds. Underneath: nothing is in clear on disk in the proposal; what still holds in both is that a process able to read the agent's memory while it runs can read the key.
Where a vault key exists in clear, today and with native PKI in sgit. On the left, today: the key is sealed to the receiving agent while it travels as a QR code, then written to a file when the agent opens it, passed to sgit clone as a command-line argument, and kept in the clone's folder in clear, next to the clone's signing key. On the right, the proposal: sgit makes the agent's key pair, the sealed key is given to sgit clone as it is, the clone keeps it sealed to the agent's own key, and each command asks a small sgit agent process to open it in memory, in about two milliseconds. Underneath: nothing is in clear on disk in the proposal; what still holds in both is that a process able to read the agent's memory while it runs can read the key.

Update, 24 October 2026, 13:00. Later the same day another agent working with me on pki.sgit.ai sent ten requirements for the same fix. The two are read against each other, and against the code, in a side-by-side review. It changes four things here:

The text below is as published.

In short

What works now

Three things on pki.sgit.ai have become part of how I work.

Where it breaks: the last metre

The receiving side, read against sgit 0.24.0's code:

StepWhere the vault key isIn clear?
The QR image, carried by mesealed to the receiving agent's keyno
pki-agent open qr.png --save DIRvault-key-<id>.txtyes, a file
sgit clone <vault key>the command lineyes: shell history, the process list, often the agent's own transcript
The clone.sg_vault/local/vault_key, mode 0600yes
A read-only cloneclone_mode.json holds the read keyyes (recorded as an accepted risk, AppSec F07)
The clone's signing key.sg_vault/local/key-rnd-imm-*.pem, unencrypted PKCS#8yes
The access token.sg_vault/local/tokenyes

sgit writes every one of these files through one helper that sets the permissions to 0600 and encrypts nothing. Anything that can read the folder can therefore read every vault the agent holds:

What sgit already has

More than I thought:

Two weaknesses turned up in the same reading, and both should be fixed whatever else happens:

The design

Four pieces. Each is small, and together they mean the clear vault key is never written anywhere.

1. sgit makes its own identity

$ sgit id init --session --name "report builder (Claude Code session)"
  identity sha256:3f9c…  (session: in memory only; gone when the agent stops)
$ sgit id show --qr me.png          # the public key as pki.sgit.ai/qr/key/v1 JSON, or a k1 link

sgit id is sgit pki keygen with the right defaults: the same key types, the same bundle and the same store, so pki.sgit.ai and its agent tool read it unchanged. A key is held in one of three ways:

KindWhere the private key isFor
SessionOnly in the memory of sgit agent. It is never written, and it ends with the process.A throwaway cloud container: a new key per session, a new handover per session
LastingOn disk, encrypted under a passphrase with Argon2id. The passphrase is given to sgit agent once.A vault keeper, or an agent that outlives its container
HardwareA TPM 2.0, a security key's PIV slot or a Mac's Secure Enclave. The key cannot be copied out.A laptop or a server that has one

Hardware mostly speaks P-256, not RSA-4096. So the third kind needs a second envelope: ECDH P-256 with HKDF and AES-256-GCM, the construction the CLI team already drew up for its sealed files layer. The RSA envelope stays, because it is what pki.sgit.ai speaks today.

2. A sealed key goes in as it is

$ sgit clone --sealed handover.png work      # or an m1… code, a file, or - for stdin
  sealed to: sha256:3f9c… (this identity)   from: sha256:a1b2… (chains to the root)   expires: 30 Oct
  ▸ Packs fetched (1 pack(s), 2087 objects, 4 request(s) in 6.2s)

The clone accepts exactly what the QR handover produces: the strict pki.sgit.ai/qr/sealed/v1 JSON, or the m1 code inside it. It checks the same things pki-agent open checks:

It opens the key in memory, derives what it needs, and never prints the key or writes it in clear. Reading a QR image needs a decoder, which can be an optional extra (sgit-ai[qr]), so the base install takes the JSON or the code.

3. The clone keeps it sealed

.sg_vault/local/
  vault_key.sealed      the vault key, sealed to this identity (or to several: a person's recovery key too)
  token.sealed          the access token, the same way
  key-rnd-imm-….pem     the clone's signing key, now encrypted under a key sealed the same way
  identity              which identity opens them: a fingerprint, nothing secret

4. sgit agent holds the private key

$ eval "$(sgit agent start --session)"       # sets SGIT_AGENT_SOCK, like ssh-agent sets SSH_AUTH_SOCK
$ sgit status                                # asks the agent to open vault_key.sealed: ~2 ms
$ sgit agent lock                            # forgets every opened key; the private key stays (or, --session, goes)

A small process that holds the private key, and the vault keys it has opened, for a set time, behind a Unix socket only its user can reach. This is the pattern of ssh-agent and gpg-agent, and it is right for the same reasons:

The flow, end to end

A vault key handed from one agent to another with a person as the transport. The key is sealed until the receiving agent's sgit agent opens it in memory, and it stays sealed on that agent's disk afterwards.
A vault key handed from one agent to another with a person as the transport. The key is sealed until the receiving agent's sgit agent opens it in memory, and it stays sealed on that agent's disk afterwards.

What it costs

Measured in this sandbox with cryptography 50.0.2, the library sgit uses:

OperationTime
Open a sealed key (RSA-OAEP 4096)2.1 ms
Seal a key (RSA-OAEP 4096)0.1 ms
Make a session identity (RSA-4096 + P-256)about 470 ms, once per session
Load an RSA-4096 private key (the library's key check)330 ms, once per agent start
Unlock a lasting key with Argon2id (3 passes, 64 MiB)152 ms, once per agent start
What sgit already spends per command (PBKDF2 at 600,000)108 ms

With the agent holding the opened key, every command after the first is about 2 ms slower than today. If the agent also holds the derived keys, it is about 100 ms faster.

Where the work is

The code changes are mostly one refactor:

Phases:

  1. Phase 1, the door. The single key source, behind the existing behaviour, with tests that the 27 call sites use it. No change for users.
  2. Phase 2, sealed at rest. sgit id, sgit agent with session and lasting keys, vault_key.sealed, sgit vault seal-local, clone --sealed for the m1 code and the JSON, and Argon2id for private keys. This is the phase that closes the gap.
  3. Phase 3, the sending side. sgit vault seal --to, expiry in the sealed message, read-only handovers (the read key alone), QR images through the optional extra.
  4. Phase 4, hardware and identity. The P-256 envelope for TPM, PIV and Secure Enclave keys, and signing commits with the identity's own key. A commit's author is then a record in pki.sgit.ai's register, not only a key inside the vault.

None of it needs the server to change: what goes to the host is exactly what goes today.

How this relates to the native PKI vault mode

On 9 October the CLI team mapped a second vault mode built on key pairs:

That is a new kind of vault. This proposal is smaller and comes first: it keeps today's vaults and today's keys, and changes only how a key reaches an agent and how the agent keeps it. Both use the same identity, so the identity built here is the one the vault mode would use later.

What this does not claim

If you want to try this on your own agents, or to argue with the design, write to agent@riskmandate.ai.

Where this comes from

A message of mine on 24 October 2026, written up by cli.sgit.ai's build agent, cli.build, on the same day; the editorial responsibility is mine.

This page as markdown · its source, admin/content/articles/a-vault-key-that-never-lands.md