v0.2.7 — security hot-fix#3
Merged
Merged
Conversation
Decode the base64 transaction Jupiter returns and refuse to sign unless the on-chain instructions match the user's intent: input mint and amount, output mint, and signer (fee-payer = wallet pubkey). Compromised Jupiter responses or MITM tampering on a custom RPC URL can no longer redirect funds. The verifier tolerates compute-budget and ALT-extend instructions; unknown extras are allowed but at least one SPL Token transfer/transfer_checked from our input ATA at the expected amount is required. ALT-resolved hot-pool ATAs fall back to a best-effort static-keys lookup; full ALT resolution would require an RPC fetch and is out of scope for this hot-fix. Adds wallet::sign_jupiter_swap as a thin wrapper around sign_transaction; sign_transaction itself stays generic so transfer_sol/transfer_spl paths continue to work unchanged. ExpectedSwap is built once in execute_with_retry from SwapParams and threaded through execute_order, execute_managed_order, and execute_metis_order — every path that ends up at Jupiter is guarded. Tests: 11 parser-level scenarios in wallet::verify::tests (4 reject paths + happy + tolerance + roundtrips) plus 2 wallet-level integration tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rwa keys generate and rwa keys import now write key.age (passphrase-encrypted) by default. --allow-plaintext opts out (with stderr warning); the legacy --encrypt flag is hidden but still accepted to preserve scripts that opt in explicitly. rwa keys show prints a deprecation warning when it reads a plaintext key.json. prompt_new_passphrase rejects passphrases shorter than 12 characters and rejects digits-only passphrases — short or low-entropy passphrases were a trivial offline-brute-force vector against age's scrypt-default KDF when combined with a stolen key.age file. When the passphrase is read from RWA_PASSPHRASE, a one-time stderr warning is printed (gated by OnceLock so multiple call sites — keys.rs and gm/mod.rs — do not spam the user). The warning explains shell-history, ps -E, and core-dump leakage paths and points users at interactive entry. Tests: keys_generate_default_creates_age_file, keys_generate_allow_plaintext_creates_json_file, passphrase_min_length_enforced, passphrase_digits_only_rejected, passphrase_strong_ok, passphrase_exactly_12_nondigit_ok. Breaking-change note (UX, not data): scripts that relied on the previous default of plaintext output should add --allow-plaintext or migrate to encrypted keys. Existing key.json files remain readable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bump workspace version 0.2.6 -> 0.2.7 and document the security hot-fix in CHANGELOG.md (Verify Jupiter swap instructions, encryption-by-default, min passphrase length, RWA_PASSPHRASE env warning). Test count workspace-wide: 234 -> 236. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The initial v0.2.7 verifier required the wallet pubkey at signer index 0 (fee payer). That broke every gasless route: Jupiter Z (RFQ) puts the market maker at index 0, and Ultra gasless adds Jupiter as a secondary fee payer — in both cases the wallet is a signer at index 1+. Search for the wallet pubkey across all signer slots [0..num_required_signatures) instead of pinning it to the fee-payer slot. The input-transfer authority check is unchanged and is what actually confirms the wallet authorized the debit, so loosening the signer-position check does not weaken security: a tx where the wallet is not a signer at all still fails (new test gasless_rejects_when_owner_not_signer). Tests: 11 -> 13 in wallet::verify::tests. New regression accepts_gasless_with_external_fee_payer mirrors the Jupiter Z layout (MM at index 0, owner at index 1, both signers, TransferChecked authority = owner). Workspace total 236 -> 238. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Security hot-fix release. Three independent hardenings on the wallet/keys path, plus a follow-up fix that keeps Jupiter Z (RFQ) and Ultra gasless flows working under the new verifier.
crates/ondo/src/wallet/verify.rs). Decodes the base64 transaction Jupiter returns and refuses to sign unless the on-chain instructions match the user's intent: input mint/amount, output mint, and that the wallet pubkey is among the signers. The input-transfer authority check independently confirms the wallet authorized the actual debit.keys generate/keys import. Plaintextkey.jsonrequires explicit--allow-plaintext(with stderr warning). Existing plaintext keys remain readable;keys showwarns when it sees one.RWA_PASSPHRASEis read from env.[0..num_required_sigs)rather than requiring it at index 0. Jupiter Z makes the market maker the fee payer at index 0; Ultra gasless adds Jupiter as a secondary fee payer. The wallet is a signer at index 1+ in both cases.Why now
Doc-cross-check against current Jupiter Ultra API caught the gasless regression before merge — the initial verifier would have refused every gasless swap with
OwnerMismatch. The follow-up fix adds 2 regression tests (accepts_gasless_with_external_fee_payer,gasless_rejects_when_owner_not_signer).Test plan
cargo test --workspace— 33 (cli) + 199 (ondo lib) + 6 (ondo integration) = 238 tests, all green (was 190 on main, +48)cargo clippy --workspace --all-targets -- -D warnings— cleancargo build --release— cleanwallet::verify::testscovers 4 reject scenarios + happy path + tolerance + gasless accept + gasless reject (13 tests)rwa keys generate(createskey.age),rwa keys generate --allow-plaintext(createskey.json+ warn),rwa buy TSLAon 10 --dry-run,rwa portfolioKnown limitations (tracked for v0.3)
simulateTransactionis documented inline and planned for v0.3.🤖 Generated with Claude Code