Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Plan

Work tracked in this repository. Update it in the same change as the work it tracks.

Next

  1. Cargo’s native minimum publish age (not before 2026-11-19). Rust 1.100 (due 2026-11-12) stabilises registry.global-min-publish-age; on 1.98 the key only warns. Wait until the release is itself at least 7 days old (later if a point release follows), then:
    • bump rust-toolchain.toml to 1.100.x, and the Dockerfile’s RUST_IMAGE tag and digest to match (scripts/ci/docker-toolchain.sh fails until they agree);
    • add .cargo/config.toml with [registry] global-min-publish-age = "7 days";
    • remove scripts/ci/crate-age.sh and its line in scripts/ci/check.sh, and drop “dependency age” from the check recipe comment in the justfile (Cargo enforces it at resolve time instead);
    • confirm cargo update then respects the age and just check and CI still pass.

Backlog

  • Instruction table: enforce mode (high): report mode is built (instruction_table = { path, sha256, mode = "report" }, findings instructions, accounts and profile in reports and audit entries). Remaining: mode = "enforce", which refuses on any finding (feat!:, since it refuses what was signed before), after report mode has run against real play long enough to show the table and forge-mcp’s payloads agree (Hardening proposals, 4).
  • Redeploy watch and holds (low; high before mainnet): read each game program’s last-deploy slot from its program-data account; on a change, write a program-redeploy audit entry and, with on_program_redeploy = "hold", refuse auto signs per instruction family until programs accept or a passing canary run over canary_dir (payloads with expected outcomes, where an unexpected success fails and an RPC error releases nothing) (Hardening proposals, 8).
  • Reserved headroom for recovery (optional, after the instruction table): reserved_per_minute and reserved_lamports kept for messages whose game instructions are all in reserved_families, classified from the table, never from summary (Hardening proposals, 9).
  • Audit store: the log is one JSONL file that only grows. Signs now read only its end and the console only what was appended (with a full read at most once a minute), but audit verify and the console’s start-up and minutely check still read it all, and its size is unbounded. Revisit when a real audit.jsonl passes 100 MB (roughly 100,000 signs) or audit verify takes more than 10 s. Decide between rotation (daily segments, the hash chain carried across files, audit verify and the console reading all segments) and a store with an index, keeping the chain’s guarantees and the repair procedure in SECURITY.md.
  • RPC latency (optional, when a trace shows the need): a sign through serve takes about 1.7 s against rpc1.z.ink, almost all RPC round trips. Candidates, each with the trace symptom that would justify it: a keep-alive call while serve is idle if the server drops connections sooner than 2 minutes; one retry of a read after a transport error on a reused connection; more idle connections per host for parallel signs; JSON-RPC batches for the five reads before sending, if the RPC accepts them; poll-schedule tuning under congestion (RPC calls and latency).
  • Approval outside the terminal: confirm reads /dev/tty, which an agent running as the terminal’s user can type into through a multiplexer or a terminal app’s scripting API, and which serve in a container usually lacks. Move approval to a channel that checks who answers, such as a Unix socket that accepts only a configured approver uid (peer credentials), so confirm keys also work under serve.
  • serve thread cap and RPC budgets: one thread per request is unbounded today; cap concurrent requests and answer the rest with 503. check has no rate limit either: add [serve] check_per_minute per token (429) and keep polling of already-sent transactions ahead of check and preflight RPC calls; the genesis hash is already cached per process (Hardening proposals, 10).
  • MCP: drive /mcp with a real client in tests (optional): the handler’s messages are checked against the Rust MCP SDK’s types (rmcp, a dev-only dependency), but no client has been run against a live service. The SDK’s own client would do it at the cost of an HTTP stack in the test build (MCP conformance).
  • MCP: progress during a long sign (optional): the 2026-07-28 revision lets a server answer a request with an event stream and send progress on it. The service answers with one JSON object, so a client sees nothing until the report. Needs streaming, which the hand-written handler does not do (Design decisions).
  • SPL Token authority check: refuse top-level SPL Token Transfer / Approve (and their Checked forms) whose authority is the signing key, using the public SPL Token instruction layout. Today such instructions pass on program id alone, so a hand-written payload could move tokens from a token account the key owns.
  • Key stores: an encrypted-file backend (age, passphrase at serve start) and macOS Keychain / Secret Service backends, for running outside a container. Check first whether a Keychain item can be isolated from other processes of the same user. For age: write files at a scrypt work factor of at least 2^18 and refuse to load one below it; use age rather than an own Argon2id envelope; take the passphrase from the terminal or systemd credentials, never an environment variable or argument (Hardening proposals, 11).
  • Read-after-write (optional): document the report’s slot as the minimum context slot for the caller’s next reads, and pass the key’s last landed slot as minContextSlot on the signer’s own pre-sign reads (Hardening proposals, 12).
  • systemd unit (low): a chapter with an example hardened unit (dedicated user, ProtectSystem=strict, empty capabilities, SystemCallFilter=@system-service, LimitCORE=0, MemorySwapMax=0, TimeoutStopSec=230, LoadCredentialEncrypted= for the key passphrase) checked with systemd-analyze security (Hardening proposals, 13).
  • Secrets in memory: Keypair already zeroes itself on drop (solana-ed25519’s default zeroize feature), and key files, payload text and partial signers are read into Zeroizing buffers. What is left: buffers that grow while being filled (serde_json parsing a key file into a Vec<u8>, read_to_string from stdin or an HTTP body) free copies that are never wiped, so reserve the full size up front; serve’s MCP route parses the request into a serde_json::Value that holds partial_signers and is dropped without wiping; and moves of a Keypair leave copies on the stack. Ahead of those, guard against copies outside the process: turn off core dumps (RLIMIT_CORE 0, and PR_SET_DUMPABLE 0 on Linux) and consider mlock for key pages so they are not swapped out. Low priority: all of this assumes a memory disclosure in a process the agent’s OS user cannot read.
  • Local-validator integration tests: prove the outcomes that only fake-chain unit tests cover today: failed (exit 12, landed with a program error), an expired blockhash with no status found (unknown, exit 14; exit 13 is retired), and the one-time re-sign on BlockhashNotFound.
  • Releases: reproducible builds (verify two builds match byte for byte), an SBOM attested next to the provenance, and an external review before any mainnet use. Checksums and build provenance attestations ship with each release since v0.4.0.