Skip to content

Security Architecture

A technical deep-dive into Patchwork's security design.

Defence in Depth

Patchwork follows the defence-in-depth principle: no single layer is trusted to work alone. Each layer covers the failure modes of the others.

┌─────────────────────────────────────────────┐
│  Layer 5: Key Hardening & Signing Proxy     │
│  Root-owned keyring, signing via relay       │
├─────────────────────────────────────────────┤
│  Layer 4: Auto-Seal with HMAC + Witnesses   │
│  Periodic cryptographic snapshots            │
├─────────────────────────────────────────────┤
│  Layer 3: Heartbeat Protocol                │
│  30s liveness proof, gap detection           │
├─────────────────────────────────────────────┤
│  Layer 2: Root-Owned Audit Relay            │
│  Independent chain, protected by OS perms    │
├─────────────────────────────────────────────┤
│  Layer 1: Hash-Chained Audit Log            │
│  SHA-256 chain, tamper detection             │
└─────────────────────────────────────────────┘

See Tamper-Proof Layers for a user-friendly explanation of each layer.

Cryptographic Primitives

PrimitiveAlgorithmPurpose
Event hashingSHA-256Hash chain integrity
Seal signingHMAC-SHA256Tamper-evident snapshots
Key derivationSHA-256 (first 16 hex chars)Key ID generation
Canonical serializationDeterministic JSON (sorted keys)Reproducible hashes

Hash Chain

Each event hash is computed over a canonical JSON representation of the event (excluding hash and prev_hash fields). The canonical serializer sorts keys deterministically and strips whitespace to ensure identical inputs always produce identical hashes.

hash(event) = SHA-256(canonical_json(event \ {hash, prev_hash}))

The prev_hash field creates the chain link:

event[n].prev_hash = event[n-1].hash

Seal Payload

The seal payload is a deterministic string:

patchwork-seal:v1:{tip_hash}:{chained_events}:{sealed_at}

This is signed with HMAC-SHA256 using the active key from the keyring.

Timing-Safe Comparison

Seal verification uses crypto.timingSafeEqual to prevent timing attacks on HMAC comparison.

Keyring Architecture

Keys are stored in a directory-based keyring:

/Library/Patchwork/keys/seal/    (root-owned, 0o700)
├── ACTIVE                       (text file: active key ID)
├── a1b2c3d4e5f6g7h8.key        (0o600)
├── f9e8d7c6b5a49382.key        (0o600, rotated)
└── ...
  • Each key is 32 bytes of crypto.randomBytes, hex-encoded
  • Key IDs are derived from SHA-256(key).slice(0, 16)
  • The ACTIVE file points to the current signing key
  • Rotated keys are preserved for seal verification
  • The relay reads keys; user-space code requests signatures via the socket

Signing Proxy Flow

User space                    Root relay
    │                             │
    ├─ sign request ─────────────▶│
    │  (data, key_id?)            │
    │                             ├─ load key from keyring
    │                             ├─ HMAC-SHA256(data, key)
    │◀─ signature response ───────┤
    │  (signature, key_id,        │
    │   signed_at, source)        │

If the relay is unavailable, the client falls back to a user-owned keyring at ~/.patchwork/keys/seal/ — less secure (no root protection) but functional.

Relay Daemon

The relay runs as a launchd/systemd service under root. It:

  1. Listens on a Unix domain socket (/Library/Patchwork/relay.sock)
  2. Accepts fire-and-forget event messages from user-space hooks
  3. Maintains an independent hash chain in root-owned files
  4. Emits heartbeats every 30 seconds
  5. Auto-seals the chain every 15 minutes
  6. Publishes seals to configured witness endpoints

Process Isolation

The relay daemon and the user's AI agent run in completely separate process trees. The agent cannot:

  • Read the relay's files (root-owned, 0o600/0o700)
  • Write to the relay's socket (write-only, the relay validates messages)
  • Kill the relay process (requires root)
  • Modify the launchd/systemd configuration (requires root)

Witness Trust Model

Witnesses provide independent verification of seals. The trust model is:

  1. No single witness is trusted. A quorum of independent witnesses must confirm.
  2. Witnesses don't trust each other. Each witness stores its own append-only log.
  3. The protocol is open. Anyone can run a witness server.
  4. Clients keep receipts. Even if a witness is compromised, the client has local proof.

This follows the same principle as Certificate Transparency in the TLS ecosystem.

See Seals & Witnesses for configuration details.

File Permissions

PathOwnerPermissionsWhy
~/.patchwork/user0o700User data directory
~/.patchwork/events.jsonluser0o600User-level audit log
~/.patchwork/keys/user0o700User keyring (fallback)
/Library/Patchwork/root0o755System enforcement
/Library/Patchwork/events.relay.jsonlroot0o644Root audit log
/Library/Patchwork/keys/seal/root0o700Root keyring
/Library/Patchwork/keys/seal/*.keyroot0o600Signing keys
/Library/Patchwork/relay.sockroot0o777IPC (anyone can write)

Watchdog

The watchdog is a periodic task (launchd timer / systemd timer / Task Scheduler) that verifies:

  1. Hooks are installed in the AI agent's configuration
  2. Hook scripts haven't been modified (hash baseline)
  3. The relay daemon is running
  4. The policy file is intact

If any check fails, the watchdog can re-install hooks and alert the administrator.

Released under the BUSL-1.1 License.