Repository navigation
v2: wallet hardening, Microsoft Store build and dependency upgrades - #194
Merged
Merged
Conversation
Apply mechanical fixes from the pre-release security audit. No behavior change for valid inputs; each change is tagged with its audit ID (SEC-NNN). Panic/DoS hardening: - New char-safe truncation helper (util.rs) replaces every attacker- reachable &s[..N]; Ledger frame parsing made saturating, bounds-checked and frame-capped; serialize_apdu rejects >255B; packed_tx varuint counts capped before allocation; PowerUp math on i128/checked. (SEC-010/011/012/013/019/036/042/043/044/056) Network resource limits: - Cap RPC/Hyperion/discovery response bodies (8 MiB / 1 MiB) and disable HTTP redirects on all clients. (SEC-017/060) Key-material hygiene: - Zeroize decrypted private keys; proactive 30s idle-lock task; reset_wallet, change_passphrase and export_backup now cover testnets, bls_* finalizer keys and OS-keyring index entries; drop the pubkey log loop. (SEC-026/027/028/029/041) Config & dapp isolation: - Drop the devtools feature from release builds; JSON-encode and validate the dapp bridge request_id/reason before eval; validate the dapp URL scheme on initial load; scope sensitive events to the main window. (SEC-008/045/048/050) CI hardening: - Least-privilege GITHUB_TOKEN, pin Bun to 1.3.11, cargo --locked, verify e2e .deb checksums. (SEC-062/064/065) Verified: cargo check, cargo clippy and tsc --noEmit all pass. Deferred: Windows owner-only ACL for pin.dat/biometric.dat/keystore files left as a TODO (SEC-030), to land with the unlock redesign.
Replace the Unix-only scripts/with-supported-node.sh wrapper with cross-platform JS helpers (scripts/with-supported-node.js, scripts/ tauri-dev.js) so start/build/watch/test and tauri:dev run on Windows, macOS and Linux. Move the Angular dev server to port 42024 (devUrl + ng serve). Update CLAUDE.md and README accordingly.
…tion window for signing - Removed the LinkSessionService and associated sealed message handling due to security concerns. - Introduced a new ConfirmWindowComponent for trusted transaction confirmations. - Updated TauriIpcService to support new signing flow with backend confirmation. - Modified TransactionService to route signing requests through the trusted confirmation window. - Added confirmation policy settings in the SettingsComponent to require passphrase for every signature. - Enhanced the MsigInboxComponent to bind approvals to exact proposal bytes for security.
…prove ABI serialization
…, echarts 6.1, prettier 3.9, vitest 4.1.10; cargo update 129 crates; drop unused @types/echarts
…ta action routing
Fixes surfaced while registering a Telos finalizer key and testing on Vaulta.
Provider reliability:
- Detect stale RPC nodes: health check now compares head_block_time to the
local clock and flags a node lagging > MAX_HEAD_LAG_SECS (120s) as stale.
Stale nodes are excluded from selection but never circuit-broken, so a
wildly-wrong local clock degrades gracefully instead of locking up. This is
the root cause of the mis-sent finalizer tx (a dead greymass node stuck days
behind still answered 200 OK).
- Remove the two non-functional Telos endpoints (greymass = pre-Savanna/stale,
eosnation = 404 on the chain API) from chain_config.
BP Keys:
- Render the real permission structure from get_account (owner/active/custom
perms with their actual public keys, thresholds, account-auths, waits)
instead of a hard-coded owner/active/claim placeholder.
- Gate the finalizer-key UI on Savanna support: check the chain's eosio ABI for
regfinkey and show a clear message when the chain hasn't activated Savanna,
instead of letting the user push a doomed transaction.
Vaulta rebrand:
- Add a per-chain system_contract config field ("core.vaulta" on Vaulta, "eosio"
elsewhere) and a WalletStateService.systemAccount() helper. Route the core
economic actions (claimrewards, voteproducer, buy/sellram, delegatebw, rex,
powerup, ...) to core.vaulta on Vaulta; producer/consensus actions stay on
eosio. Fixes the "EOS has been rebranded to Vaulta" assertion on claim.
- Keep these on the trusted local (Native) serializers by normalizing
core.vaulta -> eosio in try_native_serialize and the high-risk-warning
matcher (layouts are byte-identical), so signing stays WYSIWYS.
Tests: provider staleness + Vaulta serialization covered; full backend lib
suite passes (113).
New dashboard screen creating accounts via newaccount + buyrambytes + delegatebw, signed through the trusted confirmation window. Generates keys in the backend, validates on-chain name availability, and can import the created account into the wallet. Routed at /dashboard/create-account with a nav entry; checks off the ROADMAP account-creation items.
… module Route secp256k1 keygen, BLS finalizer IKM, and AES-GCM nonces through a single crate::rng module backed by OsRng (getrandom) instead of thread_rng/rand::random, keeping the entropy trust base to the OS kernel. Add fail-closed guards (reject all-zero draws), a startup CSPRNG self-test, and zeroization of transient secret buffers.
- Added ContactsService to handle contact management, including loading, adding, updating, and removing contacts scoped to specific chains. - Introduced validation and normalization functions for contact addresses, supporting account names, FIO handles, and FIO public keys. - Implemented migration logic for legacy contact data to the new per-chain structure. - Enhanced the Tauri IPC service to include exchange deposit account configurations. - Created memo validation logic for exchange deposit accounts, ensuring correct formatting and length. - Updated the SendComponent to integrate contact management and memo validation, providing user feedback for exchanges. - Added unit tests for memo validation and exchange rules.
The titlebars relied on `-webkit-app-region: drag`, which is an Electron/Chromium-shell feature that WebView2 and WKWebView ignore. In Tauri, dragging comes solely from `data-tauri-drag-region`, and a bare attribute only drags when that exact element is the click target. The dashboard therefore had a single draggable element — the 120px `.titlebar-drag-fill`. Empty space after the last account tab, the tab strip's reserved 28px paddings, the chain-filter padding and the flex gaps were all dead zones with no visible element to explain them. The lockscreen and landing titlebars worked only because the attribute was copied onto each child, and still missed the "EOS" span inside the wordmark; the sign-confirm window's brand text was dead as well. Switch every titlebar to `data-tauri-drag-region="deep"`, so any click that does not land on an interactive element drags the window — Tauri's handler still lets buttons, links and inputs block it, keeping account tab clicks and tab reordering intact. The chain-filter dropdown opts out with `data-tauri-drag-region="false"` so its padding does not move the window while the menu is open. Also drop the inert `-webkit-app-region` declarations so the mechanism is not misleading later. Bump the workspace to 2.0.0-alpha.2 (package.json, tauri.conf.json, Cargo.toml/lock, about screen) so the signed macOS alpha can ship with these changes.
The version was written out in five places — package.json, tauri.conf.json, Cargo.toml, Cargo.lock and three templates — so a release meant editing all of them and the footers had already drifted (they showed a suffix-less v2.0.0-alpha while About said alpha.1). package.json is now the single source of truth: - tauri.conf.json takes `"version": "../package.json"`, which Tauri resolves at config-read time, so bundle metadata, the DMG name and the updater all follow the same number. - Cargo.toml is pinned to 0.0.0 with a comment — the crate version is not the app version, and nothing reads CARGO_PKG_VERSION. - The UI injects AppVersionService, which asks the running bundle via Tauri's getVersion() and falls back to `dev` in the browser-only dev server. The About screen, lockscreen and sidebar footers use it. - scripts/macos-package.sh reads package.json for the DMG name. tauri.appstore.conf.json still pins a plain x.y.z, since App Store CFBundleShortVersionString rejects prerelease tags; that number cannot be derived, so macos-appstore-package.sh now fails the build when it drifts from package.json instead of shipping a stale version.
The scroll arrows were 24px circles positioned absolutely over the tab strip, which meant they sat on top of the first and last tab and forced 28px of dead padding inside the scroller to keep from covering them. Make them real flex siblings instead: [ ‹ ] [ viewport ] [ › ], full titlebar height, flush and flat with a hairline separating them from the tabs, so nothing overlaps and the arrows read as window chrome. The edge fades move onto the new viewport element so they stop at the arrows rather than washing over them. Both arrows now render together as soon as the strip overflows and are merely disabled at each end. Rendering them per-direction resized the strip mid-scroll and slid the tabs under the pointer. Tab balances are rounded for scanning — `18088.0334794 WAX` reads as `18,088 WAX`, millions collapse to `1.23M`, and dust keeps enough digits not to render as zero. The exact amount stays in the tooltip. The formatter lives in core/format.ts so other dense views can share it.
The post-unlock screen opened with a hero that took most of the window before showing anything actionable: a kicker, a 2rem title, a paragraph of onboarding copy, three meta chips, three highlight cards and four stat cards — cards nested inside cards, where most of the height came from. It also repeated itself. The chips restated the account and chain counts, "fast state mode" restated the priced count, and "producer coverage" restated the chains card's own subtitle. This is a screen people land on several times a day to answer three questions: is anything on fire, what is it all worth, and where is the account I need. So the hero becomes a summary bar — one panel, four figures split by hairlines, with the estimated value given the only extra emphasis. The description is gone; someone opening the wallet for the first time reads the empty state, which already explains the screen. The unpriced case now reads as a dash plus "No cached price yet" instead of wrapping "No cached price" across three lines at 28px. Everything below it is trimmed the same way: - Resource health puts its title and threshold legend on one line and drops the instruction to open resources — each row has a Manage action already. - Chain headers lose the "Network summary" kicker and drop from 20px to 15px, with the counts inline. - Account cards lose the RAM metric block, which showed the same number as the RAM meter directly beneath it, and the repeated "Open account" caption becomes a chevron that leans in on hover. RAM usage in bytes moves to the meter's tooltip. Balances on this screen use the same rounding as the account tabs, with the exact amounts kept in title attributes, and core/format.ts picks up unit tests for the rounding edges (dust, millions, non-asset input).
…able
Everything under the updater was already wired — plugin registered and
gated off for App Store builds, capabilities granted, pubkey pinned,
createUpdaterArtifacts on, and a service that downloads with progress
and relaunches. None of it could reach a user.
Nothing read isUpdateAvailable and installUpdate() had no call sites,
so a found update was detected and discarded. The single call site
passes silent=true, which skips the one alert in the service, and check
failures went to console.error only. On top of that the endpoint 404s:
GitHub's releases/latest/ resolves to the newest non-prerelease, which
for this repo is v1.0.5 from 2020, and the alpha release has only the
DMG attached — no latest.json, no .sig.
UI:
- Settings gains an Updates card: running version, current state
(checking / up to date / available / downloading with a progress
bar), release notes, and Install and restart. Hidden entirely in App
Store builds, where Apple ships updates.
- The sidebar footer swaps the version label for an "Update ready"
indicator when there is something to install, deep-linking to that
card, which scrolls into view and highlights. The startup check stays
silent — the indicator is the notification, not a toast.
- UpdateService keeps the last error and check time so a persistently
unreachable manifest is visible instead of failing forever in silence.
Release side:
- The endpoint moves to a fixed branch path, which is unaffected by
prerelease flags: raw.githubusercontent.com/eosrio/simpleos/updater.
- scripts/updater-manifest.js builds latest.json from the signed
artifacts under src-tauri/target, mapping each to the {os}-{arch}
keys the plugin looks up. A universal macOS build is listed under
both darwin-aarch64 and darwin-x86_64 — Tauri matches on the running
arch and has no universal fallback.
- scripts/publish-updater.js uploads those artifacts to the release and
writes the manifest to the branch, merging into what is already
published so a Windows release adds its platform without dropping the
macOS entries. It refuses to publish a manifest referencing an
artifact that is neither built locally nor already on the release, so
the endpoint cannot advertise a download that 404s.
- docs/releasing-updates.md documents the flow; bun run updater:manifest
/ updater:publish (and :dry) drive it.
Adds the packaging path for Windows and Linux, makes building from source work for people who are not cutting a release, and adds a way to exercise the update flow locally. Verified by building and running the Windows bundle on Windows. scripts/package-desktop.js is now the entry point for `bun run tauri:build` on every OS (nsis / appimage+deb / app+dmg). It exists mainly because of what happens without the updater signing key: the config pins an updater public key, and Tauri refuses to bundle when it finds a public key with no private key, so a clone of this repo was simply not buildable. It now passes --no-sign in that case and the build succeeds. Such a build still *receives* official updates — verification uses the public key in the config — it just cannot be published as one. `tauri:build:raw` keeps the unwrapped behaviour. Three traps found by actually running the release path, each of which cost a full compile before showing itself: - `tauri build` only reads TAURI_SIGNING_PRIVATE_KEY. The _PATH variant that docs/macos-packaging.md tells releasers to export is ignored when bundling, and the build dies with "a public key has been found, but no private key" *after* everything is compiled. Both packaging scripts now read the file into the variable Tauri actually reads, which is likely why the alpha.1 release shipped without any .sig. - With a key but no TAURI_SIGNING_PRIVATE_KEY_PASSWORD, Tauri prompts on stdin and hangs a non-interactive build at the same late stage. Both scripts warn, and the docs say to export it even when empty. - The Windows updater artifact is the setup.exe itself, not a .nsis.zip. The manifest generator classifies it correctly. scripts/updater-selftest.js rehearses discovery, download, signature check and install against a throwaway key and a localhost endpoint, so nothing production is involved. The generated override sets dangerousInsecureTransportProtocol because the plugin panics at startup on a non-HTTPS endpoint; it lives only in that generated file and never in the shipped config. Note the single-instance plugin: a second launch hands off and exits, which looks exactly like a broken updater. Because a rehearsal leaves artifacts signed by the throwaway key in the same target directory, the manifest generator now compares each signature's minisign key ID against the pubkey in tauri.conf.json and refuses to build a manifest from anything the shipped app cannot verify.
The saturated chain brand colors read fine as a fill or a 1px border but fail badly as small text — Telos at 2.6:1 on a dark card, WAX at 2.0:1 on a light one. Rather than desaturate the brand, add --accent-text: the same hue lifted on dark and deepened on light until it clears AA on every surface in the palette, per chain and per theme. --accent stays the fill/border color; --accent-text is what `color:` uses. Every `color: var(--accent)` in the app moves over. The neutral text ramp is retuned on the same basis, with the measured worst-case ratio across all five --bg-* surfaces recorded next to each token. --text-muted carries real content at 10-13px, so it now clears AA (4.5) even on --bg-active; --text-disabled is placeholder-only and AA-exempt, but stays legible at 3:1. Several places that recessed accent text with `opacity: 0.6` now use --text-muted instead — the alpha was undoing the contrast the ramp exists to guarantee. The light-theme chain blocks must stay after the chain accents: the bare [data-theme="light"] default ties with :root on specificity and wins only on source order, which is how chains without an explicit light block (including vaulta) get a readable accent.
Add the action-permission screen and handle contract-wide links. Harden signing, vault recovery, preference access, updater verification, and CI. Improve RPC failover, history recovery, and account/network response ownership with regression tests and readiness evidence.
Update current-major frontend and Rust dependencies and refresh both lockfiles.
pako 3 ships its own types, so drop the deprecated @types/pako stub. Named deflateRaw and inflateRaw imports are unchanged.
The test environment now requires Node 22.22, 24.15, or 26. Unit tests pass on Node 26.
Angular 22 accepts Vitest 5. The unit suite still passes with clearMocks on by default.
PublicKey.from/equals and Name.value still match the two call sites. signing-request 3 still pulls antelope 1 until that package moves.
The ESR helpers keep the same calls. Identity fixtures still match, and the duplicate antelope 1 copy leaves the bundle.
The simple feature is gone, so enable the sha2 feature and use its Sha256. pbkdf2 0.13 requires Rust 1.85. Encrypt and decrypt round-trips still pass.
Nonce::from_slice is deprecated, so build the nonce with TryFrom. The keystore encrypt and decrypt tests still pass.
keyring 3 was built without a platform feature, so it only had its mock store. The startup probe always failed and every shipped build kept its vault in FileKeyStore. keyring 4 enables the real OS store by default, so the probe started passing and the app switched to an empty OsKeyStore. Unlock then could not find the vault verification token and reported a wrong passphrase, although the vault on disk was intact. Select FileKeyStore unconditionally and drop the startup probe. OsKeyStore also lacks the atomic store_batch and is_empty the vault relies on, so moving to the OS keyring needs an explicit migration. The Test Keyring diagnostic now reports the real backend and the probe result separately. Add a known-answer test that pins the unlock chain (PBKDF2 master key, PBKDF2 storage key, AES-256-GCM verify token) to vectors from an independent implementation. Round-trip tests cannot catch a crypto crate upgrade that changes the output.
Nothing in the crate uses them since the anchor-link sealed-session transport was removed. pbkdf2 and aes-gcm bring their own versions.
Matches the RustCrypto generation pbkdf2, aes-gcm and k256 already use, so the build no longer carries two copies of sha2. No source changes.
No source changes. The BLS key encoding tests still pass.
rand 0.10 is on rand_core 0.10 like k256 0.14, so key generation uses SysRng directly and the hand-written K256Rng adapter is gone. secure_rng() wraps SysRng in UnwrapErr, which panics on any OS RNG error, so generation still fails closed. Entropy still comes only from getrandom, and the build now carries a single rand.
Semver-compatible updates only, including tokio 1.53.2, hyper 1.12, serde_json 1.0.154 and zeroize 1.9.1.
jsdom 30.1.2, prettier 3.9.10 and vitest 5.0.4. TypeScript stays on 6.0 because Angular 22.2 supports >=6.0 <6.1 only.
fio.api.eosnation.io and libre-api.edenia.cloud no longer resolve, testnet.wax.pink.gg serves a certificate for another name, and api.eossweden.org fails the TLS handshake with both rustls and schannel. Each chain keeps at least two working endpoints.
Rio Blocks is the name the company uses for its software, and the Microsoft Store listing is published under it.
Listing capabilities in app.security.capabilities enables only the listed ones, so both App Store configs dropped sign-confirm. The trusted confirmation window then has no capability and cannot reach approve_sign or reject_sign.
The Store re-signs MSIX packages, so this gives a signed Windows build without a code signing certificate. Tauri only bundles NSIS and MSI, so bun run tauri:build:win:store builds the binary with --no-bundle, stages it with the manifest and visual assets, indexes them with makepri and packs the MSIX with makeappx. The NSIS installer on GitHub is unchanged. The Store build reuses the app-store feature and the appstore Angular configuration, so the self-updater and its UI are left out. MSIX redirects new AppData files to a per-package folder that Windows deletes on uninstall, which would take vault keys with it. The manifest excludes the app's Roaming and Local AppData folders from write virtualization (Windows 11 per-folder form, Windows 10 fallback), so the Store and NSIS builds share the same vault and it survives uninstall. Versions map to MSIX form so prereleases sort below the release (2.0.0-alpha.3 -> 2.0.3.0, 2.0.0 -> 2.0.999.0). Unplated target-size icons keep Windows from drawing the taskbar icon on an accent plate.
PIN unlock stored the wallet passphrase in pin.dat, encrypted under a key derived from the 4-6 digit PIN with a salt shared by every install. A copy of the app data folder was enough to recover the passphrase by trying every PIN offline, and wallet reset left the file behind. The PIN now lasts only while the app runs. set_pin keeps the master key in memory, encrypted under a key derived from the PIN with a random salt, and unlock_with_pin re-opens the session after an auto-lock or manual lock. Five wrong PINs, quitting, a passphrase change, reset or sign-per-use mode drop it, and nothing PIN-related is written to disk. Startup and reset delete any pin.dat left by earlier builds, and reset also removes confirm_policy so a new wallet starts from the strict confirmation policy. The lockscreen shows how many attempts remain and falls back to the passphrase once the PIN is gone. Settings explains that the PIN lasts until the app quits and hides it in sign-per-use mode.
The stylesheet imported both from Google Fonts, so every launch fetched them from fonts.gstatic.com, which delayed text rendering and sent the user's IP address to Google. They now come from @fontsource and ship inside the app.
Describes what stays on the device, what the app sends over the network and to whom, and how to erase the vault. The Microsoft Store listing links to it.
Windows Hello unlock stored the passphrase in biometric.dat with DPAPI and asked UserConsentVerifier for a yes/no prompt first. The prompt gated nothing cryptographically: any program running as the same user could call CryptUnprotectData with the published entropy and read the passphrase without a prompt. Enabling now creates a Windows Hello key (KeyCredentialManager), has it sign a random 32-byte challenge, and encrypts the passphrase with AES-256-GCM under SHA-256 of a domain tag and that signature. biometric.dat keeps only the challenge and the ciphertext. Unlock signs the challenge again, which requires the Windows Hello prompt, to rebuild the key. Enrollment verifies the signature as RSA PKCS#1 v1.5 against the key's public half, so the scheme is only enabled where signing is deterministic. The plaintext is length-prefixed and padded to 256-byte blocks so the file does not reveal the passphrase length. A watcher brings the Hello dialog to the foreground, since this API version cannot parent it to the app window. When the key is gone, for example after a Windows PIN reset, or the file no longer matches, the shortcut is removed and the lockscreen falls back to the passphrase with an explanation. Startup deletes any biometric.dat in the old format, and reset and passphrase changes also delete the Hello key.
They were working notes for the September release review, not project documentation.
The browser-only mock wallet and the design spec used real account names. Store screenshots and demos are taken from this mode.
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.
Brings
masterup to date withv2-tauri-rewritesince #163. After this, building frommastergives the current v2 desktop wallet (bun install && bun run tauri:build; see the README).Security
sign-confirmwindow that shows exactly the bytes that will be signed. The legacy signing and export commands are no longer registered, so the renderer cannot sign silently.rngmodule, and the backend was hardened against panics, key exposure and config gaps.pin.datfiles from earlier builds are deleted.sign-confirmcapability again.Features
Distribution
bun run tauri:build:win:store, documented indocs/windows-store.md. The NSIS installer and its updater are unchanged.package.json.PRIVACY.mddescribes what stays on the device and what the app sends over the network.Dependencies
Tauri 3.0.0-alpha.4, Angular 22.2.2, reqwest 0.13, k256 0.14, sha2 0.11, ripemd 0.2, pbkdf2 0.13, aes-gcm 0.11, rand 0.10, base64 0.23, keyring 4, windows 0.62. The unused
aes,cbcandhmaccrates were removed, along with four unreachable RPC endpoints.Testing
cargo test --lib: 157 passed, 3 ignored. This covers the session PIN (it never reaches disk, attempts are capped, and it is dropped where it would outlive its key) and the Windows Hello file format.bun run test: 101 passed;bun run buildsucceeds