Security review checklist — Top 20 CosmWasm + Top 20 EVM (approved via cl8y-ecosystem-qa #8) #5
Labels
No labels
enhancement
governance
QA
testing
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
code/ust1-window#5
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
@PlasticDigits approved via cl8y-ecosystem-qa#8. Full checklist committed at
specs/UST1-Window-Security-Checklist.mdin cl8y-ecosystem-qa (commit 556fdc0).Scope
ust1-oracle + ust1-window CosmWasm contracts on Terra Classic, plus ust1-oracle-service (off-chain, BSC-reading).
Contract surface
ust1-oracle: UpdateRate, SetOracleOperator, SetPaused, ProposeGovernance, AcceptGovernance + Config, State + instantiate/migrate
ust1-window: Receive (cw20 hook — vFDUSD→UST1 mint / UST1→vFDUSD burn), SetLimits, SetPaused, SetFeeBps, ProposeGovernance, AcceptGovernance + Config, EffectiveSwap + instantiate/migrate
ust1-oracle-service: polls BSC Venus
exchangeRateStored, applies throttle/daily-cap/monotonic policy, broadcasts UpdateRate to TerraChecklist categories
CW-01 to CW-20 — access control, reply/submsg, fixed-point math, rounding, oracle staleness, two-step governance, migrate privilege, reentrancy, unbounded loops, panics, replay, pause bypass, limit bypass, fee_bps bounds, oracle policy (INV-ORACLE-*), attribute injection, cw20 spoofing, instantiate validation, attached funds, dependency hygiene.
EVM-01 to EVM-20 — RPC trust, reorg susceptibility, Venus address spoofing, decimals scaling, Rust math overflow, policy-drift (off-chain vs on-chain), signing key handling, TLS, DoS, liveness gap, replay, malicious UpdateRate injection, Venus deprecation, wrong-chain (chainId check), operator front-run, read/write separation, config injection, sensitive logging, supply-chain, CI pipeline integrity.
Method × issue coverage matrix included in the spec doc.
Next
I'll work through verification, document evidence per method in this issue thread (or split into sub-issues per category as findings emerge), and link back to the spec doc for any updates.
Full spec: https://gitlab.com/PlasticDigits/cl8y-ecosystem-qa/-/blob/main/specs/UST1-Window-Security-Checklist.md
@PlasticDigits initial sweep complete — all 40 categories (CW-01 to CW-20 + EVM-01 to EVM-20) reviewed against the contract surface and oracle-service code. Full notes below; findings will be filed as sub-issues for tracking.
Summary
CosmWasm findings
CW-05 — No oracle staleness guard on swap side.
ust1-windowdeposit/withdraw queryoracle.stateand use the returnedratewithout checkinglast_update_secagainst current block time. Iforacle-servicestops broadcasting, swaps continue indefinitely against the last-known rate. INV-ORACLE-THROTTLE-001 guarantees updates are ≥4h apart but places no upper bound on rate age. Recommend max-age check indeposit/withdraw.CW-13 — Strict
>on limits makes them inclusive caps.ensure_limitsrejects only whenust1_notional > per_tx_ust1_limitandnext > rolling_24h_ust1_limit. A swap exactly equal to the limit passes. Probably intentional but ambiguous vs spec. Recommend documenting Config field semantics.CW-20 —
cw20-mintablegit dep not pinned. In workspaceCargo.toml:cw20-mintable = { git = "https://github.com/PlasticDigits/cw20-mintable", features = ["library"] }. Norev,tag, orbranch. Supply-chain risk — force-push or compromise would affect next clean build. Recommend pinning to a commit SHA.EVM / oracle-service findings
EVM-01 — Sequential RPC fallback, not multi-provider cross-check.
run_with_evm_rpc_url_fallbacktries URL 1, falls through to URL 2 only on error. First responsive URL wins. A single malicious or compromised RPC can forge the rate. Recommend reading from ≥2 providers and requiring exact match (or median) before broadcasting.EVM-02 — No reorg protection.
exchangeRateStored().call()uses Alloy's default block tag (latest). No confirmation depth, noFinalizedtag. Reorg could cause pre-reorg rate to be read and broadcast. Recommend reading atFinalizedorlatest - N.EVM-03 — Venus vToken address not allowlisted.
VENUS_VTOKEN_ADDRESSaccepted as any 0x-prefixed hex string. Wrong vToken (e.g. a different Venus market) would be read silently. Recommend hardcoded expected address or explicit allowlist, assert on startup.EVM-08 — No HTTPS check on RPC URLs.
BSC_RPC_URLSandTERRA_LCD_URLaccepted as any string. HTTP URLs silently accepted and vulnerable to MITM. Recommendstarts_with("https://")validation at config load.EVM-10 — No liveness alerting. Service logs "tick failed" / "skip update" but has no external alert if it hasn't broadcast successfully in >4h. A silent failure compounds CW-05 (stale rate on-chain). Recommend metric endpoint, heartbeat file, or dead-man alert.
EVM-14 — No
eth_chainIdverification. Service doesn't assertchainId == 56on BSC reads. Wrong-chain RPC (opBNB 204, mainnet 1, testnet) would be read silently. Recommendeth_chainIdcheck per connection.EVM-18 —
ConfigderivesDebugwithterra_mnemonicfield.config.rs:5has#[derive(Debug, Clone)] pub struct Config { ... pub terra_mnemonic: String, ... }. Anydebug!(?cfg)or{:?}formatter leaks the mnemonic into logs. Recommend dropping Debug on Config, or wrapping mnemonic in a redacting type.Minor notes (document or decide later)
migratein both contracts relies on chain-level wasm admin, no contract-level governance check on top. Standard CosmWasm pattern. Worth noting in deployment docs that whoever holds the chain admin can migrate.instantiateacceptsper_tx_ust1_limit = 0androlling_24h_ust1_limit = 0without rejection. Zero-limit deploys DoS all swaps. Recoverable via governanceSetLimits. Deployer footgun.exec_*paths don't rejectinfo.funds.is_empty(). Attached uluna would lock in the contract. Can't hit via normal flows (governance wouldn't attach funds) but worth defensive rejection.RATE_SCALE = 1e18matches VenusexchangeRateStoredonly because FDUSD is 18-decimal. If ever pointed at a non-18-decimal underlying, math breaks silently. Document invariant.poll_interval_secs = 21_600(6h), this is low-risk. If service is ever tuned to faster polling, add exponential backoff.exchangeRateStoredreverts (paused/deprecated), service logs and retries forever. Configurable alert path preferred.cargo auditnot available on QA server this session. Recommend running in CI on every push (supplements the filed CW-20 git-pin finding).Passes (no action needed)
checked_*, proper Uint256 widening), CW-04 rounding (against-user floor), CW-06 two-step governance (clean pending clear), CW-08 reentrancy (state-before-submsg), CW-09 unbounded loops (no iteration anywhere), CW-10 panics (no unwrap in production), CW-11 replay, CW-12 pause bypass (correctly skipped on gov actions), CW-14 fee_bps bounds, CW-15 oracle policy (throttle/daily/monotonic all enforced), CW-16 attribute injection (bech32 validated inputs safe in practice), CW-17 cw20 spoofing (only registered tokens accepted).ust1_common— perfect), EVM-07 signing key (not exposed on signer struct), EVM-11 Terra nonce (fresh sequence per tick + on-chain throttle idempotence), EVM-12 malicious UpdateRate injection (poll-only, caps limit blast radius), EVM-15 operator front-run (structurally bounded), EVM-16 read/write separation (clean), EVM-17 config injection (required-field errors), EVM-20 CI pipeline (gitleaks + pre-commit + workspace-optimizer).Filing the 9 medium-severity findings as sub-issues next.
mentioned in issue #6
mentioned in issue #7
mentioned in issue #8
mentioned in issue #9
mentioned in issue #10
mentioned in issue #11
mentioned in issue #12
mentioned in issue #13
mentioned in issue #14
@PlasticDigits all 9 medium-severity findings filed as sub-issues #6 through #14.
Summary by category:
The 9 minor notes from the sweep comment above are in-comment only — not filing separately unless you want them as tracking issues. Happy to split any of them out if you'd rather.
This issue (#5) can serve as the verification tracker. As each sub-issue is fixed I'll close it + confirm on the parent. Ready for dev triage on severity/priority.
mentioned in issue #1
@PlasticDigits all 9 sub-issues from the initial sweep are now CLOSED as of this morning's verification.
Closed today: #6 #7 #8 #9 #10 #11 #12 #13 #14
Every fix verified by code review + 40/40 workspace test run (up from 28/28 before fixes — 12 new tests landed). All fixes meet or exceed the recommendations in the original findings. Particular standouts:
last_update_sec == 0protection + governance-settable max age, beyond specsecrecy::SecretString(compile-time expose_secret requirement) instead of manual Debug implParent #5 can serve as the ongoing verification tracker. No outstanding findings from the initial sweep. Happy to sweep again after any significant contract or oracle-service change.
@PlasticDigits all 9 findings from the 4/21 sweep have been closed via #6-#14 (fixes verified 4/22). Minor notes remain as documentation/invariant annotations — not blockers.
Resolution status
>on limits (inclusive caps)EvmRpcReadPolicyquorum)BSC_CONFIRMATION_BLOCKS)validate_rpc_url)ORACLE_MAX_SILENCE_SECS)BSC_ALLOWED_CHAIN_IDS)secrecy::SecretString)Also shipped this week off this sweep
Minor notes still standing (docs-only)
The 9 minor notes (CW-04, CW-07, CW-18, CW-19, EVM-04, EVM-09, EVM-13) were documentation / deployer-footgun observations, not security blockers. Spec
UST1-Window-Security-Checklist.mdin ecosystem-qa retains them as invariant reference.Companion specs
The UST1 pattern is being replicated for YO and DEX (dev's 11am request):
YO-Security-Checklist-DRAFT.md— pushed to ecosystem-qa today (53f24a9), lowest priorityDEX-Security-Checklist.md— pending peer draftClosing this tracker — sweep + all 9 findings accounted for.