Skip to content

V12 issues - #126

Open
illuzen wants to merge 39 commits into
mainfrom
illuzen/v12-2
Open

V12 issues#126
illuzen wants to merge 39 commits into
mainfrom
illuzen/v12-2

Conversation

@illuzen

@illuzen illuzen commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Addresses the V12 security audit findings in v12-issues.md.

Scope: only findings with Validity: Unreviewed. V12 marks 314 Low findings as Validity: Invalid (likely incorrect); those are excluded from this analysis and are not treated as open work.

Severity Unreviewed (in scope) Invalid (excluded)
High 20 0
Medium 30 0
Low 11 314
Info 2 0

This PR remediates all 20 High and 30 Medium Unreviewed findings (with red→green tests where applicable). The 11 Unreviewed Lows are listed below; most remain open for follow-up. A few Invalid Lows were hardened opportunistically and are noted separately (out of audit scope).

34 commits on illuzen/v12-2. Library suite: 211+ tests passing.

High (20/20 addressed)

ID Title Outcome
#159453 Wallet mnemonic/seed as CLI args Fixed — always hidden prompt
#159924 Malformed wallet aborts listing Fixed — skip bad files; validate SS58
#160053 MultisigCreated mis-attribution Fixed — correlate creator/signers/threshold/nonce
#160582 Raw --password CLI credentials Fixed — reject at helper boundary
#160592 Keystore permissive permissions Fixed — dir 0700, files 0600
#160593 Unauthenticated address redirect Fixed — integrity check on decrypt
#160594 Unauthenticated metadata substitution Fixed — passwordless paths stop trusting envelope
#160598 Filesystem races in wallet storage Fixed — locks, random temps, name checks
#160605 Failed legacy migration bypass Fixed — fail closed on migration save
#160611 Watched txs succeed when absent Fixed — missing extrinsic → error
#160612 Unsafe retries duplicate txs Fixed — single submit, no nonce-bump retry
#160624 Unverified RPC signing context Fixed — Quantus runtime identity gate
#160655 Batch not atomic Fixed — utility.batch_all
#160708 Password-file permission checks Fixed — Unix owner-only required
#160716 Legacy AES key alongside ciphertext Already fixed (e0be480); residual: refuse re-persisting digests
#160737 Wallet creation not atomic Fixed — exclusive create / hard_link
#160748 Ephemeral mnemonic strands funds Fixed — require persisted mnemonic
#160754 Transfer events unbound Fixed — match from/amount/count
#160773 Self-update without integrity check Fixed — verify published SHA-256
#160791 Unvalidated RPC token properties Fixed — fail-closed decimals/symbol/ss58

Medium (30/30 addressed)

ID Title Outcome
#159340 Version/nonce panics on decrypt Fixed
#159469 Exported mnemonic on stdout Fixed — require --output (0o600)
#159662 Storage pagination loop/overflow Fixed
#159890 Spent transfers reported available Fixed
#159916 Single-block over-limit abort Fixed — offset pagination
#160052 Duplicate signers Fixed — sort+dedup
#160103 Wormhole --secret argv Fixed — --secret-file
#160105 Secrets not zeroized after proof Fixed
#160110 Unbounded Merkle depth Fixed
#160591 Wallet secrets retained Fixed — Drop/zeroize/redacted Debug
#160595 Wallet name path escape Fixed (with #160598 name validation)
#160625 Unbounded tx-status waits Fixed — deadlines
#160640 Malformed pubkey panic Fixed — InvalidPublicKey
#160652 Token metadata / decimal format Fixed — checked_pow + validation
#160656 Transfer data / chain decimals Fixed
#160660 Removal missing member rank Fixed — required --min-rank
#160667 Recursive wormhole unfinalized Fixed — finalized snapshots
#160674 Delay conversion overflow Fixed — checked helpers
#160697 Circuit artifacts unauthenticated Fixed — manifest.json SHA-256
#160699 Artifact symlink redirection Fixed — refuse symlinks
#160700 Build artifact publish races Fixed — atomic publish
#160715 Exhausting Argon2 params Fixed — lock to generated profile
#160718 Preimage AlreadyNoted substring Fixed — verify on-chain
#160724 WS URL credentials in diagnostics Fixed — redact userinfo
#160732 Transfer total wrap Fixed — checked_add
#160734 Batch vs call-count limit Fixed — runtime batched_calls_limit
#160749 Failed extrinsic reported verified Fixed — failure-dominant
#160776 Missing aggregate bypasses split Fixed
#160777 Offset not global across ranges Fixed
#160783 Public helpers panic on bad input Fixed — fallible APIs

