commit c04eca7dd9e3333bd44be5b6f89a103a06623b63
parent c2d08cb3ab0629002aae90639eb48e74ad4bb527
Author: Joris Hartog <jorishartog@hotmail.com>
Date: Sun, 30 Aug 2026 07:23:20 +0200
Fix vulnerable desktop dependencies
Diffstat:
19 files changed, 192 insertions(+), 42 deletions(-)
diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml
@@ -0,0 +1,70 @@
+name: Security and desktop builds
+
+on:
+ pull_request:
+ push:
+ branches: [main]
+
+permissions:
+ contents: read
+
+jobs:
+ dependency-policy:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check out repository
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ persist-credentials: false
+ - name: Install pinned Rust toolchain
+ run: rustup toolchain install 1.88.0 --profile minimal
+ - name: Install pinned cargo-audit
+ run: cargo +1.88.0 install cargo-audit --version 0.22.1 --locked
+ - name: Audit dependencies and enforce source/license policy
+ run: ./scripts/check-dependencies.sh
+ - name: Test node
+ run: cargo test --locked
+ - name: Check fuzz targets
+ run: cargo check --locked --manifest-path fuzz/Cargo.toml
+
+ desktop-build:
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ runs-on: ${{ matrix.os }}
+ defaults:
+ run:
+ shell: bash
+ steps:
+ - name: Check out repository
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ persist-credentials: false
+ - name: Install Linux desktop dependencies
+ if: runner.os == 'Linux'
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y --no-install-recommends \
+ libwebkit2gtk-4.1-dev \
+ build-essential \
+ libxdo-dev \
+ libssl-dev \
+ libayatana-appindicator3-dev \
+ librsvg2-dev
+ - name: Install pinned Rust toolchain
+ run: rustup toolchain install 1.88.0 --profile minimal
+ - name: Build host sidecar
+ run: |
+ cargo +1.88.0 build --locked
+ target_triple="$(rustc +1.88.0 -vV | sed -n 's/^host: //p')"
+ mkdir -p src-tauri/binaries
+ if [ "${{ runner.os }}" = "Windows" ]; then
+ cp target/debug/iuna.exe "src-tauri/binaries/iuna-sidecar-${target_triple}.exe"
+ else
+ cp target/debug/iuna "src-tauri/binaries/iuna-sidecar-${target_triple}"
+ fi
+ - name: Test desktop crate
+ run: cargo +1.88.0 test --locked --manifest-path src-tauri/Cargo.toml
+ - name: Build desktop crate
+ run: cargo +1.88.0 build --locked --manifest-path src-tauri/Cargo.toml
diff --git a/Cargo.toml b/Cargo.toml
@@ -2,6 +2,7 @@
name = "iuna"
version = "0.4.3"
edition = "2024"
+rust-version = "1.88"
license = "Apache-2.0"
[dependencies]
diff --git a/Dockerfile.local-testnet b/Dockerfile.local-testnet
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1
-FROM rust:1.86.0-bookworm AS builder
+FROM rust:1.88.0-bookworm AS builder
WORKDIR /src/iuna
COPY . .
diff --git a/README.md b/README.md
@@ -68,9 +68,11 @@ Release and deploy with:
./deployment.sh 0.2.48
```
-By default, deployment runs the regular unit tests and verifies that the fuzz
-targets compile against their locked dependencies. Run the extended adversarial,
-fuzz, and release-property suites explicitly when needed:
+By default, deployment audits all three Rust lockfiles, enforces the dependency
+source/license policy, runs the regular unit tests, and verifies that the fuzz
+targets compile against their locked dependencies. Release hosts therefore need
+`cargo-audit` and `jq` in addition to the pinned Rust 1.88 toolchain. Run the
+extended adversarial, fuzz, and release-property suites explicitly when needed:
```sh
./deployment.sh --full-tests 0.2.48
diff --git a/ROADMAP.md b/ROADMAP.md
@@ -118,8 +118,12 @@ Focus: improve usability, tooling, and governance after the base network is stab
A release intended for deployment must pass:
+- `./scripts/check-dependencies.sh`, which audits `Cargo.lock`,
+ `fuzz/Cargo.lock`, and `src-tauri/Cargo.lock` and enforces the dependency
+ source/license allowlist
- `cargo test --locked`
- `cargo check --locked --manifest-path fuzz/Cargo.toml`
+- desktop test/build jobs on Linux, macOS, and Windows
- fuzz gate runs with `256` iterations each for `p2p_envelope`,
`compact_snapshot`, `domain_json`, `stratum_request`, and `wallet_config`
- `cargo test --locked --release --test properties -- --ignored`
diff --git a/deployment.sh b/deployment.sh
@@ -101,6 +101,7 @@ run_release_tests() {
require_command cargo
+ ./scripts/check-dependencies.sh
cargo test --locked
cargo check --locked --manifest-path fuzz/Cargo.toml
@@ -222,7 +223,7 @@ build_windows_desktop_in_docker_if_possible() {
-v iuna-windows-tauri-target:/work/iuna/src-tauri/target \
-v "$(pwd):/src/iuna:ro" \
-v "$(pwd)/downloads:/out" \
- rust:1.86-bookworm \
+ rust:1.88-bookworm \
bash -c '
set -euo pipefail
@@ -299,7 +300,7 @@ build_linux_cli_archives() {
-v "$(pwd):/src/iuna:ro" \
-v "$(pwd)/downloads:/out" \
-v "$(pwd)/.docker-build:/node-out" \
- rust:1.86-bookworm \
+ rust:1.88-bookworm \
bash -c '
set -euo pipefail
diff --git a/docs/security-review.md b/docs/security-review.md
@@ -158,6 +158,7 @@ Evidence already in the tree:
Before the mainnet-candidate launch, attach or publish logs for:
```sh
+./scripts/check-dependencies.sh
cargo test --locked
cargo check --locked --manifest-path fuzz/Cargo.toml
cargo run --locked --manifest-path fuzz/Cargo.toml --bin p2p_envelope -- -runs=256 fuzz/corpus/p2p_envelope
@@ -246,6 +247,17 @@ see which revision was tested.
playbooks are the maintained in-tree references.
- Release evidence: keep successful release-gate logs from the exact tagged
candidate revision.
+- Desktop dependency security: the project and release builders use Rust 1.88.
+ The desktop lockfile pins `quick-xml 0.41.0`, `plist 1.10.0`, and
+ `time 0.3.47`, removing RUSTSEC-2026-0194, RUSTSEC-2026-0195, and
+ RUSTSEC-2026-0009. `scripts/check-dependencies.sh` audits the root, fuzz, and
+ desktop lockfiles without vulnerability ignores and rejects unapproved
+ dependency sources or license identifiers. It is mandatory in
+ `deployment.sh` and the CI security job. CI also test-builds the desktop crate
+ on Linux, macOS, and Windows. RustSec still reports non-vulnerability warnings
+ for Tauri's Linux GTK3 stack (unmaintained crates and the `glib::VariantStrIter`
+ advisory); iuna does not call that iterator API directly. Reassess this
+ transitive stack on every Tauri upgrade and no later than 2026-11-30.
- Height `1000` activation: the release activates both grinding resistance and
transaction signing format v1 automatically. All candidate nodes must upgrade
before activation; the height activation itself requires no chain-state reset
diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml
@@ -2,6 +2,8 @@
name = "iuna-fuzz"
version = "0.0.0"
edition = "2024"
+rust-version = "1.88"
+license = "Apache-2.0"
publish = false
[package.metadata]
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
@@ -1,4 +1,4 @@
[toolchain]
-channel = "1.86.0"
+channel = "1.88.0"
components = ["rustfmt", "clippy"]
profile = "minimal"
diff --git a/scripts/check-dependencies.sh b/scripts/check-dependencies.sh
@@ -0,0 +1,64 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+cd "$(dirname "${BASH_SOURCE[0]}")/.."
+
+require_command() {
+ command -v "$1" >/dev/null 2>&1 || {
+ echo "error: missing required command: $1" >&2
+ exit 1
+ }
+}
+
+require_command cargo
+require_command cargo-audit
+require_command jq
+
+lockfiles=(Cargo.lock fuzz/Cargo.lock src-tauri/Cargo.lock)
+for lockfile in "${lockfiles[@]}"; do
+ cargo audit --file "$lockfile"
+done
+
+manifests=(Cargo.toml fuzz/Cargo.toml src-tauri/Cargo.toml)
+metadata_file="$(mktemp)"
+trap 'rm -f "$metadata_file"' EXIT
+
+for manifest in "${manifests[@]}"; do
+ cargo metadata --locked --format-version 1 --manifest-path "$manifest" |
+ jq -r '.packages[] | [.name, (.license // "MISSING"), (.source // "LOCAL")] | @tsv'
+done | sort -u > "$metadata_file"
+
+policy_failed=false
+while IFS=$'\t' read -r package license source; do
+ case "$source" in
+ LOCAL|registry+https://github.com/rust-lang/crates.io-index) ;;
+ *)
+ echo "error: dependency ${package} uses unapproved source ${source}" >&2
+ policy_failed=true
+ ;;
+ esac
+
+ if [ "$license" = "MISSING" ]; then
+ echo "error: dependency ${package} has no declared license" >&2
+ policy_failed=true
+ continue
+ fi
+
+ license_tokens="$(printf '%s\n' "$license" |
+ sed -E 's/[()\/]/ /g; s/(^|[[:space:]])(AND|OR|WITH)([[:space:]]|$)/ /g')"
+ for license_token in $license_tokens; do
+ case "$license_token" in
+ 0BSD|Apache-2.0|BSD-1-Clause|BSD-2-Clause|BSD-3-Clause|BSL-1.0|CC0-1.0|ISC|LGPL-2.1-or-later|MIT|MIT-0|MPL-2.0|NCSA|Unicode-3.0|Unlicense|Zlib|LLVM-exception) ;;
+ *)
+ echo "error: dependency ${package} uses unapproved license token ${license_token} (${license})" >&2
+ policy_failed=true
+ ;;
+ esac
+ done
+done < "$metadata_file"
+
+if [ "$policy_failed" = "true" ]; then
+ exit 1
+fi
+
+echo "Dependency audit, source policy, and license policy passed for all manifests."
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
@@ -1814,9 +1814,9 @@ checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
[[package]]
name = "num-conv"
-version = "0.1.0"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
[[package]]
name = "num-traits"
@@ -2198,9 +2198,9 @@ checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
[[package]]
name = "plist"
-version = "1.8.0"
+version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07"
+checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85"
dependencies = [
"base64 0.22.1",
"indexmap 2.14.0",
@@ -2320,9 +2320,9 @@ dependencies = [
[[package]]
name = "quick-xml"
-version = "0.38.4"
+version = "0.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c"
+checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1"
dependencies = [
"memchr",
]
@@ -3341,9 +3341,9 @@ dependencies = [
[[package]]
name = "time"
-version = "0.3.45"
+version = "0.3.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd"
+checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
dependencies = [
"deranged",
"itoa",
@@ -3356,15 +3356,15 @@ dependencies = [
[[package]]
name = "time-core"
-version = "0.1.7"
+version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca"
+checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
[[package]]
name = "time-macros"
-version = "0.2.25"
+version = "0.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd"
+checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
dependencies = [
"num-conv",
"time-core",
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
@@ -2,6 +2,8 @@
name = "iuna-desktop"
version = "0.4.3"
edition = "2024"
+rust-version = "1.88"
+license = "Apache-2.0"
publish = false
[build-dependencies]
diff --git a/src/adapters/chain_store/compact.rs b/src/adapters/chain_store/compact.rs
@@ -131,10 +131,7 @@ pub(super) fn encode_compact_snapshot(snapshot: &ChainSnapshot) -> Result<Vec<u8
);
}
if block.prev_hash != expected_prev_hash {
- bail!(
- "chain snapshot block {} has non-canonical previous hash",
- height
- );
+ bail!("chain snapshot block {height} has non-canonical previous hash");
}
if block.hash != block.compute_hash() {
bail!("chain snapshot block {height} has a non-canonical hash");
diff --git a/src/adapters/p2p/line_codec.rs b/src/adapters/p2p/line_codec.rs
@@ -39,7 +39,7 @@ impl<R: AsyncRead + Unpin> LimitedLineReader<R> {
if let Some(newline) = available.iter().position(|byte| *byte == b'\n') {
if self.pending.len() + newline > MAX_GOSSIP_LINE_BYTES {
- anyhow::bail!("p2p message exceeds {} byte limit", MAX_GOSSIP_LINE_BYTES);
+ anyhow::bail!("p2p message exceeds {MAX_GOSSIP_LINE_BYTES} byte limit");
}
self.pending.extend_from_slice(&available[..newline]);
self.reader.consume(newline + 1);
@@ -53,7 +53,7 @@ impl<R: AsyncRead + Unpin> LimitedLineReader<R> {
}
if self.pending.len() + available.len() > MAX_GOSSIP_LINE_BYTES {
- anyhow::bail!("p2p message exceeds {} byte limit", MAX_GOSSIP_LINE_BYTES);
+ anyhow::bail!("p2p message exceeds {MAX_GOSSIP_LINE_BYTES} byte limit");
}
let consumed = available.len();
self.pending.extend_from_slice(available);
diff --git a/src/domain/adversarial_tests.rs b/src/domain/adversarial_tests.rs
@@ -1396,12 +1396,9 @@ fn mini_weighted_ticket_draw(
format!("{}:{}", parent.hash, parent.vdf_output)
};
let seed = if rank == 0 {
- format!("iuna-ticket-draw:{}:{}", target_height, parent_randomness)
+ format!("iuna-ticket-draw:{target_height}:{parent_randomness}")
} else {
- format!(
- "iuna-ticket-draw-rank:{}:{}:{}",
- target_height, rank, parent_randomness
- )
+ format!("iuna-ticket-draw-rank:{target_height}:{rank}:{parent_randomness}")
};
let digest = Sha256::digest(seed.as_bytes());
let mut bytes = [0_u8; 16];
@@ -3209,8 +3206,7 @@ fn mini_ticket_oracle_matches_ledger_across_maturity_expiry_and_consumption() {
&& ticket.eligible_from_height == expected_from
&& ticket.eligible_until_height == expected_until
}),
- "burn ticket {} was not scheduled with expected maturity/expiry",
- signature
+ "burn ticket {signature} was not scheduled with expected maturity/expiry"
);
}
}
diff --git a/src/domain/hex.rs b/src/domain/hex.rs
@@ -10,7 +10,7 @@ pub(super) fn decode_hex_array<const N: usize>(input: &str) -> Result<[u8; N]> {
let len = bytes.len();
bytes
.try_into()
- .map_err(|_| anyhow!("expected {} hex bytes, got {len}", N))
+ .map_err(|_| anyhow!("expected {N} hex bytes, got {len}"))
}
pub(super) fn decode_hex(input: &str) -> Result<Vec<u8>> {
diff --git a/src/domain/ledger_queries.rs b/src/domain/ledger_queries.rs
@@ -113,9 +113,11 @@ impl Ledger {
launch_profile_hash: self.launch_profile.hash(),
mine_reward: self.mine_reward,
current_mine_difficulty_bits: self.current_mine_difficulty_bits(),
- balances: include_balances
- .then(|| balances_from_utxos(&self.utxos))
- .unwrap_or_default(),
+ balances: if include_balances {
+ balances_from_utxos(&self.utxos)
+ } else {
+ Default::default()
+ },
pending_transactions: self.pending.len(),
}
}
diff --git a/src/domain/ticket.rs b/src/domain/ticket.rs
@@ -86,12 +86,9 @@ pub(super) fn draw_parent_randomness(parent: &Block, target_height: u64) -> Stri
fn ticket_draw_seed(parent: &Block, target_height: u64, rank: u32) -> String {
let parent_randomness = draw_parent_randomness(parent, target_height);
if rank == 0 {
- format!("iuna-ticket-draw:{}:{}", target_height, parent_randomness)
+ format!("iuna-ticket-draw:{target_height}:{parent_randomness}")
} else {
- format!(
- "iuna-ticket-draw-rank:{}:{}:{}",
- target_height, rank, parent_randomness
- )
+ format!("iuna-ticket-draw-rank:{target_height}:{rank}:{parent_randomness}")
}
}
diff --git a/src/main.rs b/src/main.rs
@@ -187,7 +187,7 @@ async fn main() -> Result<()> {
println!("UI data database: {}", ui_data_store.path().display());
println!("management UI: http://{}", opts.http_addr);
if p2p_accept_inbound {
- println!("p2p listener: {}", configured_p2p_addr);
+ println!("p2p listener: {configured_p2p_addr}");
} else {
println!("p2p listener: disabled (outbound-only)");
}