diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c13bd3e..d89ff6e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,8 +22,8 @@ jobs: name: Format runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 - - uses: dtolnay/rust-toolchain@stable + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable with: components: rustfmt - run: cargo fmt --all -- --check @@ -32,12 +32,12 @@ jobs: name: Clippy runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 - - uses: dtolnay/rust-toolchain@stable + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable with: components: clippy - - uses: Swatinem/rust-cache@v2 - - run: cargo clippy --all-targets --all-features -- -D warnings + - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2 + - run: cargo clippy --locked --all-targets --all-features -- -D warnings test: name: Test (${{ matrix.os }}) @@ -47,7 +47,18 @@ jobs: matrix: os: [ubuntu-latest, windows-latest, macos-latest] steps: - - uses: actions/checkout@v5 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - - run: cargo test --all --all-features + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable + - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2 + - run: cargo test --locked --all --all-features + + msrv: + name: MSRV (Rust 1.91.0) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable + with: + toolchain: 1.91.0 + - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2 + - run: cargo test --locked --all --all-features diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index a6cd259..277696f 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -24,9 +24,11 @@ jobs: name: Fuzz smoke runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 - - uses: dtolnay/rust-toolchain@nightly - - uses: Swatinem/rust-cache@v2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # pinned action + with: + toolchain: nightly + - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2 with: workspaces: fuzz - name: Install cargo-fuzz diff --git a/.github/workflows/release-validation.yml b/.github/workflows/release-validation.yml new file mode 100644 index 0000000..b7fb3e8 --- /dev/null +++ b/.github/workflows/release-validation.yml @@ -0,0 +1,24 @@ +name: Release validation + +on: + push: + tags: ["v*.*.*"] + workflow_dispatch: + +permissions: + contents: read + +jobs: + validate: + name: Validate release artifacts + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + - uses: Swatinem/rust-cache@v2 + - run: cargo fmt --all -- --check + - run: cargo clippy --workspace --all-targets --all-features -- -D warnings + - run: cargo test --workspace --all-features + - run: cargo package --workspace --no-verify diff --git a/.github/workflows/security-audit.yml b/.github/workflows/security-audit.yml new file mode 100644 index 0000000..d137c02 --- /dev/null +++ b/.github/workflows/security-audit.yml @@ -0,0 +1,41 @@ +name: Dependency Security Audit + +on: + pull_request: + paths: + - "Cargo.lock" + - "**/Cargo.toml" + - ".github/workflows/security-audit.yml" + push: + branches: [main] + paths: + - "Cargo.lock" + - "**/Cargo.toml" + - ".github/workflows/security-audit.yml" + schedule: + - cron: "23 4 * * 1" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: dependency-audit-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + audit: + name: RustSec advisory audit + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Install stable Rust + uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable + - name: Install cargo-audit + run: cargo install cargo-audit --locked + - name: Audit locked dependencies + run: cargo audit --file Cargo.lock diff --git a/.github/workflows/upload-sarif.yml b/.github/workflows/upload-sarif.yml new file mode 100644 index 0000000..4e841ad --- /dev/null +++ b/.github/workflows/upload-sarif.yml @@ -0,0 +1,41 @@ +name: Upload contract analysis to GitHub code scanning + +on: + workflow_dispatch: + inputs: + wasm_path: + description: Repository-relative path to a Soroban contract WASM artifact + required: true + type: string + +permissions: + contents: read + security-events: write + actions: read + +jobs: + analyze: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Install stable Rust + uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable + - name: Validate artifact path + env: + WASM_PATH: ${{ inputs.wasm_path }} + shell: bash + run: | + case "$WASM_PATH" in + ""|/*|..|../*|*/../*|*/..) echo "wasm_path must be a repository-relative path without parent traversal" >&2; exit 2 ;; + esac + test -f "$WASM_PATH" || { echo "WASM artifact not found in repository" >&2; exit 2; } + - name: Generate SARIF report + env: + WASM_PATH: ${{ inputs.wasm_path }} + run: cargo run --locked --bin stellar-foundry -- --format sarif analyze-wasm "$WASM_PATH" > results.sarif + - name: Upload SARIF + uses: github/codeql-action/upload-sarif@4bfa242aa49c5f7c6770b98670d354a7dfac05f8 # v3 + with: + sarif_file: results.sarif + category: stellar-foundry-contract-analysis diff --git a/CHANGELOG.md b/CHANGELOG.md index 3178463..df82caa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,30 +9,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Workspace with `devkit-core`, `devkit-xdr`, and `devkit-cli`. -- `devkit-core`: `DevkitError`, `Network` profiles with canonical passphrases, - `OutputFormat`. -- `devkit-xdr`: decode base64 `ScVal`, `TransactionEnvelope`, and - `ContractEvent` to JSON with bounded reading; classify Stellar strkeys. -- `stellar-foundry` CLI: `version`, `doctor`, `strkey`, `scval`, `envelope`, - `event`, with terminal and JSON output and stable exit codes. -- `devkit-rpc`: typed Stellar RPC protocol layer with an injectable `Transport`, - a deterministic `MockTransport`, validated `Endpoint` (https only; loopback - `http`; no embedded credentials), typed `Health` / `NetworkInfo` / - `LatestLedger`, `getEvents` and `getFeeStats` passthrough, and a retry policy - that retries only transport failures. -- CLI: `endpoint ` validates an RPC endpoint against the security policy. -- Documentation: ecosystem research, architecture, getting started, CLI - reference, project status, roadmap. -- CI: format, clippy, and tests on Linux, Windows, and macOS. -- Tooling: `tools/changelog.py` generates reviewable release sections from - Conventional Commits; a dry-run workflow runs on demand and on release. -- Benchmark: `cargo run --release -p devkit-xdr --example mem_profile` reports - peak and current memory per decode input size (see `docs/MEMORY.md`). -- Fuzzing: `fuzz/` cargo-fuzz targets for XDR/SCVal decoding, exercised by a - weekly bounded scheduled workflow (see `docs/TESTING.md`). -- Governance: README, CONTRIBUTING, SECURITY, CODE_OF_CONDUCT, LICENSE, - pull-request and issue templates. +- `devkit-contract`: bounded, non-executing WASM validation and structural + metadata for imports, exports, and custom sections. +- `devkit-analysis`: deterministic structural findings with stable rule IDs + and SARIF 2.1.0 serialization. +- `devkit-config`: strict TOML configuration parsing, timeout validation, + environment overrides, and static diagnostic events. +- `devkit-rpc`: bounded HTTP JSON-RPC transport with timeout, redirects + disabled, response-size limits, response validation, and structured errors. +- CLI commands `inspect-wasm`, `analyze-wasm`, and `rpc-health`. +- `rpc-health` now consumes bounded TOML configuration, timeout settings, and environment overrides; endpoint precedence is explicit in the CLI reference. +- Release-validation workflow, release checklist, Drips readiness checklist, and manual SARIF upload workflow for GitHub code scanning. +- RustSec dependency auditing and declared Rust 1.91 workspace MSRV in PR #127. +- Malformed-XDR regression corpus and bounded-input hardening. +- CI: formatting, Clippy, and Linux, Windows, and macOS tests. ### Fixed @@ -40,3 +30,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 `Limits::none()`. A crafted XDR length prefix can no longer request an unbounded allocation (memory-exhaustion denial of service); such inputs are rejected as a structured decode error. Found by the `decode_scval` fuzz target. + +### Limitations + +- These changes are on open pull-request branches and are not all merged to + `main`. +- `contractspecv0` XDR entries are decoded into official XDR-JSON definitions + with a variant kind and best-effort name; a normalized ABI schema remains future work. +- Analysis findings are structural heuristics, not a comprehensive + vulnerability detector. +- SARIF upload requires manual workflow dispatch, a repository-relative WASM + artifact, and GitHub code-scanning permissions. +- The configuration API is currently wired into `rpc-health`; other commands do not consume RPC configuration because they do not need it. diff --git a/Cargo.lock b/Cargo.lock index 0a11925..2733e8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,18 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "android_system_properties" version = "0.1.6" @@ -47,7 +59,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -58,9 +70,15 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys", + "windows-sys 0.61.2", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.5.1" @@ -85,6 +103,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" + [[package]] name = "block-buffer" version = "0.10.4" @@ -115,11 +139,17 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + [[package]] name = "cc" -version = "1.4.7" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54413ede23c2daf518f35156dfde027feb2374004d63bd497f983c8db9c0e313" +checksum = "86e055fa6e3e40ad6187ba25b6855eeb635d5271f8e0f0e1e9a5826b8d75bea0" dependencies = [ "find-msvc-tools", "shlex", @@ -131,6 +161,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + [[package]] name = "cfg_eval" version = "0.1.2" @@ -142,6 +178,17 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "rand_core", +] + [[package]] name = "chrono" version = "0.4.45" @@ -185,7 +232,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -215,6 +262,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + [[package]] name = "crate-git-revision" version = "0.0.6" @@ -267,7 +323,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -278,7 +334,7 @@ checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" dependencies = [ "darling_core", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -289,55 +345,86 @@ checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" [[package]] name = "defmt" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +checksum = "e4a0795ac740cb54d7b3775c9c17345f6aaecf884c01733b39d6017738e0e3e1" dependencies = [ - "bitflags", + "bitflags 1.3.2", "defmt-macros", ] [[package]] name = "defmt-macros" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +checksum = "840bb056bd99c495c9188198a258ab787c79f306d864db3e6e8451442d6d8366" dependencies = [ "defmt-parser", "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.9", ] [[package]] name = "defmt-parser" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +checksum = "71958f69fb2de856c5dd51ac9377e986ca1aa5919f931bf5395a807277f889a0" dependencies = [ - "thiserror 2.0.20", + "thiserror 2.0.21", ] [[package]] name = "deranged" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +checksum = "9e9de72ce2ad1f90dc62fa25f0f430ef85eb4b0d8fa0be4f30373bc40a21d28e" dependencies = [ "serde_core", ] +[[package]] +name = "devkit-analysis" +version = "0.1.0" +dependencies = [ + "devkit-contract", + "serde", + "serde_json", +] + [[package]] name = "devkit-cli" version = "0.1.0" dependencies = [ "clap", + "devkit-analysis", + "devkit-config", + "devkit-contract", "devkit-core", "devkit-rpc", "devkit-xdr", "serde_json", ] +[[package]] +name = "devkit-config" +version = "0.1.0" +dependencies = [ + "serde", + "toml", +] + +[[package]] +name = "devkit-contract" +version = "0.1.0" +dependencies = [ + "devkit-core", + "serde", + "serde_json", + "stellar-xdr", + "wasmparser", +] + [[package]] name = "devkit-core" version = "0.1.0" @@ -353,6 +440,7 @@ name = "devkit-rpc" version = "0.1.0" dependencies = [ "devkit-core", + "reqwest", "serde", "serde_json", "url", @@ -387,7 +475,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -398,9 +486,9 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "equivalent" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826" [[package]] name = "escape-bytes" @@ -416,9 +504,9 @@ checksum = "40404c3f5f511ec4da6fe866ddf6a717c309fdbb69fbbad7b0f3edab8f2e835f" [[package]] name = "find-msvc-tools" -version = "0.1.13" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25905e51abafe4dcea6c15fec58c57b601cdbd0ee53d22ea1d3016c587d39b" +checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48" [[package]] name = "form_urlencoded" @@ -429,12 +517,34 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + [[package]] name = "futures-core" version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + [[package]] name = "futures-task" version = "0.3.34" @@ -448,7 +558,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-core", + "futures-io", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "slab", ] @@ -463,6 +576,33 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "rand_core", + "wasm-bindgen", +] + [[package]] name = "hash32" version = "0.3.1" @@ -478,6 +618,16 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "serde", +] + [[package]] name = "hashbrown" version = "0.17.1" @@ -510,6 +660,104 @@ dependencies = [ "serde", ] +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3e324da4c95177d6291d4c8730197c0d1822f8a9766814a4a44fa5ab797c9c" +dependencies = [ + "atomic-waker", + "bytes", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-util" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff" +dependencies = [ + "base64 0.23.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + [[package]] name = "iana-time-zone" version = "0.1.65" @@ -536,9 +784,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +checksum = "fe090a42a0d3140fed8c63263ed1fecc02422cb2f4bfe55cd8de2de1b8e105dd" dependencies = [ "displaydoc", "potential_utf", @@ -550,9 +798,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +checksum = "25d29b57c17c6528d0dc4c2141cfa2d08200465acb537a3909c8b9b8a4a984c0" dependencies = [ "displaydoc", "litemap", @@ -563,9 +811,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +checksum = "404a3b8c486f205c87fe7f23ab6894d84a4ad76d6e4671ae1981ba7def87c599" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -577,15 +825,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" +checksum = "20f0088bfb7441f7a37fa0c9e20cb1e9cba1cac6b9ce8cd2a6dc44294f8e8cbc" [[package]] name = "icu_properties" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +checksum = "2ea83d991c56158f7cf898bf86436c1dfe7c07ee82fe88951f2695c9f37a379f" dependencies = [ "displaydoc", "icu_collections", @@ -598,15 +846,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" +checksum = "b98f29af82d02b163fe50161388350e743bb3107cc8e8c2f7314cb505faddfcf" [[package]] name = "icu_provider" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +checksum = "413099315636c588ee8f011190b329b971a09a8fae9d157e01dcae8379468514" dependencies = [ "displaydoc", "icu_locale_core", @@ -667,6 +915,12 @@ dependencies = [ "serde_core", ] +[[package]] +name = "ipnet" +version = "2.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -681,9 +935,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.37" +version = "0.2.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1baf72f08796de0260609515130699b890ac25f30e610ad894bc5856cafdb" +checksum = "b2b005715dcbeb0089a3c0dab99f2ff1cc3b2525323552703d648585d342a383" dependencies = [ "defmt", "jiff-core", @@ -708,9 +962,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.37" +version = "0.2.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "378268a1116ad67ae6228701118ac9f491d78fda38a40a1f1a9e1348de6f7212" +checksum = "2cc9817253cf7c7ee4684451bd327e88d6f3658014e54a29198625590650695c" dependencies = [ "jiff-core", "proc-macro2", @@ -720,9 +974,9 @@ dependencies = [ [[package]] name = "jiff-tzdb" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e" +checksum = "fa8377070c6bae868759445e5a77f66d84f0b72f3a054bfb00e6d038b8282da7" [[package]] name = "jiff-tzdb-platform" @@ -735,9 +989,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.105" +version = "0.3.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" dependencies = [ "cfg-if", "futures-util", @@ -746,9 +1000,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.189" +version = "0.2.191" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a" [[package]] name = "litemap" @@ -762,12 +1016,29 @@ version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" +[[package]] +name = "lru-slab" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" + [[package]] name = "memchr" version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +[[package]] +name = "mio" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + [[package]] name = "num-conv" version = "0.2.2" @@ -833,9 +1104,9 @@ dependencies = [ [[package]] name = "powerfmt" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +checksum = "4a6394b9e965e73d0a289ee54f589087e2c676aedf60885baf52c76b771e4958" [[package]] name = "proc-macro2" @@ -846,6 +1117,62 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quinn" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4051e23e9185c255a7e33ef59cdbca87a22d359052eecd22fc6b901fb37d9d11" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.21", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e750cca55fe4f0439a15d0bb529da9651e79993e8e72c61a899a36d462befbe" +dependencies = [ + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.21", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af66907df18639dcf4db56ca65490cabc4b27a97dbadd96f2926cca73298f016" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + [[package]] name = "quote" version = "1.0.47" @@ -855,6 +1182,38 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core", +] + [[package]] name = "ref-cast" version = "1.0.27" @@ -872,7 +1231,102 @@ checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", +] + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustls" +version = "0.23.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] @@ -881,6 +1335,12 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + [[package]] name = "schemars" version = "0.8.22" @@ -906,9 +1366,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" +checksum = "ab508826f74a77ca9d5aba6ff19b522583ee3eaf28a19384ff3d0e5835fadf6e" dependencies = [ "dyn-clone", "ref-cast", @@ -916,6 +1376,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + [[package]] name = "serde" version = "1.0.229" @@ -943,14 +1409,14 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] name = "serde_json" -version = "1.0.151" +version = "1.0.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" dependencies = [ "itoa", "memchr", @@ -968,11 +1434,23 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_with" -version = "3.23.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "935177bb8c0cd8ca1a4e6d1a2ac8988bea69cab4f9d3a31311e012ad27868ea4" +checksum = "df9adc193c780ef8f159aee8b61e2d5801aaa555e6eb0947fe45530ec506296f" dependencies = [ "base64 0.23.1", "bs58", @@ -983,7 +1461,7 @@ dependencies = [ "jiff", "schemars 0.8.22", "schemars 0.9.0", - "schemars 1.2.2", + "schemars 1.2.3", "serde_core", "serde_json", "serde_with_macros", @@ -992,14 +1470,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.23.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d607aa01a3cb0ad757d6fd216136910db3c97b102fe686585689615a02dbcdc" +checksum = "3e17bbc68e28663bbbb90df47e058aa7eda4fb445b89fe70457bb94fbccf6e49" dependencies = [ "darling", "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -1009,7 +1487,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -1027,9 +1505,19 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.16.1" +version = "1.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891" +checksum = "d0d5d4b1981b8bbbb7aed204cae5726d683d5d6ac7e74ec54122ea28f315e7c8" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] [[package]] name = "stable_deref_trait" @@ -1061,9 +1549,9 @@ dependencies = [ [[package]] name = "stellar-xdr" -version = "28.0.0" +version = "28.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93d09ff8b9f919b084f664003c4c546ac66a76affd5429460dbe29f4b326f8e" +checksum = "52599dcc4daa661c19d912f5ad55322772515a198c0da3c985ef3922f7f40b77" dependencies = [ "base64 0.22.1", "cfg_eval", @@ -1083,6 +1571,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.119" @@ -1096,15 +1590,24 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.6" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.14.0" @@ -1113,7 +1616,7 @@ checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -1127,11 +1630,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.20" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" dependencies = [ - "thiserror-impl 2.0.20", + "thiserror-impl 2.0.21", ] [[package]] @@ -1147,13 +1650,13 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.20" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -1188,9 +1691,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +checksum = "abc840c238154d58f9e86b2792261edfde565a17696e1cda1a449de27979801a" dependencies = [ "displaydoc", "zerovec", @@ -1202,6 +1705,30 @@ version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" +[[package]] +name = "tokio" +version = "1.54.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "toml" version = "0.8.23" @@ -1243,6 +1770,76 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags 2.13.2", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "typenum" version = "1.20.1" @@ -1251,9 +1848,15 @@ checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicode-ident" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" @@ -1285,11 +1888,26 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "want" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec4cdd0dd910afe868b7ef477227d8d538b46b3075031afee8a9f2acb0a2ed0b" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + [[package]] name = "wasm-bindgen" -version = "0.2.128" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" dependencies = [ "cfg-if", "once_cell", @@ -1298,11 +1916,22 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbab34de2d982e9b48e18d216d04c4a6f641066ff19ffb699980f591ee3610e" +dependencies = [ + "js-sys", + "tokio", + "wasm-bindgen", +] + [[package]] name = "wasm-bindgen-macro" -version = "0.2.128" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1310,26 +1939,69 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.128" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.128" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" dependencies = [ "unicode-ident", ] +[[package]] +name = "wasmparser" +version = "0.219.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5220ee4c6ffcc0cb9d7c47398052203bc902c8ef3985b0c8134118440c0b2921" +dependencies = [ + "ahash", + "bitflags 2.13.2", + "hashbrown 0.14.5", + "indexmap 2.14.2", + "semver", + "serde", +] + +[[package]] +name = "web-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5f280bbb2b287bc8e7de7e3852945a0ff50d1bbcb5ed42b6c7a568c546df171" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "windows-core" version = "0.62.2" @@ -1389,6 +2061,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -1398,6 +2079,70 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "winnow" version = "0.7.15" @@ -1415,9 +2160,9 @@ checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" [[package]] name = "yoke" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +checksum = "417ef3fcf6c300b7dbd25c85531a746931c02ad536f3cf4786ed1e88223340b3" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -1426,16 +2171,36 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33811428bee40dbceb6d545e95754741d17a6aef9a4849f0fd62e2ba4f412a78" +checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.8.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5fe1f8f1b06191a00962174c61aa5005e0bb391a6d80d07e24d115c01a92ed8" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863ad3ac83293fb4d740aedbfdc9240dd8d1a50c1099acd76ce80ce7c7230c7f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "zerofrom" version = "0.1.8" @@ -1453,15 +2218,15 @@ checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", "synstructure", ] [[package]] name = "zeroize" -version = "1.9.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879" dependencies = [ "zeroize_derive", ] @@ -1490,9 +2255,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +checksum = "f58a2799cfe9a1670c7ea8edbd52ef8edb93cb37a7f83bf31ba9e35d9dae6a40" dependencies = [ "yoke", "zerofrom", @@ -1501,13 +2266,13 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.6" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +checksum = "c3743ddbf47738d929cd1ae2e199d40d0162979dfe31aef8ae646d2bf00bf97a" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 32668e3..79e05ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,11 +5,15 @@ members = [ "crates/devkit-xdr", "crates/devkit-rpc", "crates/devkit-cli", + "crates/devkit-contract", + "crates/devkit-analysis", + "crates/devkit-config", ] [workspace.package] version = "0.1.0" edition = "2021" +rust-version = "1.91" license = "Apache-2.0" authors = ["StellarFoundry"] repository = "https://github.com/StellarFoundry/stellar-devkit" @@ -25,10 +29,15 @@ anyhow = "1" clap = { version = "4", features = ["derive"] } stellar-xdr = { version = "28.0.0", features = ["serde", "base64", "alloc"] } stellar-strkey = "0.0.18" +reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] } +wasmparser = "0.219" devkit-core = { path = "crates/devkit-core", version = "0.1.0" } devkit-xdr = { path = "crates/devkit-xdr", version = "0.1.0" } devkit-rpc = { path = "crates/devkit-rpc", version = "0.1.0" } +devkit-contract = { path = "crates/devkit-contract", version = "0.1.0" } +devkit-analysis = { path = "crates/devkit-analysis", version = "0.1.0" } +devkit-config = { path = "crates/devkit-config", version = "0.1.0" } [profile.release] lto = "thin" diff --git a/README.md b/README.md index e370771..aff4281 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,7 @@ The core works fully **offline** and never requires AI. - `stellar-foundry` CLI — `version`, `doctor`, `strkey`, `scval`, `envelope`, `event`. -Everything else (RPC, fixtures, contract inspection, security analysis) is -planned and tracked as issues; it is not implemented. +Library APIs and CLI commands now include bounded HTTP RPC health checks, structural WASM inspection, deterministic findings with SARIF serialization, and validated TOML configuration. See [docs/IMPLEMENTED_CAPABILITIES.md](docs/IMPLEMENTED_CAPABILITIES.md) for exact scope and known limitations. ## Install and build @@ -38,6 +37,9 @@ stellar-foundry strkey CA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAXE stellar-foundry scval --format json stellar-foundry envelope --format json stellar-foundry event --format json +stellar-foundry inspect-wasm ./contract.wasm +stellar-foundry analyze-wasm --format sarif ./contract.wasm +stellar-foundry rpc-health https://soroban-testnet.stellar.org ``` Exit codes: `0` success, `2` usage error, `3` runtime/decode error. @@ -61,6 +63,7 @@ Exit codes: `0` success, `2` usage error, `3` runtime/decode error. - [Project status](docs/PROJECT_STATUS.md) - [Roadmap](docs/ROADMAP.md) - [Contributing through Drips Wave](docs/WAVE.md) +- [Drips readiness checklist](docs/DRIPS_READINESS.md) ## Contributing diff --git a/crates/devkit-analysis/Cargo.toml b/crates/devkit-analysis/Cargo.toml new file mode 100644 index 0000000..7ca0c4f --- /dev/null +++ b/crates/devkit-analysis/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "devkit-analysis" +version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true +repository.workspace = true +description = "Deterministic structural security checks and SARIF output for Soroban WASM." + +[dependencies] +devkit-contract.workspace = true +serde.workspace = true +serde_json.workspace = true diff --git a/crates/devkit-analysis/src/lib.rs b/crates/devkit-analysis/src/lib.rs new file mode 100644 index 0000000..f160e82 --- /dev/null +++ b/crates/devkit-analysis/src/lib.rs @@ -0,0 +1,118 @@ +//! Deterministic, evidence-backed structural checks for contract WASM. +//! Findings are indicators, not proof that a contract is vulnerable or malicious. + +use devkit_contract::ContractReport; +use serde::Serialize; +use serde_json::{json, Value}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum Severity { + Error, + Warning, + Note, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct Finding { + pub rule_id: String, + pub title: String, + pub severity: Severity, + pub message: String, + pub evidence: String, +} + +pub fn analyze(report: &ContractReport) -> Vec { + let mut findings = Vec::new(); + if !report.has_contract_spec { + findings.push(Finding { + rule_id: "DEVKIT-WASM-001".into(), + title: "Soroban contract specification is absent".into(), + severity: Severity::Warning, + message: "No contractspecv0 custom section was found; callers may not be able to inspect typed contract interfaces from this artifact.".into(), + evidence: format!("custom_sections={}", report.custom_sections.join(",")), + }); + } + for import in report.imports.iter().filter(|item| item.module != "env") { + findings.push(Finding { + rule_id: "DEVKIT-WASM-002".into(), + title: "Import from a non-standard module".into(), + severity: Severity::Warning, + message: "The module imports a symbol from a module other than env. Verify this import is expected for the target runtime.".into(), + evidence: format!("{}::{}", import.module, import.name), + }); + } + if report.wasm_bytes > 1024 * 1024 { + findings.push(Finding { + rule_id: "DEVKIT-WASM-003".into(), + title: "Large WASM artifact".into(), + severity: Severity::Note, + message: "The WASM artifact exceeds 1 MiB. Review size and deployment/resource constraints; size alone is not a vulnerability.".into(), + evidence: format!("wasm_bytes={}", report.wasm_bytes), + }); + } + findings +} + +pub fn to_sarif(findings: &[Finding], artifact_uri: Option<&str>) -> Value { + let rules = vec![ + json!({"id":"DEVKIT-WASM-001","name":"MissingContractSpec","shortDescription":{"text":"Soroban contract specification is absent"},"defaultConfiguration":{"level":"warning"}}), + json!({"id":"DEVKIT-WASM-002","name":"NonStandardImport","shortDescription":{"text":"Import from a non-standard module"},"defaultConfiguration":{"level":"warning"}}), + json!({"id":"DEVKIT-WASM-003","name":"LargeWasmArtifact","shortDescription":{"text":"Large WASM artifact"},"defaultConfiguration":{"level":"note"}}), + ]; + let results: Vec = findings + .iter() + .map(|finding| { + let level = match finding.severity { + Severity::Error => "error", + Severity::Warning => "warning", + Severity::Note => "note", + }; + let mut result = json!({ + "ruleId": finding.rule_id, + "level": level, + "message": {"text": finding.message}, + "properties": {"title": finding.title, "evidence": finding.evidence} + }); + if let Some(uri) = artifact_uri { + result["locations"] = + json!([{"physicalLocation":{"artifactLocation":{"uri":uri}}}]); + } + result + }) + .collect(); + json!({ + "$schema":"https://json.schemastore.org/sarif-2.1.0.json", + "version":"2.1.0", + "runs":[{"tool":{"driver":{"name":"stellar-foundry","informationUri":"https://github.com/StellarFoundry/stellar-devkit","rules":rules}},"results":results}] + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use devkit_contract::{ContractReport, ImportInfo}; + + #[test] + fn emits_evidence_for_nonstandard_imports() { + let report = ContractReport { + wasm_bytes: 100, + imports: vec![ImportInfo { + module: "host".into(), + name: "call".into(), + }], + exports: vec![], + custom_sections: vec![], + has_contract_spec: false, + contract_spec_bytes: 0, + contract_spec_entries: vec![], + }; + let findings = analyze(&report); + assert!(findings + .iter() + .any(|item| item.rule_id == "DEVKIT-WASM-002" && item.evidence == "host::call")); + let sarif = to_sarif(&findings, Some("contract.wasm")); + assert_eq!(sarif["version"], "2.1.0"); + assert!(sarif["runs"][0]["results"].as_array().unwrap().len() >= 2); + } +} diff --git a/crates/devkit-cli/Cargo.toml b/crates/devkit-cli/Cargo.toml index 42b434b..e886cfa 100644 --- a/crates/devkit-cli/Cargo.toml +++ b/crates/devkit-cli/Cargo.toml @@ -17,3 +17,7 @@ devkit-xdr.workspace = true devkit-rpc.workspace = true clap.workspace = true serde_json.workspace = true + +devkit-contract.workspace = true +devkit-analysis.workspace = true +devkit-config.workspace = true diff --git a/crates/devkit-cli/src/main.rs b/crates/devkit-cli/src/main.rs index d8591bf..fd9e51f 100644 --- a/crates/devkit-cli/src/main.rs +++ b/crates/devkit-cli/src/main.rs @@ -6,8 +6,10 @@ //! * `3` runtime error (decode failure, I/O) use std::{ + fs, io::{self, IsTerminal, Read}, process::ExitCode, + time::Duration, }; use clap::{Parser, Subcommand, ValueEnum}; @@ -23,13 +25,14 @@ const MAX_STDIN_BYTES: usize = 16 * 1024 * 1024; enum FormatArg { Terminal, Json, + Sarif, } impl From for OutputFormat { fn from(value: FormatArg) -> Self { match value { FormatArg::Terminal => OutputFormat::Terminal, - FormatArg::Json => OutputFormat::Json, + FormatArg::Json | FormatArg::Sarif => OutputFormat::Json, } } } @@ -38,6 +41,10 @@ impl From for OutputFormat { #[derive(Debug, Parser)] #[command(name = "stellar-foundry", version, about, long_about = None)] struct Cli { + /// Optional TOML configuration file (used by commands that support configuration). + #[arg(long, global = true)] + config: Option, + /// Output format. #[arg(long, value_enum, default_value_t = FormatArg::Terminal, global = true)] format: FormatArg, @@ -77,6 +84,15 @@ enum Command { /// The endpoint URL to validate. url: String, }, + /// Inspect a WASM module without executing it. + InspectWasm { path: String }, + /// Run structural checks against a WASM module; --format sarif emits SARIF 2.1.0. + AnalyzeWasm { path: String }, + /// Query getHealth from an explicitly supplied Stellar RPC endpoint. + RpcHealth { + /// RPC endpoint; overrides config file and environment. + endpoint: Option, + }, } #[derive(Debug)] @@ -157,6 +173,7 @@ fn print_json_error(category: &str, message: &str) { fn run(cli: Cli) -> Result { let format: OutputFormat = cli.format.into(); + let config_path = cli.config.clone(); match cli.command { Command::Version => { println!("{TOOL_NAME} {VERSION}"); @@ -191,6 +208,54 @@ fn run(cli: Cli) -> Result { print_value(format, &xdr::summary("contract_event", &decoded)); Ok(EXIT_SUCCESS) } + Command::InspectWasm { path } => { + let bytes = read_wasm(&path)?; + let report = devkit_contract::inspect_wasm(&bytes).map_err(runtime)?; + let value = serde_json::to_value(&report) + .map_err(|error| CliError::new(EXIT_ERROR, "internal", error.to_string()))?; + print_value(format, &value); + Ok(EXIT_SUCCESS) + } + Command::AnalyzeWasm { path } => { + let bytes = read_wasm(&path)?; + let report = devkit_contract::inspect_wasm(&bytes).map_err(runtime)?; + let findings = devkit_analysis::analyze(&report); + if cli.format == FormatArg::Sarif { + println!("{}", devkit_analysis::to_sarif(&findings, Some(&path))); + } else { + let count = findings.len(); + print_value( + format, + &serde_json::json!({ "findings": findings, "count": count }), + ); + } + Ok(EXIT_SUCCESS) + } + Command::RpcHealth { endpoint } => { + let mut config = load_config(config_path.as_deref())?; + config + .apply_environment() + .map_err(|message| CliError::new(EXIT_USAGE, "configuration", message))?; + let endpoint = endpoint.or(config.rpc_url).ok_or_else(|| { + CliError::new( + EXIT_USAGE, + "configuration", + "provide an RPC endpoint argument, set rpc_url in the config file, or set STELLAR_FOUNDRY_RPC_URL", + ) + })?; + devkit_config::diagnostic(config.verbose, "rpc_health_start"); + let endpoint = devkit_rpc::Endpoint::parse(&endpoint).map_err(runtime)?; + let transport = + devkit_rpc::HttpTransport::new(endpoint, Duration::from_millis(config.timeout_ms)) + .map_err(runtime)?; + let health = devkit_rpc::Client::new(transport) + .health() + .map_err(runtime)?; + let value = serde_json::to_value(health) + .map_err(|error| CliError::new(EXIT_ERROR, "internal", error.to_string()))?; + print_value(format, &value); + Ok(EXIT_SUCCESS) + } Command::Endpoint { url } => { let endpoint = devkit_rpc::Endpoint::parse(&url).map_err(runtime)?; let report = serde_json::json!({ @@ -218,14 +283,97 @@ fn doctor(format: OutputFormat) { let report = serde_json::json!({ "tool": TOOL_NAME, "version": VERSION, - "capabilities": ["strkey", "scval", "transaction_envelope", "contract_event", "endpoint_validation"], + "capabilities": ["strkey", "scval", "transaction_envelope", "contract_event", "endpoint_validation", "wasm_inspection", "wasm_analysis", "rpc_health"], "networks": networks, - "network_access": "none (offline decoding only)", - "rpc_transport": "mock only (no live transport yet)", + "network_access": "explicit only (rpc-health command)", + "rpc_transport": "bounded HTTP JSON-RPC and deterministic mock", }); print_value(format, &report); } +/// Load optional TOML configuration with a strict size limit. Environment +/// overrides are applied by the command so unrelated CLI commands remain +/// independent of RPC-specific environment variables. +fn load_config(path: Option<&str>) -> Result { + const MAX_CONFIG_BYTES: u64 = 1024 * 1024; + let config = if let Some(path) = path { + let metadata = fs::metadata(path).map_err(|error| { + CliError::new( + EXIT_ERROR, + "io", + format!("failed to inspect config file: {error}"), + ) + })?; + if metadata.len() > MAX_CONFIG_BYTES { + return Err(CliError::new( + EXIT_USAGE, + "configuration", + "config file exceeds the 1048576-byte limit", + )); + } + let content = fs::read_to_string(path).map_err(|error| { + CliError::new( + EXIT_ERROR, + "io", + format!("failed to read config file: {error}"), + ) + })?; + if content.len() as u64 > MAX_CONFIG_BYTES { + return Err(CliError::new( + EXIT_USAGE, + "configuration", + "config file exceeds the 1048576-byte limit", + )); + } + devkit_config::Config::parse_toml(&content) + .map_err(|message| CliError::new(EXIT_USAGE, "configuration", message))? + } else { + devkit_config::Config::default() + }; + config + .validate() + .map_err(|message| CliError::new(EXIT_USAGE, "configuration", message))?; + Ok(config) +} + +fn read_wasm(path: &str) -> Result, CliError> { + let metadata = fs::metadata(path).map_err(|error| { + CliError::new( + EXIT_ERROR, + "io", + format!("failed to inspect WASM file: {error}"), + ) + })?; + if metadata.len() > devkit_contract::MAX_WASM_BYTES as u64 { + return Err(CliError::new( + EXIT_ERROR, + "invalid_input", + format!( + "WASM exceeds the {}-byte limit", + devkit_contract::MAX_WASM_BYTES + ), + )); + } + let bytes = fs::read(path).map_err(|error| { + CliError::new( + EXIT_ERROR, + "io", + format!("failed to read WASM file: {error}"), + ) + })?; + if bytes.len() > devkit_contract::MAX_WASM_BYTES { + return Err(CliError::new( + EXIT_ERROR, + "invalid_input", + format!( + "WASM exceeds the {}-byte limit", + devkit_contract::MAX_WASM_BYTES + ), + )); + } + Ok(bytes) +} + /// Resolve a decode argument, reading bounded UTF-8 input only when explicitly asked. fn read_value(value: String) -> Result { if value != "-" { @@ -283,3 +431,41 @@ fn runtime(error: devkit_core::DevkitError) -> CliError { }; CliError::new(EXIT_ERROR, category, error.to_string()) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn loads_rpc_config_file() { + let path = std::env::temp_dir().join(format!( + "stellar-foundry-config-{}.toml", + std::process::id() + )); + fs::write( + &path, + "rpc_url = \"https://soroban-testnet.stellar.org\"\ntimeout_ms = 2500\nverbose = true\n", + ) + .expect("write temporary config"); + let config = load_config(path.to_str()).expect("valid config"); + let _ = fs::remove_file(&path); + assert_eq!( + config.rpc_url.as_deref(), + Some("https://soroban-testnet.stellar.org") + ); + assert_eq!(config.timeout_ms, 2500); + assert!(config.verbose); + } + + #[test] + fn rejects_unknown_config_keys() { + let path = std::env::temp_dir().join(format!( + "stellar-foundry-invalid-config-{}.toml", + std::process::id() + )); + fs::write(&path, "unexpected = true\n").expect("write temporary config"); + let result = load_config(path.to_str()); + let _ = fs::remove_file(&path); + assert!(result.is_err()); + } +} diff --git a/crates/devkit-config/Cargo.toml b/crates/devkit-config/Cargo.toml new file mode 100644 index 0000000..db1f4d2 --- /dev/null +++ b/crates/devkit-config/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "devkit-config" +version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true +repository.workspace = true +description = "Validated configuration and secret-safe diagnostics for StellarFoundry DevKit." + +[dependencies] +serde.workspace = true +toml.workspace = true diff --git a/crates/devkit-config/src/lib.rs b/crates/devkit-config/src/lib.rs new file mode 100644 index 0000000..bf226af --- /dev/null +++ b/crates/devkit-config/src/lib.rs @@ -0,0 +1,90 @@ +//! Configuration parsing and secret-safe diagnostics. +//! Configuration is loaded from TOML text; environment variables can override +//! endpoint, timeout and verbosity without printing their values. + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(default, deny_unknown_fields)] +pub struct Config { + pub rpc_url: Option, + pub timeout_ms: u64, + pub verbose: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + rpc_url: None, + timeout_ms: 10_000, + verbose: false, + } + } +} + +impl Config { + pub fn parse_toml(input: &str) -> Result { + let config: Self = + toml::from_str(input).map_err(|error| format!("invalid config: {error}"))?; + config.validate()?; + Ok(config) + } + + pub fn validate(&self) -> Result<(), String> { + if !(100..=120_000).contains(&self.timeout_ms) { + return Err("timeout_ms must be between 100 and 120000".into()); + } + if let Some(url) = &self.rpc_url { + if url.trim().is_empty() { + return Err("rpc_url must not be empty".into()); + } + } + Ok(()) + } + + pub fn apply_environment(&mut self) -> Result<(), String> { + if let Ok(value) = std::env::var("STELLAR_FOUNDRY_RPC_URL") { + self.rpc_url = Some(value); + } + if let Ok(value) = std::env::var("STELLAR_FOUNDRY_TIMEOUT_MS") { + self.timeout_ms = value + .parse() + .map_err(|_| "STELLAR_FOUNDRY_TIMEOUT_MS must be an integer".to_string())?; + } + if let Ok(value) = std::env::var("STELLAR_FOUNDRY_VERBOSE") { + self.verbose = match value.to_ascii_lowercase().as_str() { + "1" | "true" | "yes" => true, + "0" | "false" | "no" => false, + _ => return Err("STELLAR_FOUNDRY_VERBOSE must be true/false".into()), + }; + } + self.validate() + } +} + +/// Emit a static diagnostic event only when verbose mode is enabled. +/// Callers should pass event names, never URLs, headers, payloads or secrets. +pub fn diagnostic(verbose: bool, event: &'static str) { + if verbose { + eprintln!("stellar-foundry: {event}"); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_defaults_and_explicit_values() { + let config = Config::parse_toml("timeout_ms = 5000\nverbose = true").unwrap(); + assert_eq!(config.timeout_ms, 5000); + assert!(config.verbose); + assert_eq!(config.rpc_url, None); + } + + #[test] + fn rejects_unknown_keys_and_unsafe_timeouts() { + assert!(Config::parse_toml("mystery = 1").is_err()); + assert!(Config::parse_toml("timeout_ms = 1").is_err()); + } +} diff --git a/crates/devkit-contract/Cargo.toml b/crates/devkit-contract/Cargo.toml new file mode 100644 index 0000000..8dce73c --- /dev/null +++ b/crates/devkit-contract/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "devkit-contract" +version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true +repository.workspace = true +description = "Bounded, non-executing inspection of Soroban contract WASM artifacts." + +[dependencies] +devkit-core.workspace = true +serde.workspace = true +serde_json.workspace = true +stellar-xdr.workspace = true +wasmparser.workspace = true diff --git a/crates/devkit-contract/src/lib.rs b/crates/devkit-contract/src/lib.rs new file mode 100644 index 0000000..ec93f4c --- /dev/null +++ b/crates/devkit-contract/src/lib.rs @@ -0,0 +1,207 @@ +//! Bounded structural inspection for untrusted Soroban contract WASM. +//! +//! This crate validates the WASM container but never executes it. The +//! `contractspecv0` is decoded as a bounded stream of official Stellar XDR +//! `ScSpecEntry` values. This crate never executes contract code. + +use std::io::Cursor; + +use devkit_core::DevkitError; +use serde::Serialize; +use serde_json::Value; +use stellar_xdr::{Limited, Limits, ReadXdr, ScSpecEntry}; +use wasmparser::{Parser, Payload, Validator}; + +pub const MAX_WASM_BYTES: usize = 16 * 1024 * 1024; +const MAX_SPEC_ENTRIES: usize = 4096; +const MAX_XDR_DEPTH: u32 = 128; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct ImportInfo { + pub module: String, + pub name: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct ExportInfo { + pub name: String, + pub kind: String, + pub index: u32, +} + +#[derive(Debug, Clone, PartialEq, Serialize)] +pub struct ContractSpecEntry { + /// Stable variant name from the official XDR model. + pub kind: String, + /// Best-effort declared name for functions and user-defined types. + pub name: Option, + /// Complete XDR-JSON representation of the decoded entry. + pub definition: Value, +} + +#[derive(Debug, Clone, PartialEq, Serialize)] +pub struct ContractReport { + pub wasm_bytes: usize, + pub imports: Vec, + pub exports: Vec, + pub custom_sections: Vec, + pub has_contract_spec: bool, + pub contract_spec_bytes: usize, + pub contract_spec_entries: Vec, +} + +/// Decode the concatenated ScSpecEntry XDR stream with explicit resource +/// limits. The WASM module is already capped at 16 MiB; an additional entry +/// cap prevents a tiny malicious stream from producing an excessive report. +fn decode_contract_spec(bytes: &[u8]) -> Result, DevkitError> { + if bytes.is_empty() { + return Ok(Vec::new()); + } + let limits = Limits { + depth: MAX_XDR_DEPTH, + len: MAX_WASM_BYTES, + }; + let cursor = Cursor::new(bytes); + let mut reader = Limited::new(cursor, limits); + let mut entries = Vec::new(); + for item in ScSpecEntry::read_xdr_iter(&mut reader) { + let entry = item.map_err(|error| { + DevkitError::decode(format!("invalid contractspecv0 XDR entry: {error}")) + })?; + if entries.len() >= MAX_SPEC_ENTRIES { + return Err(DevkitError::invalid(format!( + "contractspecv0 exceeds the {MAX_SPEC_ENTRIES}-entry limit" + ))); + } + let definition = serde_json::to_value(&entry).map_err(DevkitError::decode)?; + let debug = format!("{entry:?}"); + let kind = debug + .split(['(', ' ', '{']) + .next() + .unwrap_or("Unknown") + .to_owned(); + let name = find_name(&definition); + entries.push(ContractSpecEntry { + kind, + name, + definition, + }); + } + Ok(entries) +} + +fn find_name(value: &Value) -> Option { + match value { + Value::Object(object) => { + if let Some(Value::String(name)) = object.get("name") { + return Some(name.clone()); + } + object.values().find_map(find_name) + } + Value::Array(values) => values.iter().find_map(find_name), + _ => None, + } +} + +pub fn inspect_wasm(bytes: &[u8]) -> Result { + if bytes.is_empty() { + return Err(DevkitError::invalid("WASM input is empty")); + } + if bytes.len() > MAX_WASM_BYTES { + return Err(DevkitError::invalid(format!( + "WASM exceeds the {MAX_WASM_BYTES}-byte limit" + ))); + } + Validator::new() + .validate_all(bytes) + .map_err(|error| DevkitError::decode(format!("invalid WASM module: {error}")))?; + let mut report = ContractReport { + wasm_bytes: bytes.len(), + imports: Vec::new(), + exports: Vec::new(), + custom_sections: Vec::new(), + has_contract_spec: false, + contract_spec_bytes: 0, + contract_spec_entries: Vec::new(), + }; + for payload in Parser::new(0).parse_all(bytes) { + match payload.map_err(DevkitError::decode)? { + Payload::ImportSection(reader) => { + for import in reader { + let import = import.map_err(DevkitError::decode)?; + report.imports.push(ImportInfo { + module: import.module.to_owned(), + name: import.name.to_owned(), + }); + } + } + Payload::ExportSection(reader) => { + for export in reader { + let export = export.map_err(DevkitError::decode)?; + report.exports.push(ExportInfo { + name: export.name.to_owned(), + kind: format!("{:?}", export.kind), + index: export.index, + }); + } + } + Payload::CustomSection(section) => { + let name = section.name().to_owned(); + if name == "contractspecv0" { + if report.has_contract_spec { + return Err(DevkitError::decode( + "WASM module contains multiple contractspecv0 sections", + )); + } + report.has_contract_spec = true; + report.contract_spec_bytes = section.data().len(); + report.contract_spec_entries = decode_contract_spec(section.data())?; + } + report.custom_sections.push(name); + } + _ => {} + } + } + Ok(report) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn rejects_empty_and_non_wasm_inputs() { + assert!(inspect_wasm(&[]).is_err()); + assert!(inspect_wasm(b"not wasm").is_err()); + } + + #[test] + fn rejects_oversized_input_before_parsing() { + let input = vec![0; MAX_WASM_BYTES + 1]; + assert!(inspect_wasm(&input) + .unwrap_err() + .to_string() + .contains("limit")); + } + + #[test] + fn accepts_a_minimal_valid_wasm_module() { + let report = inspect_wasm(b"\0asm\x01\0\0\0").unwrap(); + assert_eq!(report.wasm_bytes, 8); + assert!(report.imports.is_empty()); + assert!(report.exports.is_empty()); + assert!(!report.has_contract_spec); + } + #[test] + fn rejects_malformed_contract_spec_xdr() { + let name = b"contractspecv0"; + let payload_len = 1 + name.len() + 1; + let mut wasm = b"\0asm\x01\0\0\0".to_vec(); + wasm.extend_from_slice(&[0, payload_len as u8, name.len() as u8]); + wasm.extend_from_slice(name); + wasm.push(0xff); + + let error = inspect_wasm(&wasm).unwrap_err().to_string(); + assert!(error.contains("contractspecv0 XDR entry"), "{error}"); + } +} diff --git a/crates/devkit-rpc/Cargo.toml b/crates/devkit-rpc/Cargo.toml index 27e26c8..d2bf3c9 100644 --- a/crates/devkit-rpc/Cargo.toml +++ b/crates/devkit-rpc/Cargo.toml @@ -12,3 +12,4 @@ devkit-core.workspace = true serde.workspace = true serde_json.workspace = true url = "2" +reqwest.workspace = true diff --git a/crates/devkit-rpc/src/lib.rs b/crates/devkit-rpc/src/lib.rs index 466dae6..1f514da 100644 --- a/crates/devkit-rpc/src/lib.rs +++ b/crates/devkit-rpc/src/lib.rs @@ -15,6 +15,9 @@ use std::cell::RefCell; use std::collections::VecDeque; +use std::io::Read; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::time::Duration; use devkit_core::DevkitError; use serde::{Deserialize, Serialize}; @@ -149,6 +152,107 @@ impl Transport for MockTransport { } } +/// A bounded HTTP JSON-RPC transport for a Stellar RPC endpoint. +/// +/// Responses are capped before JSON parsing. Requests have a finite timeout, +/// JSON-RPC identifiers are checked, and server error messages are returned as +/// structured errors. Redirects are disabled by the HTTP client to avoid +/// silently forwarding RPC requests to an unexpected host. +pub struct HttpTransport { + endpoint: String, + client: reqwest::blocking::Client, + next_id: AtomicU64, +} + +impl HttpTransport { + /// Creates a transport with an explicit request timeout. + pub fn new(endpoint: Endpoint, timeout: Duration) -> Result { + if timeout.is_zero() { + return Err(DevkitError::invalid( + "RPC timeout must be greater than zero", + )); + } + let client = reqwest::blocking::Client::builder() + .timeout(timeout) + .redirect(reqwest::redirect::Policy::none()) + .build() + .map_err(|error| { + DevkitError::Io(format!("failed to build RPC HTTP client: {error}")) + })?; + Ok(Self { + endpoint: endpoint.as_str().to_owned(), + client, + next_id: AtomicU64::new(1), + }) + } + + /// Creates a transport with a 10-second request timeout. + pub fn with_default_timeout(endpoint: Endpoint) -> Result { + Self::new(endpoint, Duration::from_secs(10)) + } +} + +impl Transport for HttpTransport { + fn call(&self, method: &str, params: Value) -> Result { + if method.trim().is_empty() { + return Err(DevkitError::invalid("RPC method must not be empty")); + } + let id = self.next_id.fetch_add(1, Ordering::Relaxed); + let request = json!({ + "jsonrpc": "2.0", + "id": id, + "method": method, + "params": params + }); + let response = self + .client + .post(&self.endpoint) + .json(&request) + .send() + .map_err(|error| DevkitError::Io(format!("RPC HTTP request failed: {error}")))?; + let status = response.status(); + let mut body = Vec::new(); + response + .take((MAX_RPC_RESPONSE_BYTES + 1) as u64) + .read_to_end(&mut body) + .map_err(|error| DevkitError::Io(format!("failed reading RPC response: {error}")))?; + if body.len() > MAX_RPC_RESPONSE_BYTES { + return Err(response_too_large()); + } + if !status.is_success() { + return Err(DevkitError::Io(format!( + "RPC endpoint returned HTTP status {status}" + ))); + } + let value: Value = serde_json::from_slice(&body).map_err(DevkitError::decode)?; + if value.get("jsonrpc").and_then(Value::as_str) != Some("2.0") { + return Err(DevkitError::decode( + "RPC response is missing JSON-RPC version 2.0", + )); + } + if value.get("id").and_then(Value::as_u64) != Some(id) { + return Err(DevkitError::decode( + "RPC response identifier does not match request", + )); + } + if let Some(error) = value.get("error") { + let code = error + .get("code") + .and_then(Value::as_i64) + .unwrap_or_default(); + let message = error + .get("message") + .and_then(Value::as_str) + .unwrap_or("unknown RPC error"); + return Err(DevkitError::decode(format!("RPC error {code}: {message}"))); + } + value + .get("result") + .cloned() + .ok_or_else(|| DevkitError::decode("RPC response has neither result nor error")) + } +} + /// Well-known result of `getHealth`. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] diff --git a/docs/CLI.md b/docs/CLI.md index df6aa73..4d7eca5 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -3,10 +3,10 @@ The `stellar-foundry` binary. ```text -stellar-foundry [--format terminal|json] +stellar-foundry [--config PATH] [--format terminal|json|sarif] ``` -`--format` is global and defaults to `terminal`. +`--format` is global and defaults to `terminal`. `--config PATH` is also global; the TOML configuration is currently consumed by `rpc-health`. ## Commands @@ -38,6 +38,27 @@ Decodes a base64 XDR `ContractEvent` into JSON. Pass `-` to read one value from When `-` is supplied, stdin must be piped or redirected; the CLI never waits for interactive terminal input. Input is limited to 16 MiB and must be valid UTF-8. Only one value is read; streaming multiple values is not supported. +### `rpc-health [ENDPOINT]` + +Checks the health endpoint of a Stellar RPC server. The endpoint can be supplied as an argument, configured as `rpc_url` in a TOML file, or supplied through `STELLAR_FOUNDRY_RPC_URL`. Precedence is command-line endpoint, then environment override, then config file. The request timeout defaults to 10 seconds and can be set with `timeout_ms` (100–120000) or `STELLAR_FOUNDRY_TIMEOUT_MS`. Set `verbose = true` or `STELLAR_FOUNDRY_VERBOSE=true` to emit static diagnostic event names; endpoint values and response payloads are not logged. + +Example config: + +```toml +rpc_url = "https://soroban-testnet.stellar.org" +timeout_ms = 10000 +verbose = false +``` + +Example invocation: + +```text +stellar-foundry --config ./stellar-foundry.toml rpc-health +stellar-foundry rpc-health https://soroban-testnet.stellar.org +``` + +Configuration files are limited to 1 MiB. Unknown TOML keys and invalid timeout values are rejected. + ## Exit codes | Code | Meaning | diff --git a/docs/DRIPS_APPLICATION_DRAFT.md b/docs/DRIPS_APPLICATION_DRAFT.md index e3912f4..1c4d008 100644 --- a/docs/DRIPS_APPLICATION_DRAFT.md +++ b/docs/DRIPS_APPLICATION_DRAFT.md @@ -1,67 +1,56 @@ # Drips Application Draft -Factual draft answers. Nothing here guarantees approval, points, or payout, and -no prior program participation, funding, community size, or contributor activity -is claimed. +This is a factual draft for review before any application is submitted. It does not guarantee acceptance, points, funding, or payout. Claims below distinguish the current open PR branch from features already merged to `main`. ## Project description -StellarFoundry DevKit is a developer infrastructure toolkit for Stellar and -Soroban. It provides bounded XDR/SCVal/transaction/event decoding with -structured output, a typed RPC protocol layer with a deterministic mock -transport, endpoint security validation, and a CLI (`stellar-foundry`). It -composes with official crates rather than reimplementing them, works fully -offline, and never requires AI. +StellarFoundry DevKit is a Rust developer toolkit for Stellar and Soroban. Its current implementation provides bounded decoding of `ScVal`, `TransactionEnvelope`, and `ContractEvent` XDR; Stellar strkey classification; RPC endpoint validation; a typed RPC protocol layer with deterministic mock transport; and a CLI named `stellar-foundry`. -## Stellar ecosystem relevance +The implementation branch also adds bounded HTTP JSON-RPC transport, non-executing WASM inspection, bounded decoding of contractspecv0 XDR entries, deterministic structural findings, SARIF 2.1.0 serialization and a manual code-scanning upload workflow, strict TOML configuration wired into `rpc-health`, and release/readiness workflows. These changes are still under review until PR #133 merges. -The toolkit depends on official Stellar crates (`stellar-xdr`, -`stellar-strkey`) and targets Stellar/Soroban workflows directly. It complements -`stellar-cli` and the official RPC client instead of duplicating them, and it -serves developers building on Stellar. +## Problem and ecosystem relevance -## Planned issues +Stellar developers often need to inspect XDR values, contract artifacts, events, and RPC behavior during development and testing. DevKit centralizes several of these workflows behind reusable Rust APIs and a CLI, with an emphasis on bounded handling of untrusted inputs, deterministic tests, and structured output. -119 issues across core, RPC, XDR/SCVal, transactions, events, contract -inspection, testing, security, CLI, configuration, GitHub/SARIF, performance, -observability, release, and documentation. Complexity: 7 trivial, 84 medium, -28 high. See [ISSUE_BACKLOG.md](ISSUE_BACKLOG.md) and -[DRIPS_ISSUE_AUDIT.md](DRIPS_ISSUE_AUDIT.md). Most issues are not yet -implemented; the working foundation is described in -[PROJECT_STATUS.md](PROJECT_STATUS.md). +The project composes with ecosystem crates such as `stellar-xdr` and `stellar-strkey`. It is intended to complement official Stellar tooling, not replace the official CLI or RPC client. -## Contributor opportunities +## Current implementation and evidence + +- Repository: [StellarFoundry/stellar-devkit](https://github.com/StellarFoundry/stellar-devkit) +- Combined implementation and hardening PR: [PR #133](https://github.com/StellarFoundry/stellar-devkit/pull/133) +- Security/CI hardening PR: [PR #127](https://github.com/StellarFoundry/stellar-devkit/pull/127) +- Latest combined CI run: [CI run #40679489331](https://github.com/StellarFoundry/stellar-devkit/actions/runs/40679489331) -Small, well-scoped `good first issue` tasks (completions, exit-code tests, -Dependabot) through medium features (typed RPC models, config system) to high -work (live transport, fuzzing, SSRF hardening, SARIF, release automation). +The combined CI run passed formatting, Clippy, Linux, Windows, macOS, and Rust 1.91.0 MSRV tests: [CI run #40680053061](https://github.com/StellarFoundry/stellar-devkit/actions/runs/40680053061). The regenerated lockfile includes the new transport and WASM-parser dependencies, and the RustSec audit passed: [audit run #40680053077](https://github.com/StellarFoundry/stellar-devkit/actions/runs/40680053077). -## Supporting links +## Current limitations -- Repository: -- Architecture: [ARCHITECTURE.md](ARCHITECTURE.md) -- Ecosystem research: [ECOSYSTEM_RESEARCH.md](ECOSYSTEM_RESEARCH.md) +- `contractspecv0` entries are decoded with the official Stellar XDR types into XDR-JSON definitions, variant names, and best-effort names; a normalized ABI schema remains future work. +- Security-analysis rules are structural heuristics, not a comprehensive vulnerability detector. +- SARIF can be uploaded using the manual workflow when a repository-relative WASM artifact exists and code-scanning permissions are available. +- RPC configuration is wired into `rpc-health`; other commands do not consume RPC settings. +- The implementation PRs remain open. Do not state that all proposed features are already available on `main`. -## Current project status +## Contributor opportunities -Early. Working foundation plus RPC protocol layer with CI on Linux/Windows/macOS. -See [PROJECT_STATUS.md](PROJECT_STATUS.md). Live RPC and higher-level analysis are -not implemented. +The issue backlog documents potential work across RPC, XDR/SCVal, contract inspection, testing, security, CLI, configuration, SARIF, performance, observability, release engineering, and documentation. Backlog issues are proposed work, not completed functionality. Check current issue states and assignment requirements before describing specific issues as available. -## Maintainer experience +See [ISSUE_BACKLOG.md](ISSUE_BACKLOG.md), [DRIPS_ISSUE_AUDIT.md](DRIPS_ISSUE_AUDIT.md), and [DRIPS_READINESS.md](DRIPS_READINESS.md). -Maintained under the StellarFoundry organization by the account `Ayinkx01`. -StellarFoundry is not claimed to be independent of its maintainer; it is -maintained by `Ayinkx01`. +## Supporting documentation -## Technical roadmap +- [Architecture](ARCHITECTURE.md) +- [Ecosystem research](ECOSYSTEM_RESEARCH.md) +- [Project status](PROJECT_STATUS.md) +- [Testing guide](TESTING.md) +- [Release checklist](RELEASE_CHECKLIST.md) +- [Contributing through Drips Wave](WAVE.md) -Twelve phases from foundation through release; see [ROADMAP.md](ROADMAP.md). +## Final verification before submission -## Honesty statement +- Confirm the latest combined CI remains green after final documentation updates. +- Confirm the RustSec audit passes against the committed lockfile. +- Verify the current Drips Wave program's application process, eligibility, required evidence, and deadlines with the program maintainers. +- Replace this draft only with verified facts from the final repository state; do not claim approval, funding, user traction, or contributor activity without evidence. -- No approval, payout, or contributor activity is guaranteed. -- The issue set is smaller than 125; we do not inflate complexity to reach a - target. -- Metrics such as stars, contributors, and funding are not claimed unless - verified. +No application has been submitted by this draft. diff --git a/docs/DRIPS_READINESS.md b/docs/DRIPS_READINESS.md index fb64e67..640e709 100644 --- a/docs/DRIPS_READINESS.md +++ b/docs/DRIPS_READINESS.md @@ -1,123 +1,62 @@ # Drips Readiness -This document prepares the repository for a possible Drips Wave application. It -contains **no guarantee** of approval, payout, contributor activity, or points. +This document records the repository's current state for a possible Drips Wave application. It does not guarantee approval, funding, payout, contributor activity, or points. ## Project summary -StellarFoundry DevKit is a developer infrastructure toolkit for Stellar and -Soroban. It composes with official crates (`stellar-xdr`, `stellar-strkey`, and -later `stellar-rpc-client`) instead of reimplementing them, and provides a -workflow layer: decoding and inspection with structured output, a typed RPC -protocol layer with deterministic mocks, fixtures, analysis, and a CLI. +StellarFoundry DevKit is a Rust developer toolkit for Stellar and Soroban. It provides bounded XDR decoding, endpoint validation, a typed RPC protocol layer, WASM metadata inspection, deterministic structural findings, SARIF serialization, configuration parsing, and a CLI. -## Problem solved +## Problem and ecosystem fit -Stellar developers repeatedly need to decode XDR/SCVal, inspect transactions and -events, talk to RPC, and test those flows deterministically. That glue code is -rebuilt per project. DevKit centralizes it with stable, testable interfaces and -no AI dependency. +Stellar developers routinely need to inspect XDR values, transactions, events, contract artifacts, and RPC behavior. DevKit aims to consolidate these workflows behind testable Rust APIs and command-line interfaces while composing with ecosystem crates such as `stellar-xdr` and `stellar-strkey`. -## Why it belongs in the Stellar ecosystem +The project complements official Stellar tooling; it should not claim to replace the official CLI or RPC client. -- It targets Stellar and Soroban specifically and depends on official crates. -- It does not duplicate `stellar-cli` or the official RPC client; it complements - them with workflow, testing, and analysis. -- It is useful to the same developers the Stellar ecosystem funds. +## Implementation status on the current combined PR branch -## Current implementation status +The combined branch for PR #133 includes the feature work and the security-hardening changes from PR #127. This combined work is still under review and has **not** been merged to `main`. -- **Working:** workspace with `devkit-core`, `devkit-xdr`, `devkit-rpc`, - `devkit-cli`; bounded base64 decoding of `ScVal`/`TransactionEnvelope`/ - `ContractEvent` to JSON; strkey classification; endpoint security validation; - typed RPC protocol layer with a deterministic mock transport; CLI with stable - exit codes. -- **Not implemented:** live RPC transport, contract inspection, security - analysis, SARIF, GitHub Action, configuration, logging, benchmarks. -- See [PROJECT_STATUS.md](PROJECT_STATUS.md). +- Bounded base64 decoding for `ScVal`, `TransactionEnvelope`, and `ContractEvent`, with structured errors and malformed-input regression fixtures. +- RPC endpoint policy validation and a typed RPC client with deterministic mock transport. +- HTTP JSON-RPC transport with timeout, redirects disabled, a response-size cap, response validation, and structured errors. +- Non-executing WASM validation, structural metadata, and bounded decoding of `contractspecv0` XDR entries into entry kind/name/definition records. +- Deterministic structural analysis findings with stable rule IDs and SARIF 2.1.0 serialization. +- Strict TOML configuration parsing, timeout validation, environment overrides, and static diagnostic events. +- CLI commands for WASM inspection, WASM analysis, and RPC health checks using optional TOML config, environment overrides, and a validated timeout. +- Pinned CI actions, locked dependency resolution, a dedicated Rust 1.91.0 MSRV job, RustSec dependency auditing, release-validation workflow, and release checklist. -## Contributor workflow - -- Issues are labeled by `area/*`, `type/*`, `difficulty/*`, and `phase/*`. -- `good first issue` marks small, well-scoped tasks. -- Each issue states problem, why, context, scope, out-of-scope, guidance, - acceptance criteria, required tests, security considerations, documentation - requirements, dependencies, and a definition of done. -- Contributors request assignment before starting; PRs use `Closes #` and - run the local gate (fmt, clippy, tests). - -## Issue categories - -Core, RPC, XDR, SCVal, transactions, events, contract inspection, testing, -security, CLI, configuration, GitHub, SARIF, observability, documentation, -release. See [ISSUE_BACKLOG.md](ISSUE_BACKLOG.md). - -## Issue quality standards - -- No fabricated or duplicate issues; titles are unique and each is traceable to - real functionality or maintenance work. -- Complexity is honest (`trivial`/`medium`/`high`); points are assigned in the - Drips dashboard. - -## Testing standards - -- Unit tests for pure functions; malformed-input tests for every decoder; - fixture-based tests; property tests and fuzzing planned. -- The local gate is `cargo fmt --check`, `cargo clippy -D warnings`, - `cargo test --all`. +See [IMPLEMENTED_CAPABILITIES.md](IMPLEMENTED_CAPABILITIES.md), [PROJECT_STATUS.md](PROJECT_STATUS.md), [CHANGELOG.md](../CHANGELOG.md), and [PR #133](https://github.com/StellarFoundry/stellar-devkit/pull/133) for exact scope. -## Documentation standards +## Known limitations (must be represented honestly) -Every public feature is documented; `CHANGELOG.md` and `PROJECT_STATUS.md` are -kept factual. +- Contract-spec entries are decoded into XDR-JSON definitions with a variant kind and best-effort name; a normalized, fully typed ABI schema is still future work. +- Analysis rules are structural heuristics, not a complete vulnerability detector and not proof of malicious intent. +- SARIF upload is available through a manual workflow dispatch and requires a WASM artifact in the repository plus code-scanning permissions. +- RPC config is wired into `rpc-health`; other commands do not use RPC settings. Endpoint precedence is command-line, then environment, then config file. +- No claim is made here that the project has published crates or release binaries, has a large user base, or is approved for a Wave. -## Maintainer responsiveness plan - -- Initial response to new issues and PRs within 48 hours during an active Wave. -- Review decision within 72 hours of a reviewable PR. -- Assign contributors promptly; unresolved issues roll over. - -## Security process - -See [SECURITY.md](../SECURITY.md). No network access by default; untrusted input -is bounded and decoded with structured errors; every security claim is backed by -implementation and tests. - -## Release process - -See [ROADMAP.md](ROADMAP.md) and the release issue. Semantic versioning, -changelog, and (planned) cross-platform binaries. - -## Links - -- Repository: -- Backlog: [ISSUE_BACKLOG.md](ISSUE_BACKLOG.md) -- Architecture: [ARCHITECTURE.md](ARCHITECTURE.md) -- Ecosystem research: [ECOSYSTEM_RESEARCH.md](ECOSYSTEM_RESEARCH.md) +## Contributor workflow -## Quality audit outcome +- Issues are labeled by `area/*`, `type/*`, `difficulty/*`, and `phase/*`. +- Contributors request assignment before starting and follow each issue's scope and acceptance criteria. +- PRs reference the relevant issue, include tests and documentation, and report commands and results truthfully. +- The repository gate is `cargo fmt --all -- --check`, `cargo clippy --locked --workspace --all-targets --all-features -- -D warnings`, and `cargo test --locked --workspace --all-features`. CI runs on Linux, Windows, and macOS and includes Rust 1.91.0 MSRV tests; release validation checks packaging. -Every open issue was audited against the Drips maintainer criteria. Results -(see [DRIPS_ISSUE_QUALITY_AUDIT.md](DRIPS_ISSUE_QUALITY_AUDIT.md)): +## Issue quality and backlog -- **89 Drips-ready** issues (`status/ready`). -- **24 blocked by dependency** (`status/blocked`) — not to be added to a Wave - until their dependency lands. -- **3 deferred / future work** (`status/deferred`). -- **3 needs revision** (`status/needs-revision`, clarified on the issue). -- **1 duplicate removed**, **1 merged** (issue count reduced from 121 to 119). +The backlog audit and issue labels are documented in [DRIPS_ISSUE_QUALITY_AUDIT.md](DRIPS_ISSUE_QUALITY_AUDIT.md) and [ISSUE_BACKLOG.md](ISSUE_BACKLOG.md). Backlog issues are planned work, not completed features. Do not present issue count or labels as evidence that those features already exist. -Complexity labels were not altered. Drips determines applicable points and -budgets through its own system. +## Security and release process -## Known limitations +See [SECURITY.md](../SECURITY.md), [TESTING.md](TESTING.md), and [RELEASE_CHECKLIST.md](RELEASE_CHECKLIST.md). Security claims must correspond to implementation and tests; secrets, endpoint values, and payloads should not be written to logs. -- The backlog contains 119 open issues. Most are **not implemented**; the - project is early (foundation and RPC protocol layer only). See - [BUILD_REPORT.md](BUILD_REPORT.md). -- No live RPC, no release binaries, no published crates. -- Traction is minimal and is reported honestly. +## Appeal readiness checklist -## Future roadmap +- [x] Both PR heads are included in a combined review branch; PR #127 remains open and unmerged. +- [x] The combined implementation passed CI on Linux, Windows, and macOS, plus formatting, Clippy, and Rust 1.91.0 MSRV tests. +- [x] The RustSec audit passed against the regenerated lockfile on the combined head. +- [x] User-facing documentation, project status, and changelog distinguish proposed work from work merged to `main`. +- [ ] The application is finalized with links to the combined PR, latest CI/audit results, and concrete completed work. +- [ ] A maintainer/program administrator has confirmed the applicable Wave application process and eligibility. -See [ROADMAP.md](ROADMAP.md). +The combined CI and RustSec audit are green. Passing checks do not establish maintainer approval or Drips eligibility; verify the program-specific requirements before submitting. diff --git a/docs/IMPLEMENTED_CAPABILITIES.md b/docs/IMPLEMENTED_CAPABILITIES.md new file mode 100644 index 0000000..cf463af --- /dev/null +++ b/docs/IMPLEMENTED_CAPABILITIES.md @@ -0,0 +1,11 @@ +# Implemented library capabilities + +This branch adds reusable library APIs for the remaining developer-tooling workstreams: + +- **RPC transport:** `devkit_rpc::HttpTransport` sends JSON-RPC POST requests with a finite timeout, disabled redirects, request ID validation, and a response byte cap enforced before JSON parsing. Endpoint policy continues to allow HTTPS and loopback-only HTTP. +- **WASM inspection:** `devkit_contract::inspect_wasm` validates the WASM container without executing it, and reports imports, exports, custom sections, and decodes the `contractspecv0` section as a bounded stream of official `ScSpecEntry` XDR values. It returns each decoded definition as XDR-JSON with a variant kind and best-effort declared name; the stream is capped at 4,096 entries. +- **Analysis and SARIF:** `devkit_analysis::analyze` emits deterministic findings with stable rule IDs and evidence. `to_sarif` produces SARIF 2.1.0. Current rules are structural heuristics, not proof of a vulnerability. +- **Configuration/diagnostics:** `devkit_config::Config` parses strict TOML, validates timeouts, and supports environment overrides. Verbose diagnostics accept static event names and avoid printing endpoints or payloads. +- **Release readiness:** the release-validation workflow runs tests, formatting/lint checks, and package validation on version tags. + +CLI commands are available as `inspect-wasm`, `analyze-wasm`, and `rpc-health`. A manual GitHub Actions workflow can upload SARIF to code scanning when given a repository-relative WASM artifact path. The analysis rules remain structural heuristics, not a comprehensive vulnerability engine. No WASM is executed and no network request is made unless a caller explicitly constructs and uses the HTTP transport. diff --git a/docs/PROJECT_STATUS.md b/docs/PROJECT_STATUS.md index cffaf10..6ed6204 100644 --- a/docs/PROJECT_STATUS.md +++ b/docs/PROJECT_STATUS.md @@ -1,75 +1,52 @@ # Project Status -**Current phase:** Phase 1 — Foundation (in progress) +**Current phase:** Foundation and developer-tooling implementation in review **Repository:** https://github.com/StellarFoundry/stellar-devkit **Default branch:** `main` -## Completed - -- Workspace: `devkit-core`, `devkit-xdr`, `devkit-cli`. -- `devkit-core`: structured errors, network profiles (public/testnet/futurenet/ - custom) with canonical passphrases, output format. -- `devkit-xdr`: bounded decoding of base64 `ScVal`, `TransactionEnvelope`, and - `ContractEvent` to JSON; strkey classification. -- `stellar-foundry` CLI: `version`, `doctor`, `strkey`, `scval`, `envelope`, - `event`, `endpoint`; terminal/JSON output; exit codes 0/2/3. -- `devkit-rpc`: typed RPC protocol layer with an injectable transport, a - deterministic mock, endpoint validation, typed health/network/latest-ledger - results, and a retry policy. **No live network transport yet.** -- CI: fmt, clippy, tests on Linux/Windows/macOS. -- Docs: ecosystem research, architecture, getting started, CLI, roadmap, this - file. -- Governance: README, CONTRIBUTING, SECURITY, CODE_OF_CONDUCT, LICENSE, - CHANGELOG, PR/issue templates. - -## Not started (tracked as issues) - -- RPC layer (integration with `stellar-rpc-client`, mock transport, diagnostics). -- Configuration system and logging. -- Fixtures and a mock network for deterministic tests. -- Contract inspection (WASM metadata / spec). -- Transaction and event analysis (resources, fees, auth, affected state). -- Security analysis (malformed XDR, unsafe RPC configuration, SSRF policy). -- SARIF output and GitHub Action. -- Performance benchmarks and observability. -- Release engineering (binaries, crates.io, release automation). - -## Verified facts - -- `cargo build`, `cargo test`, `cargo clippy -D warnings`, `cargo fmt --check` - pass locally (run on 2026-09-22). Test count is reported in the build report. -- CLI smoke test: real contract strkey classified as `contract`. - -## Known limitations - -- JSON shapes come from `stellar-xdr`'s serde representation and are **unstable** - until a curated, versioned schema is defined. -- Decoding uses a permissive limit today; a stricter, configurable limit is - planned. -- No network functionality exists; `doctor` states this explicitly. -- No MSRV is pinned yet. -- `devkit-core` has no configuration loader yet. +**Status note:** The capabilities below are on an open pull-request branch and are not all merged into `main`. PR #133 includes PR #127's security-hardening head as a second parent for combined validation. Both PRs remain open; PR #127 is unmerged. + +## Implemented in the combined feature branch + +- `devkit-core`: structured errors, network profiles, output formats. +- `devkit-xdr`: bounded decoding of base64 `ScVal`, `TransactionEnvelope`, and `ContractEvent`; strkey classification; malformed-input regression fixtures. +- `stellar-foundry` CLI: `version`, `doctor`, `strkey`, `scval`, `envelope`, `event`, `endpoint`, `inspect-wasm`, `analyze-wasm`, and `rpc-health`; terminal/JSON output, SARIF output for analysis, and exit codes 0/2/3. +- `devkit-rpc`: typed RPC protocol layer, deterministic mock transport, endpoint validation, typed health/network/latest-ledger results, retry policy, and bounded HTTP JSON-RPC transport. +- `devkit-contract`: bounded, non-executing WASM validation, import/export/custom-section metadata, and bounded `contractspecv0` XDR stream decoding into entry kind/name/definition records. +- `devkit-analysis`: deterministic structural findings with stable rule IDs and SARIF 2.1.0 serialization. +- `devkit-config`: strict TOML parsing, timeout validation, environment overrides, and static diagnostics. +- CI: pinned actions, locked dependency resolution, formatting, Clippy, Linux/Windows/macOS tests, and Rust 1.91.0 MSRV tests. +- Security: RustSec dependency audit workflow, bounded malformed-XDR regression corpus, and a manual SARIF upload workflow for GitHub code scanning. +- Release preparation: release-validation workflow, release checklist, implementation-capabilities documentation, updated changelog, and Drips readiness checklist. + +## Verified CI status + +The previously integrated branch passed formatting, Clippy, Linux, Windows, macOS, and Rust 1.91.0 MSRV tests, plus RustSec. New contract-spec decoding, CLI config wiring, and SARIF workflow changes are awaiting a fresh combined CI and audit run before merge. + +## Remaining limitations + +- `contractspecv0` entries are decoded into XDR-JSON definitions with variant names and best-effort declared names; this is not yet a fully normalized ABI schema for every entry type. +- Security-analysis rules are structural heuristics, not a comprehensive vulnerability engine. +- SARIF upload is available through a manually triggered workflow; it requires a repository-relative WASM artifact and GitHub code-scanning permissions. +- RPC configuration is wired into `rpc-health`: command-line endpoint overrides environment, which overrides the TOML file. Other commands do not consume RPC configuration because they do not need it. +- Do not describe unmerged work as already released or available from `main`. ## Security status -- No network access, no secrets, bounded decoding, structured errors. -- Fuzzing is planned, not yet implemented. +- XDR and WASM inputs are bounded in the implemented paths. +- The HTTP transport uses a finite timeout, disables redirects, bounds response bytes before parsing, and validates JSON-RPC responses. +- Endpoint policy rejects embedded credentials and permits plaintext HTTP only for loopback. +- Security claims should be limited to behavior demonstrated by code and tests; no system is claimed to be vulnerability-free. ## Contributor backlog -- **119 open issues** after a quality audit (down from 121). -- Audit result: **89 Drips-ready**, 24 blocked by dependency, 3 deferred, 3 needing - revision. See [DRIPS_ISSUE_QUALITY_AUDIT.md](DRIPS_ISSUE_QUALITY_AUDIT.md). -- Status labels `status/ready`, `status/blocked`, `status/deferred`, and - `status/needs-revision` are applied. -- Manifest: [ISSUE_BACKLOG.md](ISSUE_BACKLOG.md); prior audit: - [DRIPS_ISSUE_AUDIT.md](DRIPS_ISSUE_AUDIT.md). -- The backlog is a roadmap: most issues are **not implemented**. +The issue backlog is a roadmap, not a list of completed features. Audit counts, labels, and issue descriptions must not be presented as proof that the corresponding work is implemented. See [ISSUE_BACKLOG.md](ISSUE_BACKLOG.md) and [DRIPS_ISSUE_QUALITY_AUDIT.md](DRIPS_ISSUE_QUALITY_AUDIT.md). ## Drips documents -- [DRIPS_READINESS.md](DRIPS_READINESS.md) -- [DRIPS_APPLICATION_DRAFT.md](DRIPS_APPLICATION_DRAFT.md) -- [DRIPS_ISSUE_AUDIT.md](DRIPS_ISSUE_AUDIT.md) +- [Drips readiness checklist](DRIPS_READINESS.md) +- [Drips application draft](DRIPS_APPLICATION_DRAFT.md) +- [Issue audit](DRIPS_ISSUE_AUDIT.md) +- [Contributing through Drips Wave](WAVE.md) diff --git a/docs/RELEASE_CHECKLIST.md b/docs/RELEASE_CHECKLIST.md new file mode 100644 index 0000000..c0e54f0 --- /dev/null +++ b/docs/RELEASE_CHECKLIST.md @@ -0,0 +1,13 @@ +# Release checklist + +Before publishing a version: + +1. Confirm the version and changelog entries agree. +2. Run `cargo fmt --all -- --check`. +3. Run `cargo clippy --workspace --all-targets --all-features -- -D warnings`. +4. Run `cargo test --workspace --all-features` on Linux, Windows, and macOS. +5. Run `cargo package --workspace --no-verify` and review packaging warnings. +6. Confirm the lockfile is regenerated and committed after dependency changes. +7. Review the generated SARIF against the SARIF 2.1.0 schema. +8. Verify artifacts and checksums before publishing; do not include credentials or local configuration. +9. State known limitations in the release notes. Structural analysis findings are heuristics and do not prove a vulnerability.