Low (11 Unreviewed — in scope)

ID Title Status in this PR
#159905 Byte-indexed address truncation on remote IDs Open
#159911 CLI transfer limit accepts values above documented 1000 Open
#159917 Fragile substring matching for limit-exceeded errors Open
#160136 Distribution invariant broken by u128 overflow Open (related hardening via checked adds elsewhere)
#160585 Password-file permits symlink targets / unbounded reads Partial — mode/owner checks added (#160708); symlink/size bounds still open
#160678 Malformed RPC header fields can panic CLI Open
#160711 Malformed wallet nonce panics during unlock Open
#160744 Unchecked RPC string slicing can crash system inspection Open
#160760 Unavailable home directories can panic Open
#160789 WalletManager lacks sync for concurrent FS ops Partial — keystore process lock / create locks from High #160598/#160737
#160800 Proposal IDs decoded from key suffix without layout validation Open

Excluded: 314 Low with Validity: Invalid

Out of scope per V12. No further triage required for merge of this PR.

Opportunistic hardening (Invalid Lows — not audit blockers)

Some Invalid Lows were still tightened while adjacent to High/Medium work (e.g. block-list bounds, storage iterate cap, JSON numeric parsing, multisend dupes, metadata checked_add). These are optional defense-in-depth, not required to close the Unreviewed set.

Info (2 Unreviewed)

  • #160685 Bind deposits/votes to confirmed referendum index — informational
  • #160730 Non-native leaves represented as native assets — informational

Breaking / UX changes callers should know

  • wallet import / from-seed: no --mnemonic / --seed flags (stdin prompts)
  • --password / -p rejected everywhere; use --password-file, env, or prompt
  • wallet create no longer silently uses an empty password; prompts (with confirm), --password-file, or env; empty only via --allow-empty-password
  • Wormhole: --secret--secret-file
  • wallet export: requires --output file (no stdout mnemonic dump)
  • Tech collective remove: requires --min-rank
  • Circuit artifacts: need a rebuild so generated-bins/ is a real directory with manifest.json (symlink-style bins rejected)
  • QuantusClient::new rejects non-Quantus / incompatible runtimes
  • Batch transfers use batch_all (atomic fail-all)

Test plan

  • cargo test --lib (211 passed)
  • Manual: quantus wallet import --name x --mnemonic '...' fails clap parse
  • Manual: quantus wallet create --name x --password secret errors with guidance
  • Manual: wallet dir/files are 0700/0600 after create
  • Manual: connect to wrong specName RPC fails
  • Manual: quantus update refuses checksum mismatch (if exercising updater)
  • Full circuit rebuild without SKIP_CIRCUIT_BUILD once for new generated-bins layout
  • Smoke send / multisig create / wormhole prove against local node

Commits

3d781aa fix(wallet): require an explicit password when creating wallets
ccc244a fix(cli): bound ranges and reject silent zero coercions
7f569f9 fix(bins): authenticate circuit artifacts and publish atomically
fdaee0c fix(cli): validate amounts delays ranks and fallible address helpers
06997c2 fix(wallet): zeroize secret material after encrypt and decrypt
8cd2d4c fix(wormhole): validate Merkle depth and prefer finalized snapshots
fbd38ed fix(wormhole): zeroize proof-generation secrets after use
fb5b36f fix(subsquid): harden exhaustive transfer queries and spent filtering
bcdab11 fix(batch): enforce runtime batched_calls_limit for batch size
12d3ff6 fix(rewards): use checked addition for indexer transfer totals
5015a2e fix(tx): bound transaction-status subscription waits
b7039e6 fix(storage): bound pagination against overflow and stuck cursors
1e9e910 fix(multisig): deduplicate signers before predict and threshold
0bec0ea fix(wallet): write exported mnemonics to a protected file
a710bf6 fix(wormhole): remove --secret argv and verify extrinsic failures
986c2cd fix(client): redact WebSocket URL credentials in diagnostics
95c1fc9 fix(wallet): return errors for malformed public keys
eeaefa7 fix(wallet): refuse to persist wallets with embedded AES key material
c5d3f2d fix(update): verify release archive SHA-256 before install
5fc7920 fix(wormhole): bind transfer events to from amount and count
304d6d5 fix(system): fail closed on invalid RPC token properties
a8051b1 fix(client): verify Quantus runtime identity at connect time
bfd228e fix(wallet): harden storage races and exclusive wallet creation
02c741a fix(wallet): authenticate address metadata and fail closed on migration
dfd96c0 fix(tx): stop unsafe nonce-bump retries on ambiguous errors
f111d9b fix(wormhole): require persisted mnemonic for HD secrets
0460883 fix(batch): use utility.batch_all for atomic transfers
27d8a9f fix(wallet): require restrictive password-file permissions
3bc368c fix(wallet): enforce owner-only keystore permissions
58b8780 fix(tx): fail when watched extrinsic is missing from block
e6dd668 fix(wallet): reject raw --password CLI credentials
d2ac243 fix(multisig): correlate MultisigCreated to creator and params
253c791 fix(wallet): skip malformed files when listing wallets
03dad4d fix(wallet): stop accepting mnemonic and seed via CLI flags

