This is the security policy for the Stellar Agent Wallet — a Stellar wallet for AI agents,
shipping the stellar-agent CLI binary and the stellar-agent-mcp MCP stdio server.
It covers which versions are supported, how to report a vulnerability, the scope of the
current public alpha, and a high-level summary of the security model.
This project is a pre-1.0 public alpha. Only the latest 0.1.x alpha is supported.
| Version | Supported |
|---|---|
latest 0.1.x |
Yes |
any older 0.1.x |
No |
There are no backports to earlier alpha builds. Install the latest tagged release (see the README for the release archives and crates.io install paths) or build from source, and report against that latest build.
Report security issues privately through GitHub private security advisories. Do not open a public issue, discussion, or pull request for a security report.
- Go to the repository Security tab.
- Choose "Report a vulnerability".
- Submit the advisory with the details below.
Please include:
- A description of the vulnerability and its impact.
- Steps to reproduce, including any required profile configuration or environment.
- The affected version or commit (the
0.1.xbuild, or the source commit you built from). - Any proof-of-concept input, log excerpt, or transaction envelope — with secrets redacted.
Do not include real secret seeds, private keys, or keyring secrets in a report. The wallet never logs secret material or raw argument values, and reports should follow the same discipline; a redacted strkey or a SHA-256 digest is enough to identify an affected account or envelope.
We handle reports under coordinated disclosure and aim to acknowledge and triage valid reports in good faith as promptly as we can. We will keep you informed as we investigate and work toward a fix. Please give us reasonable time to address an issue before any public disclosure.
Keep the following properties of the current alpha in mind when assessing impact.
- Writes and signing are testnet-only. Read-only commands accept both
stellar:testnetandstellar:mainnet, but every write and signing surface structurally refusesstellar:mainnetwith wire codenetwork.mainnet_write_forbidden, except MPP, which refuses withmpp.network_forbidden. There are no consent-gated mainnet write exceptions. Commands that take--networkrefuse before any RPC call or signing. The CLI write commands, includingvault deposit,vault withdraw,trade,trustline, andpool init, refuse a mainnet profile before signer access and any RPC call.smart-account rules verify-pins,smart-account signers list, andsmart-account signers refreshkeep their local audit writes but load no signer, sign nothing, and write nothing on chain, so they accept a mainnet profile. The MCP DeFi, sign-and-submit, commit, and rule-commit tools refuse it at entry, before the policy gate and any RPC request. At the submit layer, which every write goes through including the profile-driven flows, a declared mainnet network passphrase and a known mainnet RPC URL are each refused at zero RPC cost. Beyond those two the endpoint is asked which network it serves, that answer is authoritative over the declaration, and every signature on the envelope must verify under that network's id. Friendbot funding is scoped totestnetandfuturenetand structurally refusesmainnet(network.friendbot_mainnet_forbidden). - The threat model centers on an autonomous agent transacting under wallet guardrails: a policy engine, an out-of-band operator-approval spine, and a tamper-evident audit log. Reports that demonstrate an agent escaping or bypassing these guardrails — for example, executing a signing action that should have been denied, forced to approval, or recorded — are in scope and of high interest.
- A report that lets an autonomous agent reach
stellar:mainnetfor a write or signing action, despite the structural refusal, is in scope.
Out of scope: defects in your own keyring backend, operating system, RPC endpoint, or anchor; and issues that require already holding the wallet's keyring secrets (the seed, nonce key, or HMAC keys), since custody of those secrets is the security boundary.
For the full design see docs/concepts.md and docs/maintainers/security-internals.md. Key properties:
- Keyring custody. The signing seed, the nonce key, and the HMAC keys live in the platform keyring. A profile is a per-environment TOML config that names keyring entries by a service-plus-account keyring entry reference instead of holding keys. An endpoint URL can embed a credential in its path or query on any network. Mainnet refuses only a username or password in the URL. Profile TOML is safe to back up only when no endpoint URL embeds a credential.
- Unlock window with zeroize and mlock. When a CLI command signs with a seed read from a
--*-secret-envvariable, the 32-byte seed is loaded into an unlock window. The window is a short, TTL-bounded period (default 30s, hard cap 600s) during which the seed is resident in pinned, zeroize-on-drop memory (mlock). On every drop path (normal return, error propagation, or panic) the seed is zeroized and the lock released. Keyring signers, the Ledger, and the MCP server never enter the window. A keyring signer loads its key for each signature into a zeroize-on-drop buffer without a memory lock. - Policy engine plus operator approval. The MCP dispatch gate and the CLI value-moving
verbs evaluate each action to Allow, Deny, or RequireApproval. Read-only CLI commands
such as
balancesevaluate no policy. The Noop engine allows everything on testnet, allows read-only on mainnet, and refuses mainnet destructive actions (policy.engine_required); the V1 engine evaluates signature-verified typed criteria with first-match default-deny. When approval is required, the action is recorded in a per-profile pending-approval store and released only when the operator runsapprove, which mints an HMAC-SHA256 attestation proving the keyring holder approved it. The attestation is constant-time verified and binds the approval nonce, the envelope SHA-256, and the OS process uid. - Tamper-evident audit log. Approvals, submission lifecycle events, and explicit
authorization, key-management, and lifecycle events are appended to a per-profile
hash-chained JSONL audit log. A balance read writes no row.
The raw argument map is recorded as key names only. Typed value events retain each
leg's amount, asset, and redacted destination. The chain is verified end-to-end with
audit verify. The value-moving signing and submitting verbs arepay,claim,accounts create,trustline,trade,vault, the x402 payment tools, and the MPP charge commit. Each proves its audit writer is acquirable BEFORE the signing key is touched or a transaction is submitted. It refuses withaudit.chain_key_unavailablerather than signing unaudited when the profile's audit chain-root key was never minted. This pre-flight fails closed for a persisted profile.pay,claim, andaccounts createfall back to the zero-config synthesized profile when no profile was named and nodefault.tomlfile exists. That profile stays fail-open for this check, matching its documented no-profile-required posture. The SEP-43 sign-only pair (signTransaction,signAuthEntry) runs the same pre-flight and records anopaque_payload_signedaudit row when the signature is produced. The x402 tools writex402_payment_authorizedbefore the signed authorization leaves the wallet in the RPC re-simulation, and withhold it when that row cannot be written. An approval or rejection takes effect only after its audit row is durable, in the log or in the log's outbox. A queued row reaches the log before the consumer of the approval loads a signing key.
The core library compiles under #![forbid(unsafe_code)]. Logging redaction is enforced
throughout: secret material is never written to logs, and account, strkey, and
transaction-hash fields are truncated at the output boundary.