Trust & security
RustAG is honest about where the MVP ends. This page covers the hosted threat model, the cryptographic trust layer, service-level objectives, and a plain list of what does and does not work yet.
Security & threat model
The threat model is scoped to the hosted, multi-tenant product. The open-source local CLI runs entirely on your own machine and is out of scope — when you run a stagenet locally, you are your own trust boundary.
Tenant isolation
Tenants are mutually distrusting and may run arbitrary, untrusted Solana program bytecode inside their stagenet — that is the point of the product. The hard boundary is therefore between one stagenet runtime and everything else; a stagenet executes adversarial code and is treated as hostile. Cross-tenant isolation is defended in depth:
- Each stagenet has its own account store and data directory — no shared account namespace.
- Every
/v1/*query is filtered by the authenticatedtenant_id; a lookup that doesn't match returnsNotFound, so one tenant cannot even enumerate another's slugs. - Each stagenet runs as a separate OS process today; production hardening runs each pod under
runtimeClassName: kata(Firecracker microVM) with per-tenant CPU/memory quotas. - API keys are SHA-256-digested at rest, shown once, tenant-scoped, and revocable; upstream RPC keys (which carry
?api-key=) are deliberately never logged.
Attestation integrity
The rustag-attest crate produces a signed, Merkle-rooted proof of the exact mainnet-derived state a program was tested against. The signing digest is built from a fixed field order with length-prefixed, domain-tagged fields rather than from JSON — JSON key/whitespace ordering is not canonical and must never affect what a signature commits to. The state_root is a binary SHA-256 Merkle root over the pubkey-sorted account set, with leaves (0x00) and nodes (0x01) domain-separated to prevent second-preimage attacks. Account leaves commit to consensus-visible fields only; the internal dirty/clean/pinned bookkeeping is deliberately excluded.
let attestation = Attestation::create(manifest, &keypair); // Recompute the state root from `accounts`, confirm it matches the// manifest, and check the Ed25519 signature — no server, no network.let report = attestation.verify_against(&accounts)?;assert!(report.is_valid()); // Forging any signed field (e.g. att.manifest.slot = 999) breaks the signature.Audit-log tamper-evidence
AuditLog is an append-only, hash-chained log — the SOC 2 groundwork. Each entry carries a monotonic seq, a prev_hash, and its own hash; the chain is genesis-anchored at the all-zero hash. Any insertion, deletion, or edit anywhere in the log breaks the chain from that point forward, and verify() returns Err(index) at the exact first inconsistent entry.
Service levels
SLO targets apply to the hosted control plane and stagenets — local/CLI stagenets run on your own machine and are best-effort. Targets are deliberately modest; under-promising at this stage is intentional.
| Objective | Target | Status |
|---|---|---|
| Control-plane API (/v1/*) uptime | 99.5% / mo | Target |
| Cloud stagenet creation within 30s | 99% of attempts | Target — health-gated start |
| getAccountInfo (cache hit) | p99 < 50 ms | Target |
| getAccountInfo (cold mainnet fetch) | p99 < 2 s | Target |
| Oracle price staleness (realtime) | p99 < 2 s | Target |
| Cross-tenant data-access incidents | hard 0 | Enforced + tested |
| Stagenet wake-from-sleep | p99 < 15 s | Aspirational |
The error budget is the inverse of the availability target (0.5%/month); when exhausted, reliability work ships before features. Failure modes are explicit: a cold-fetch mainnet RPC failure serves stale cached data with a warning and never panics; on a realtime WebSocket disconnect the caller reconnects while Clean accounts keep their last value and Dirty/Pinned accounts are never touched.
Known limitations
RustAG implements the Phase 2 spec with deliberate single-node substitutions — each satisfies the same contract as the eventual target, so the swap is additive, not a rewrite:
- Streaming mirror — an
accountSubscribeWebSocket instead of native Yellowstone gRPC. Sub-second push with zero lock-in. Live filter updates on an open subscription and built-in auto-reconnect are not yet done. - Datastore — SQLite + moka instead of Postgres + Redis; correct for the single-node MVP. The Postgres migration and Row-Level-Security policies are not yet done.
- Multi-tenant isolation — child-process isolation instead of Kata + Kubernetes; the
kube-rsorchestrator and a running Kata cluster are not yet done. - Auth & billing — SHA-256-digested API keys instead of Clerk + Stripe; billing is deferred.
- Observability —
tracingspans + a JSON/api/metricstime-series; a Prometheus-format/metricsscrape endpoint is deferred.
Other open items: cargo audit / cargo deny are not yet wired into CI; examples exist under examples/ but CI does not execute them; crates are not yet published to crates.io / npm; client-compatibility is validated against @solana/web3.js but not yet the @solana/kit or Rust solana-client matrices.
FAQ
What does it cost to read real mainnet state?
Zero SOL. On first access RustAG lazily mirrors the mainnet account into the stagenet (mainnet data is public), then keeps oracles fresh in the background. You can airdrop unlimited SOL — no faucet, no cap — so an integration suite can actually run. Airdrops are capped only to prevent u64 overflow.
Is this safe to run — can I break anything on-chain?
No. A stagenet is an isolated environment; transactions you send execute locally against mirrored state and spend zero real SOL. Reading mainnet only pulls public account data on demand; it never writes to mainnet. You test unaudited code here precisely so you don't test it on mainnet.
How is this different from solana-test-validator?
The test validator gives you an empty local cluster — no real Raydium pools, no real Pyth prices, and you can't fork the chain the way you can on Ethereum. RustAG is a mainnet-mirroring stagenet: it lazily pulls real, current mainnet accounts on first access, so your code runs against live DeFi state.
How does it compare to Bankrun / LiteSVM?
RustAG's runtime is LiteSVM-backed, so it shares that fast in-process execution model — but it adds the lazy mainnet mirror, a dirty/clean/pinned state machine, SQLite persistence, a Solana-compatible JSON-RPC + WebSocket + REST server, a CLI, a dashboard, and (Phase 3) signed attestations and an exploit scanner. Bankrun/LiteSVM are in-process test harnesses; RustAG is a persistent, drop-in cluster.
Is execution deterministic / reproducible?
Yes, and it's provable. rustag attest writes a signed, Merkle-rooted manifest committing to the exact pubkey-sorted account set and ordered transaction outcomes; rustag verify <file> checks it offline with no server and no network, exiting non-zero if INVALID. The rustag-replay crate adds checkpointing and deterministic journal replay.
Can I run a full Jupiter swap end-to-end today?
Your own deployed program reading real mainnet state works now. Executing a foreign on-chain program by loading its BPF bytecode (a complete Jupiter swap end-to-end) is the remaining boundary — it needs the Phase 2+ program-loading. See Known limitations.
Can I gate CI on this?
Yes. rustag scan -s <name> --fail-on <severity> scans recorded transactions for exploit signatures and exits non-zero at or above the given severity, so it's a CI gate, not just a report. The GitHub Action spins up an ephemeral per-PR stagenet, runs your command against real mainnet state, posts a PR summary, and tears down.
rustag attest -s demo # -> .rustag/demo.attestation.json (signed, Merkle-rooted)rustag verify demo.attestation.json -s demo # offline; exits non-zero if INVALIDrustag scan -s demo --fail-on high # CI gate: exits non-zero at/above 'high'