illuzen and others added 30 commits August 4, 2026 14:30
Authority secrets on --mnemonic/--seed were visible in process argv,
shell history, and CI logs. Always read them from a hidden prompt instead.

Co-authored-by: Cursor <cursoragent@cursor.com>
A single corrupt or non-canonical wallet JSON aborted the entire list.
Validate Quantus SS58 addresses at load and isolate per-file failures.

Co-authored-by: Cursor <cursoragent@cursor.com>
Taking the first same-block MultisigCreated event could report another
transaction's address. Match creator, signers, threshold, and nonce.

Co-authored-by: Cursor <cursoragent@cursor.com>
Passwords on --password/-p were visible in process argv and logs.
Reject them at the shared helper and wallet-create boundary.

Co-authored-by: Cursor <cursoragent@cursor.com>
check_execution_success treated a missing extrinsic hash as success.
Return a NetworkError so callers do not report false confirmations.

Co-authored-by: Cursor <cursoragent@cursor.com>
Wallet directories and files inherited umask defaults, allowing local
users to read ciphertext and KDF metadata. Set dir 0700 and files 0600.

Co-authored-by: Cursor <cursoragent@cursor.com>
--password-file accepted world-readable files. On Unix, require a
regular file owned by the caller with no group/other access bits.

Co-authored-by: Cursor <cursoragent@cursor.com>
User-facing batch transfers used non-atomic utility.batch while docs
promised fail-all semantics. Switch the builder to batch_all.

Co-authored-by: Cursor <cursoragent@cursor.com>
Generating an ephemeral mnemonic when a wallet had none could strand
funds at irrecoverable addresses. Error instead and require a mnemonic.

Co-authored-by: Cursor <cursoragent@cursor.com>
Error-substring retries re-signed with an incremented nonce and could
duplicate extrinsics. Submit once with a fresh nonce and surface Subxt errors.

Co-authored-by: Cursor <cursoragent@cursor.com>
Envelope address was trusted without keypair binding, enabling transfer
redirect and spoofed listings. Validate on decrypt, stop passwordless
trust of the envelope, and propagate legacy migration save failures.

Co-authored-by: Cursor <cursoragent@cursor.com>
Predictable temp paths and check-then-write overwrites allowed races and
wallet replacement. Use exclusive create, safer temps, name checks, and locks.

Co-authored-by: Cursor <cursoragent@cursor.com>
RPC connections trusted any node metadata for signing context. Require
spec name quantus and a compatible runtime version before proceeding.

Co-authored-by: Cursor <cursoragent@cursor.com>
Missing or out-of-range tokenDecimals/symbol/ss58Format silently
mis-scaled amounts. Validate properties before using them for formatting.

Co-authored-by: Cursor <cursoragent@cursor.com>
Destination-only matching could select another same-block transfer.
Require a unique match on from, amount, and transfer_count.

Co-authored-by: Cursor <cursoragent@cursor.com>
Self-update applied GitHub archives without checking published
sha256sums. Download the sibling checksum file and verify before replace.

Co-authored-by: Cursor <cursoragent@cursor.com>
Legacy Argon2 digests must not be rewritten to disk. Encrypt already
strips digests; save paths now reject any remaining embedded key material.

Co-authored-by: Cursor <cursoragent@cursor.com>
Address derivation previously panicked on bad Dilithium public key
bytes. Propagate InvalidPublicKey through wallet creation and views.

