Security model
Written for a reviewer. This doc states the trust assumptions, the threat model, and — for each actor — exactly what they can and cannot do, with the enforcement mechanism for each claim. Read Custody first.
Trust assumptions
- The Solana runtime and the Swig program are trusted. Olbos's custody guarantees reduce to Swig's role enforcement. Swig is open-source and the mainnet binary is what runs; Olbos loads it read-only into the test validator for the rug test, so the same code path is exercised in CI as in production.
- The owner's key is trusted to the owner. If the owner's root key is stolen, the thief is the owner. Olbos cannot and does not protect against that — it's the owner's wallet security.
- The engine key is NOT trusted. The entire design assumes the engine may be fully compromised. The guarantees below all hold under that assumption.
Actors and capabilities
The owner (custody root)
- Can: withdraw funds, kill/unkill the strategy, revoke the engine's role on-chain, do anything (root). Authenticated by wallet signature.
- Cannot: be locked out. Role 0 is immutable; no action by the engine or Olbos can remove the owner's authority.
The Olbos engine (scoped role)
-
Can: deposit USDC into approved venues; unwind positions from them; up to a daily USDC cap. Fee-pay transactions.
-
Cannot:
- transfer funds to any address — the SPL token program is not in its scope;
- exceed the daily cap —
tokenRecurringLimit, enforced on-chain; - touch unapproved programs —
programLimit; - escalate its own authority — it lacks
manageAuthority; - act after revoke — its role ceases to exist on-chain.
Every "cannot" is enforced by the Swig program at execution time, not by Olbos code.
An agent (the funding caller)
- Can: pay to deploy/fund/rebalance/withdraw its own strategy. Set the owner.
- Cannot: see or use break-glass. The kill/revoke tools are not registered in agent MCP sessions — an agent cannot even enumerate them. "Agents pay, owners sign" is structural, not a permission check.
A third party (no keys)
- Can (hosted mode): nothing without a valid wallet-signed session.
- Can (open/localnet mode): read any strategy whose ID they know. Open mode is for single-tenant/operator use and is not the public posture.
The rug test
The custody claim is verified by an executable test suite (in the repository, spikes/swig-custody) run
against the real mainnet Swig binary loaded into a local validator, and
re-run against the live engine role on a deployed strategy. It asserts:
- ✅ the scoped engine role can deposit into an approved venue (positive control);
- ❌ the engine cannot transfer funds to an attacker address — rejected
on-chain (
custom program error 0xbbe— token program not in scope); - ❌ the engine cannot grant itself wider authority — rejected (lacks
manageAuthority); - ✅ the owner can revoke the engine's role unilaterally.
Worst case of full engine compromise: capital is stuck inside approved venue programs, never exfiltrated, bounded by a daily cap, and revocable by the owner at any time.
The risk gate
Separate from custody (which bounds theft), the risk gate bounds bad
allocation. It is a pure function (risk.ts): given a portfolio state, a
policy, and a proposed action, it returns ok/blocked with a reason. Because it's
pure and side-effect-free, it's exhaustively unit-testable and cannot be
bypassed — the executor refuses any plan that didn't clear it. It enforces:
- per-venue allocation caps (a move can't push a venue over its cap);
- the liquidity buffer (wallet-decreasing actions can't eat the buffer);
- the venue allowlist.
See Strategies for the economic gates (break-even, dust, min-improvement) layered on top.
Execution safety
- Simulate-first. Every transaction is simulated against live state before sending; simulation failures are logged and never broadcast. This is the last line against a malformed or newly-invalid instruction (e.g. an SDK that drifted past mainnet state).
- At-most-once intents. Deterministic intent IDs + a log check before broadcast mean a crash never double-executes. See Architecture — intents.
- Fail-safe kill. Consecutive cycle failures, or a venue health-invariant violation (e.g. a borrow appearing on a lend-only Marginfi account), trip an automatic kill and an alert.
Owner authentication: Sign-In With Solana
Owner actions and (hosted) reads are authenticated by a stateless signed capability, not a session store:
message = "olbos.tech session\nwallet: <pubkey>\nexpires: <unix>"The wallet signs this once; the signature is the session. It's verified per
request (ed25519 against the claimed pubkey, with an expiry check) — no server
secret, no database, nothing to leak or revoke server-side. The same primitive
authorizes break-glass with a per-action message
(olbos:kill:<strategyId>:<ts>, ±300s freshness window to bound replay).
This reuses one verification path for both reads and break-glass, which keeps the trusted surface small.
Multi-tenant isolation
In hosted mode (OLBOS_REQUIRE_AUTH=1):
GET /v1/strategiesreturns only the signed-in owner's strategies.- Per-strategy reads (
/positions,/risk-status,/audit-log) require a session whose owner matches the strategy's owner — else403. - The audit log is correlation-scoped:
tx.*andx402.*events are keyed by intent ID / endpoint, not strategy ID, so they're linked back to a strategy by correlation ID. One tenant never sees another tenant's payments or transaction signatures. - The global
/v1/eventsoperator stream is disabled (404).
Verified end-to-end: no-session → 401; owner sees only their strategy; owner→own → 200; owner→other → 403; global events → 404.
Known limitations & residual risks
Honesty is part of the security posture. Before deploying external capital:
- No external audit yet. This document is the input to an audit, not a substitute for one. The custody flow, session auth, and rail need independent review. This is the top blocker.
- Engine key is a file. Compromise is bounded by the Swig role (no theft, capped, revocable) but the key should move to an HSM/KMS for production.
- Borrow surface on Marginfi.
programLimit(marginfi)admits borrow instructions. A compromised engine could open a leveraged position (bounded liquidation-penalty losses, never exfiltration). Mitigated by the watch loop's no-borrow health check, which trips the fail-safe — but it's a residual surface worth an auditor's eyes. - Facilitator dependency. Payments depend on a single x402 facilitator (PayAI). Its compromise can't move custodied funds, but it can disrupt payments / settlement.
- SDK / mainnet drift. Venue SDKs lag mainnet state (already observed). The simulate-first gate catches the resulting malformed instructions, but adapter health monitoring is needed so drift surfaces as an alert, not a silent stall.
- USDC depeg / single-asset concentration. All capital is one asset. Not a code risk, a market risk to disclose.
See Operations — hardening for the full production checklist.
Next: Strategies · Venues · Operations.
