---
title: "Cosigner Security Model"
description: "Cosigner security model: trust boundaries, authentication, encryption at rest, hardening, audit and supply chain verification."
url: https://docs.fystack.io/cosigner/security
last_updated: 2026-10-14
---

> ## Documentation index
> Fetch the complete documentation index at: https://docs.fystack.io/llms.txt
> Use it to discover all available pages before exploring further.

# Cosigner Security Model

No single party can move funds alone: not Fystack, not your host, not anything in between.

## Who controls what

| You | Fystack |
| --- | --- |
| Cosigner host, storage, network | Apex: pairing, identities, revocation |
| Storage key: password or KMS policy | Orchestrator: sessions |
| Callback server, policy and keys | Gateway: stream auth and relay |
| Backups, exports, upgrades | MPCium nodes and workspace policies |

## Signing controls

Every signature passes all of these:

1. **Workspace policies** and [approval groups](https://docs.fystack.io/product/approval-group.md) in Apex.
2. **Initiator signature**: orchestrator accepts only Apex-signed requests.
3. **Committee pinning**: participants and threshold must match keygen.
4. **Orchestrator signature**: cosigner verifies it against the key pinned at pairing.
5. **Approver signature**: required for wallets with an approver.
6. **Your callback**: must return a signed `approve`.
7. **Threshold**: your cosigner's share is required.

If Fystack were fully compromised, 1 to 5 could fail. Step 6 still holds: it runs on your infrastructure, with keys Fystack never sees, against the raw bytes being signed.

## Authentication

**Cosigner identity**: Ed25519 key. Stored at `state/cosigner.key` (`0600`). Registered with Apex once via a pairing token. It signs the Gateway JWT, every session event and peer message, and audit entries.

**Cosigner → Fystack**: EdDSA JWT per stream.

```json
{
  "iss": "<cosigner_id>",
  "sub": "<cosigner_id>",
  "aud": "cosigner-gateway",
  "ctx": "cosigner-gateway-auth-v1",
  "jti": "<random>",
  "iat": 1791794400,
  "exp": 1791794460
}
```

The Gateway accepts only `EdDSA`, checks audience and expiry (30s skew) and rejects reused `jti` for 2 minutes. Revocation disconnects live streams immediately.

**Fystack → cosigner**: The orchestrator key and fingerprint are pinned at pairing. Every control message must be signed by it with an increasing sequence number. Result signatures use a separate prefix so they can't be replayed as control messages.

**Participant ↔ participant**: Peer keys arrive in the signed `session_start`. Signed X25519 exchange, per-direction HKDF keys, ChaCha20-Poly1305. Gateway and orchestrator see only ciphertext.

## Encryption at rest

_Diagram: Password or AWS KMS unwraps the storage KEK, HKDF derives four scope keys, each row is AES-256-GCM_

- **Storage key**: 32 bytes, unwrapped at start from a password (Argon2id `t=3`, 64 MiB) or AWS KMS. Memory only, zeroed after use.
- **Scope keys**: HKDF-SHA256 derives separate keys for `shares`, `preparams`, `checkpoints`, `metadata`.
- **Rows**: AES-256-GCM, random 12-byte nonce, bound to scope and row key. Moved rows fail to decrypt.
- **No plaintext shares leave**: stripped from events. Exports are sealed to RSA-OAEP + AES-GCM or a passphrase.

**Password unlock**: `cosigner-cli` never puts the password in env or args. It opens a one-shot local socket; the container proves a 32-byte token via HMAC and receives the password AES-GCM encrypted.

## Runtime hardening

- Distroless, non-root image pinned by digest.
- Read-only root filesystem; `tmpfs` for `/tmp` and `/run/cosigner`.
- All capabilities dropped, `no-new-privileges`.
- Config and secrets mounted read-only.
- Limits: 128 KiB control messages, 20 starts and 10 aborts per minute, 5 concurrent sessions.

## Audit trail

Append-only JSONL. Each entry is signed and includes the previous entry's hash, so edits and deletions are detectable. Ship it off-host.

## Supply chain verification

Binaries, installers and images are built by GitHub Actions release workflows and signed with Sigstore. The certificate names the exact workflow and tag. The runtime pipeline also runs `go mod verify`, race tests, a reproducibility check, `govulncheck` and `gosec`, and attaches an SBOM and provenance.

`cosigner-cli` verifies on every `up`, `update` and `doctor`. To check by hand:

**Verify release artifacts**

```bash
cosign verify ghcr.io/fystack/cosigner@sha256:<digest> \
  --certificate-identity https://github.com/fystack/cosigner/.github/workflows/release.yml@refs/tags/<version> \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com
```

```bash
curl -sSLO https://cosigner.fystack.io/<version>/install.sh
curl -sSLO https://cosigner.fystack.io/<version>/install.sh.sigstore.json
cosign verify-blob install.sh \
  --bundle install.sh.sigstore.json \
  --certificate-identity https://github.com/fystack/cosigner-cli/.github/workflows/release.yml@refs/tags/<version> \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com
```

Digests and checksums: [cosigner.fystack.io](https://cosigner.fystack.io/#verify). Report mismatches to security@fystack.io.

## Residual risks

- **Regular mode = host custody.** Root or Docker access on the host can read memory. [Nitro Enclave](https://docs.fystack.io/cosigner/nitro-enclave.md) support (coming soon) will remove that.
- **Callback quality.** A callback that approves without decoding the transaction adds nothing. Default deny.
- **Availability.** A hostile host can block traffic or delete and roll back rows. Encryption detects edits, not deletion. Keep backups.
- **Duplicates.** Never run the same cosigner on two hosts.
- **Recovery material.** Backup passwords and export passphrases unlock your share. Store them separately.

Before real funds: test restore, restart, failure and IAM revocation, and get an independent review.