Co-authored-by: Cursor <cursoragent@cursor.com>
Node URLs with userinfo were logged and embedded in errors. Sanitize
diagnostics so passwords are not disclosed.

Co-authored-by: Cursor <cursoragent@cursor.com>
Secrets on --secret were visible in process argv; require --secret-file.
Also treat ExtrinsicFailed as dominant so failed txs are not verified.

Co-authored-by: Cursor <cursoragent@cursor.com>
Printing mnemonics to stdout risked shoulder-surfing and log capture.
Require --output and write an owner-only file instead.

Co-authored-by: Cursor <cursoragent@cursor.com>
Duplicate signers inflated predicted addresses and could satisfy
thresholds incorrectly. Sort and dedup before prediction and checks.

Co-authored-by: Cursor <cursoragent@cursor.com>
Checked count accumulation and reject non-advancing key cursors so
malicious RPC pages cannot loop or wrap the entry count.

Co-authored-by: Cursor <cursoragent@cursor.com>
Unbounded tx_progress waits could hang forever. Apply inactivity and
overall deadlines and surface stream timeouts.

Co-authored-by: Cursor <cursoragent@cursor.com>
Untrusted Subsquid amounts were summed with wrapping u128 +=.
Reject overflows instead of silently wrapping totals.

Co-authored-by: Cursor <cursoragent@cursor.com>
Batch sizing used a soft heuristic that could exceed the runtime call
count limit. Read Utility::batched_calls_limit and fail closed.

Co-authored-by: Cursor <cursoragent@cursor.com>
Require aggregate counts, offset-paginate single over-limit blocks, apply
caller offset once globally, and exclude spent nullifiers from pending sets.

Co-authored-by: Cursor <cursoragent@cursor.com>
Proof inputs retained secret bytes after generation. Clear them before
returning so secrets do not linger in process memory.

Co-authored-by: Cursor <cursoragent@cursor.com>
Reject oversized/mismatched ZK Merkle proofs and read recursive flow
state from finalized blocks instead of best-block tips.

Co-authored-by: Cursor <cursoragent@cursor.com>
Key material and plaintext buffers were retained after use. Clear them
on Drop and after crypto operations; redact Debug output.

Co-authored-by: Cursor <cursoragent@cursor.com>
illuzen and others added 6 commits August 4, 2026 15:08
Harden decimal formatting, transfer display, collective remove rank,
delay encoding, and public helpers that previously panicked on bad input.

Co-authored-by: Cursor <cursoragent@cursor.com>
Load circuit bundles only after manifest hash checks, refuse symlink
redirection, and publish generated-bins via atomic directory replace.

Co-authored-by: Cursor <cursoragent@cursor.com>
Cap block-list and storage-iterate limits, surface missing extrinsic and
nonce absence, reject bad JSON numerics and duplicate multisend recipients,
and use checked metadata counters.

Co-authored-by: Cursor <cursoragent@cursor.com>
Create previously fell through to an empty password after rejecting
--password. Obtain a new password via file, env, or confirmed prompt,
and require --allow-empty-password for empty development wallets.

Co-authored-by: Cursor <cursoragent@cursor.com>
Replace deprecated GenericArray::from_slice usage, simplify char/find
and Option helpers, const-assert tx timeouts, allow intentional public
SDK dead_code, and stop moving out of Drop WalletData in examples.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 3d781aa. Configure here.

Comment thread src/cli/common.rs
const MILLIS_PER_SECOND: u64 = 1_000;
const TX_STATUS_INACTIVITY_TIMEOUT_SECS: u64 = 30;
const TX_STATUS_INCLUDED_TIMEOUT_SECS: u64 = 5 * 60;
const TX_STATUS_FINALIZED_TIMEOUT_SECS: u64 = 30 * 60;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inactivity timeout aborts finalization waits

High Severity

The new status watcher applies a fixed 30s inactivity timeout on every tx_progress.next() wait, including after best-block inclusion while waiting for finalization. On Quantus PoW, finalization often has silent gaps longer than 30s, so --finalized and other finalized waits can fail even though the overall 30-minute deadline has not elapsed. The error text also always cites the 30s inactivity window, including when the overall watch deadline expires.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3d781aa. Configure here.

@github-advanced-security github-advanced-security AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

illuzen and others added 3 commits August 4, 2026 17:41
After best-block inclusion, wait only on the overall watch deadline so
PoW finality gaps longer than 30s don't fail --finalized. Distinguish
inactivity vs overall-deadline timeout errors.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants