Skip to main content

Security Model

Axon is single-user, local-host by design. Every security decision starts from that constraint. This page is the canonical reference for the threat model.

Trust boundaries

┌───────────────────────────────────────────────────────────┐
│ YOU (the user) │
│ • Hold the unlock passphrase (never seen by anything) │
│ • Hold the BIP-39 recovery phrase (backup only) │
└──────────┬─────────────────────────────────────────────────┘
│ types passphrase into dashboard's unlock form

┌───────────────────────────────────────────────────────────┐
│ Daemon (Fastify, your machine, 127.0.0.1 only) │
│ • Decrypts keystore in RAM on unlock │
│ • Holds private key in memory (locked = key zeroed) │
│ • Enforces policy + signs intents │
└──────────┬─────────────────────────────────────────────────┘
│ bearer token (rotated per session, chmod 0600)

┌─────────────────────────┐ ┌─────────────────────────────┐
│ Dashboard (browser) │ │ MCP server (subprocess) │
│ • Read-only UI mostly │ │ • Spawned by agent │
│ • Unlock form │ │ • Forwards tool calls │
└─────────────────────────┘ │ to daemon over HTTP │
│ • Never sees the key │
└──────────┬──────────────────┘
│ stdio

┌─────────────────────────────┐
│ AI agent │
│ (Claude Code, Cursor, etc.) │
│ • Calls MCP tools │
│ • Never sees the key │
│ • Never sees the bearer │
└─────────────────────────────┘

Keys never leave the daemon process. The agent, the MCP server, and the dashboard all interact with the keystore through API calls that gate on the bearer token. The agent never sees the bearer either — the MCP server reads it from disk (~/.axon/session.token) at spawn time.

Keystore

~/.axon/keystore.json is a JSON-encoded encrypted blob:

  • KDF: PBKDF2-SHA256, 250,000 iterations (Web Crypto subtle.deriveBits)
  • Cipher: AES-256-GCM
  • Nonce: 12-byte random per write
  • MAC: GCM auth tag (16 bytes)
  • File mode: 0600 (owner read/write only)

Without the passphrase, brute-forcing the keystore is computationally infeasible (250k PBKDF2 rounds × the size of the password search space).

Why not Argon2id? PBKDF2 ships in Node's stdlib and the browser's Web Crypto API — no native module dependency. Argon2 would be stronger per round but ship-friction trades off. We tune the iteration count up if/when the threat model demands it.

Recovery phrase

A BIP-39 24-word phrase is generated alongside the keystore. It is printed once during the wizard and never stored anywhere. Write it down, paste it into 1Password / Bitwarden, etch it into metal — but make sure you have it.

Recovery via axon import --mnemonic "..." re-derives the private key from BIP-44 path m/44'/60'/0'/0/0 (MetaMask + Ledger compatible).

Network exposure

The daemon binds to 127.0.0.1 by default. All routes (except /v1/health and /v1/session/refresh) require the bearer token in the Authorization header.

  • The bearer token is regenerated on every daemon boot (so a leak from a previous session expires automatically)
  • The token is delivered to the dashboard via the URL fragment (#token=…), which never hits the server (fragments are client-side only)
  • The MCP server reads the token from ~/.axon/session.token, scoped to the user's UID

Binding to 0.0.0.0 (e.g., --host 0.0.0.0) requires the explicit escape hatch --i-understand-network-exposure. The daemon refuses to start otherwise. Don't expose Axon to LAN or the internet.

Audit log

Every signed intent, fill, policy change, and lifecycle event is appended to ~/.axon/audit.db (SQLite). Each row carries:

  • prev_hash — SHA-256 of the previous row
  • hash — SHA-256 of (this row's contents + prev_hash)

This is a tamper-evident hash chain. Any modification to a past row breaks every subsequent hash. axon audit verify recomputes the chain end-to-end and reports the first break.

Periodic settlement anchors roll up the chain head into a stable marker — the chain is fork-resistant even across daemon restarts.

Policy engine

Every signing path goes through the risk guard which loads policy from the encrypted policy file (~/.axon/policy.json.enc, encrypted at the same passphrase as the keystore). Defaults:

  • Daily notional cap (per agent + per wallet)
  • Max leverage (per venue)
  • Asset whitelist
  • Builder fee cap (10 bps hard ceiling)
  • Kill switch (disarmed by default)

Policy edits require the unlock passphrase — the policy file is encrypted, so a stolen disk image can't tamper without the password.

Kill switch

A hold-to-arm UI in the dashboard. Once armed:

  • Every signing path returns KILL_SWITCH_ARMED immediately
  • The MCP server's open/close perp tools refuse to call
  • Audit log records the arm event
  • A native notification fires (desktop app)

The kill switch survives daemon restarts (persisted at ~/.axon/kill-switch.json).

Trust model summary

You trust:

  1. Your local machine — disk encryption (FileVault, BitLocker) covers the at-rest case
  2. Your unlock passphrase — entered through the dashboard's form
  3. Axon's open-source code — the daemon, MCP server, dashboard, and CLI are all in this repo

You do not trust:

  1. Your AI agent to behave — the policy engine + kill switch are the rails
  2. The network — daemon binds to localhost only
  3. Apps from other origins — bearer token + CORS lock the daemon to its own dashboard

Reporting security issues

Email security@strykr.ai with details. We don't currently have a public bug bounty; we'll respond within 72 hours and coordinate disclosure.

For non-sensitive issues, open a GitHub issue.