---
title: "Signer"
description: "A separate signing process built so that the model never sees key bytes. Built and tested, not connected to the shipped app."
url: https://barestate.xyz/docs/security/signer
---

# Signer

> A separate signing process built so that the model never sees key bytes. Built and tested, not connected to the shipped app.

If the model can read a private key, a single bad prompt can leak it. The signer exists to prevent that: it keeps keys in their own process and hands out only signatures, never key bytes. This page describes how it does that and where its guarantees stop.

**Warning:**
The signer is not connected to the shipped terminal app. This page describes the component and its tests. It does not describe what the app does today.

## How signing is isolated

The signer runs as its own process, so the engine and the terminal app have to ask it for anything they want. It listens on two Unix sockets in a private directory, one for the engine and one for the terminal app. Each channel has its own random 32-byte bearer token in a private file with mode 0600. The first message on a socket must be a `hello` with the right token, which the signer compares by SHA-256 digest with a timing-safe check.

For key custody, the signer makes a random 32-byte seed and writes new private files that it never overwrites. It returns only an alias, an address and a profile id. There is no API to import a key file or take a key path, and a copied key, or a key with a different issued address, is refused. Key bytes stay inside a secret-holding type, so a caller can only ask for the public address, ask for a signature on a message, or ask the signer to scrub text.

Because those files and directories are the whole defense, the signer checks them on every use. They must be owned by you, have no group or other access, and not be a symlink.

Tests: yes for sockets, tokens and key custody. No direct test targets the directory and file checks.

**Stated limit.** Node gives no peer credentials for Unix sockets, so caller identity is whoever can open the socket and read the token file. Hostile code running as the same user is outside the boundary. Key files are plaintext on disk, protected only by permissions. No claim of encryption at rest is made.

## What the signer will sign

A signer that signs anything is only a remote key. So it accepts just five messages, `hello`, `status`, `lifetime`, `sign` and `reconcile`, and refuses unknown fields such as caller cluster, endpoint or profile claims, and any confirmation or settlement claim. Within `sign`, these rules apply.

| Rule | What it means |
|---|---|
| Network profile | Only a user-approved devnet profile, or a live OMS-owned local profile. Mainnet and testnet genesis hashes are refused. A local profile needs a literal loopback `http` endpoint |
| Transaction shape | The fee payer must be the issued signer. Only single-signer messages. No pre-signed messages. Every program and account must be on an allowlist |
| Outflow | Direct System transfer and createAccount are decoded. Other System instructions are refused. Outflow mixed with an unknown custom program is refused. A fee over the ceiling is rejected, not clipped |
| Limits | Per-transaction and cumulative lamport caps. The cumulative cap survives restarts |
| Idempotency | The SHA-256 of the exact message bytes is the reservation id. A repeat reuses the same reservation and signature |
| Unknown outcomes | They stay charged. A release needs proof of absence from finalized history past expiry. A model cannot report an outcome. There is no time-based refund |
| One at a time | A queue serializes signing and reconciliation |
| Startup | Dispatch stays inactive until both sockets are ready. A failure rolls back |

Tests: yes for each row, in the signer's development, genesis and subprocess tests.

**Stated limit.** A returned signature is a capability: anyone holding the unsigned bytes can attach it and broadcast. Allowed custom-program effects are not a universal wallet-loss bound.

## Secret scrubbing

Even with custody locked down, a key could leak through an error message or log line. To catch that, signer output is scrubbed once at the write point. Scrubbing replaces every known encoding of the key: hex, base58, base64, base64url, JSON array and CSV. Unknown errors return `E_INTERNAL` with details hidden.

The engine has a matching redactor that can apply to the trace and, with an option, to what the model sees. The settle gate redacts feedback too.

**Gap.** In the shipped session, the engine does not pass secret bytes to the redactor, so it has nothing to remove in the terminal app. It is wired for a signer that is not connected. No test covers this.

**Stated limit.** A secret embedded in a longer payload is only caught if its own encoding appears verbatim.
