From 50f40d9cd92a690d6dd224c0e0564878b470c41b Mon Sep 17 00:00:00 2001 From: anoncon Date: Sat, 30 May 2026 09:00:49 +0000 Subject: [PATCH 1/2] initial commit From 50dee736bfc48ae338c54b1c788805f227c6faa9 Mon Sep 17 00:00:00 2001 From: anoncon Date: Sat, 30 May 2026 09:38:07 +0000 Subject: [PATCH 2/2] feat: implement upgrade severity, source viewer, trend charts, and custom settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #762 — Rust engine: - Add severity field to UpgradeFinding (critical/medium for init issues) - Implement re-init guard detection (fn_has_reinit_guard, expr_has_storage_guard) - Fix pre-existing compilation errors in arithmetic_overflow.rs - Fix missing fields in DEPRECATED_SDK_USAGE FindingCode Issue #777 — Source viewer: - Add CodeSnippet with Rust/Soroban syntax tokenizer - Add SourceViewer modal for full-source inspection - Add getSource/getFileName props to FindingsList with View Source button - Add shiki dependency to package.json Issue #778 — Trend chart: - Create IndexedDB scan history utility (saveScanRecord, getScanHistory) - Create TrendChart SVG component with per-severity series - Add trend section to SummaryChart with clear-history control - Wire trend persistence into dashboard after each analysis Issue #776 — Settings: - Create settings.ts with getSettings/getSettingsHeaders - Add custom rules path and API endpoint UI in settings page - Update /api/analyze route to read x-sanctifier-* headers - Add source field to WorkspaceMember type Additional: - Inject settings headers into scan/page.tsx and playground/page.tsx API calls - Wire settings headers, scan history, source viewer into dashboard/page.tsx --- Cargo.lock | 853 +++++++----------- frontend/app/api/analyze/route.ts | 32 +- frontend/app/components/CodeSnippet.tsx | 149 ++- frontend/app/components/FindingsList.tsx | 97 +- frontend/app/components/SourceViewer.tsx | 90 ++ frontend/app/components/SummaryChart.tsx | 44 +- frontend/app/components/TrendChart.tsx | 130 +++ frontend/app/dashboard/page.tsx | 110 ++- frontend/app/lib/scan-history.ts | 124 +++ frontend/app/lib/settings.ts | 65 ++ frontend/app/lib/transform.ts | 3 +- frontend/app/playground/page.tsx | 2 + frontend/app/scan/page.tsx | 1 + frontend/app/settings/page.tsx | 48 + frontend/app/types.ts | 2 + frontend/package.json | 1 + tooling/sanctifier-core/src/finding_codes.rs | 4 + tooling/sanctifier-core/src/lib.rs | 110 ++- .../src/rules/arithmetic_overflow.rs | 22 +- .../sanctifier-core/src/upgrade_analysis.rs | 272 ++++++ 20 files changed, 1532 insertions(+), 627 deletions(-) create mode 100644 frontend/app/components/SourceViewer.tsx create mode 100644 frontend/app/components/TrendChart.tsx create mode 100644 frontend/app/lib/scan-history.ts create mode 100644 frontend/app/lib/settings.ts diff --git a/Cargo.lock b/Cargo.lock index 6efc87d8..89fd1613 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,20 +17,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "getrandom 0.3.4", - "once_cell", - "serde", - "version_check", - "zerocopy", -] - [[package]] name = "aho-corasick" version = "1.1.4" @@ -140,9 +126,9 @@ dependencies = [ [[package]] name = "assert_cmd" -version = "2.2.0" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a686bbee5efb88a82df0621b236e74d925f470e5445d3220a5648b892ec99c9" +checksum = "2aa3a22042e45de04255c7bf3626e239f450200fd0493c1e382263544b20aea6" dependencies = [ "anstyle", "bstr", @@ -172,9 +158,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "backtrace" @@ -242,7 +228,7 @@ version = "0.66.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cexpr", "clang-sys", "lazy_static", @@ -252,34 +238,19 @@ dependencies = [ "quote", "regex", "rustc-hash 1.1.0", - "shlex", + "shlex 1.3.0", "syn 2.0.117", ] -[[package]] -name = "bit-set" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec 0.6.3", -] - [[package]] name = "bit-set" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ - "bit-vec 0.8.0", + "bit-vec", ] -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - [[package]] name = "bit-vec" version = "0.8.0" @@ -294,9 +265,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "block-buffer" @@ -320,7 +291,7 @@ dependencies = [ "bolero-kani", "bolero-libfuzzer", "cfg-if", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -344,7 +315,7 @@ dependencies = [ "bolero-generator", "lazy_static", "pretty-hex", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -398,6 +369,15 @@ dependencies = [ "cc", ] +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + [[package]] name = "bstr" version = "1.12.1" @@ -411,15 +391,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" - -[[package]] -name = "bytecount" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "byteorder" @@ -485,12 +459,12 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.58" +version = "1.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" dependencies = [ "find-msvc-tools", - "shlex", + "shlex 2.0.1", ] [[package]] @@ -499,7 +473,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ - "nom 7.1.3", + "nom", ] [[package]] @@ -568,9 +542,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -599,9 +573,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck", "proc-macro2", @@ -637,33 +611,9 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.48.0", -] - -[[package]] -name = "console" -version = "0.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" -dependencies = [ - "encode_unicode", - "libc", "windows-sys 0.61.2", ] -[[package]] -name = "console" -version = "0.15.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" -dependencies = [ - "encode_unicode", - "libc", - "once_cell", - "unicode-width", - "windows-sys 0.59.0", -] - [[package]] name = "console" version = "0.16.3" @@ -717,15 +667,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - [[package]] name = "criterion" version = "0.5.1" @@ -995,19 +936,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "dialoguer" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" -dependencies = [ - "console 0.15.11", - "shell-words", - "tempfile", - "thiserror 1.0.69", - "zeroize", -] - [[package]] name = "difflib" version = "0.4.0" @@ -1028,9 +956,9 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", @@ -1089,9 +1017,9 @@ dependencies = [ [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "elliptic-curve" @@ -1139,7 +1067,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1150,26 +1078,15 @@ checksum = "2bfcf67fea2815c2fc3b90873fae90957be12ff417335dfadc7f52927feb03b2" [[package]] name = "ethnum" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca81e6b4777c89fd810c25a4be2b1bd93ea034fbe58e6a75216a34c6b82c539b" - -[[package]] -name = "fancy-regex" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2" -dependencies = [ - "bit-set 0.5.3", - "regex-automata", - "regex-syntax", -] +checksum = "40404c3f5f511ec4da6fe866ddf6a717c309fdbb69fbbad7b0f3edab8f2e835f" [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "ff" @@ -1189,13 +1106,12 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "filetime" -version = "0.2.27" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" dependencies = [ "cfg-if", "libc", - "libredox", ] [[package]] @@ -1211,16 +1127,6 @@ dependencies = [ "soroban-sdk", ] -[[package]] -name = "flate2" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - [[package]] name = "float-cmp" version = "0.10.0" @@ -1251,16 +1157,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fraction" -version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f158e3ff0a1b334408dc9fb811cd99b446986f4d8b741bb08f9df1604085ae7" -dependencies = [ - "lazy_static", - "num", -] - [[package]] name = "fsevent-sys" version = "4.1.0" @@ -1413,7 +1309,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.13.0", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", @@ -1422,17 +1318,17 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http 1.4.0", - "indexmap 2.13.0", + "http 1.4.1", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", @@ -1467,9 +1363,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "headers" @@ -1544,9 +1440,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" dependencies = [ "bytes", "itoa", @@ -1570,7 +1466,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.4.0", + "http 1.4.1", ] [[package]] @@ -1581,7 +1477,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.4.0", + "http 1.4.1", "http-body 1.0.1", "pin-project-lite", ] @@ -1624,22 +1520,21 @@ dependencies = [ [[package]] name = "hyper" -version = "1.8.1" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", - "h2 0.4.13", - "http 1.4.0", + "h2 0.4.14", + "http 1.4.1", "http-body 1.0.1", "httparse", "httpdate", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -1647,15 +1542,14 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "http 1.4.0", - "hyper 1.8.1", + "http 1.4.1", + "hyper 1.10.1", "hyper-util", "rustls", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", @@ -1672,14 +1566,14 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.4.0", + "http 1.4.1", "http-body 1.0.1", - "hyper 1.8.1", + "hyper 1.10.1", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.3", + "socket2 0.6.4", "tokio", "tower-service", "tracing", @@ -1711,12 +1605,13 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -1724,9 +1619,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -1737,9 +1632,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -1751,15 +1646,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -1771,15 +1666,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -1815,9 +1710,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", @@ -1836,12 +1731,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "serde", "serde_core", ] @@ -1878,7 +1773,7 @@ version = "1.47.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b4a6248eb93a4401ed2f37dfe8ea592d3cf05b7cf4f8efa867b6895af7e094e" dependencies = [ - + "console", "once_cell", "pest", "pest_derive", @@ -1893,16 +1788,6 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" -[[package]] -name = "iri-string" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "is-terminal" version = "0.4.17" @@ -1911,7 +1796,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1920,15 +1805,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" -[[package]] -name = "iso8601" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1082f0c48f143442a1ac6122f67e360ceee130b967af4d50996e5154a45df46" -dependencies = [ - "nom 8.0.0", -] - [[package]] name = "itertools" version = "0.10.5" @@ -1955,9 +1831,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.92" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4c90f45aa2e6eacbe8645f77fdea542ac97a494bcd117a67df9ff4d611f995" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" dependencies = [ "cfg-if", "futures-util", @@ -1965,36 +1841,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "jsonschema" -version = "0.18.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0f4bea31643be4c6a678e9aa4ae44f0db9e5609d5ca9dc9083d06eb3e9a27a" -dependencies = [ - "ahash", - "anyhow", - "base64 0.22.1", - "bytecount", - "clap", - "fancy-regex", - "fraction", - "getrandom 0.2.17", - "iso8601", - "itoa", - "memchr", - "num-cmp", - "once_cell", - "parking_lot", - "percent-encoding", - "regex", - "reqwest", - "serde", - "serde_json", - "time", - "url", - "uuid", -] - [[package]] name = "k256" version = "0.13.4" @@ -2035,11 +1881,11 @@ dependencies = [ [[package]] name = "kqueue-sys" -version = "1.0.4" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.11.1", "libc", ] @@ -2063,9 +1909,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.183" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libloading" @@ -2083,18 +1929,6 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" -[[package]] -name = "libredox" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" -dependencies = [ - "bitflags 2.11.0", - "libc", - "plain", - "redox_syscall 0.7.4", -] - [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -2103,9 +1937,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "lock_api" @@ -2118,9 +1952,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" [[package]] name = "lru-slab" @@ -2145,9 +1979,9 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "mime" @@ -2188,7 +2022,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", - "simd-adler32", ] [[package]] @@ -2205,9 +2038,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", "wasi", @@ -2224,14 +2057,14 @@ dependencies = [ "bytes", "colored 3.1.1", "futures-core", - "http 1.4.0", + "http 1.4.1", "http-body 1.0.1", "http-body-util", - "hyper 1.8.1", + "hyper 1.10.1", "hyper-util", "log", "pin-project-lite", - "rand 0.9.2", + "rand 0.9.4", "regex", "serde_json", "serde_urlencoded", @@ -2285,15 +2118,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "nom" -version = "8.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" -dependencies = [ - "memchr", -] - [[package]] name = "normalize-line-endings" version = "0.3.0" @@ -2306,7 +2130,7 @@ version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "crossbeam-channel", "filetime", "fsevent-sys", @@ -2345,21 +2169,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "num" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", + "windows-sys 0.61.2", ] [[package]] @@ -2372,26 +2182,11 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-cmp" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa" - -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-derive" @@ -2413,28 +2208,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -2511,7 +2284,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", "windows-link", ] @@ -2603,12 +2376,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - [[package]] name = "pkcs8" version = "0.10.2" @@ -2619,12 +2386,6 @@ dependencies = [ "spki", ] -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" - [[package]] name = "plotters" version = "0.3.7" @@ -2655,9 +2416,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -2757,11 +2518,11 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" dependencies = [ - "bit-set 0.8.0", - "bit-vec 0.8.0", - "bitflags 2.11.0", + "bit-set", + "bit-vec", + "bitflags 2.11.1", "num-traits", - "rand 0.9.2", + "rand 0.9.4", "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax", @@ -2789,7 +2550,7 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.2", "rustls", - "socket2 0.6.3", + "socket2 0.6.4", "thiserror 2.0.18", "tokio", "tracing", @@ -2805,7 +2566,7 @@ dependencies = [ "bytes", "getrandom 0.3.4", "lru-slab", - "rand 0.9.2", + "rand 0.9.4", "ring", "rustc-hash 2.1.2", "rustls", @@ -2826,9 +2587,9 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.3", + "socket2 0.6.4", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -2854,9 +2615,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -2865,9 +2626,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -2922,9 +2683,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -2946,16 +2707,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", -] - -[[package]] -name = "redox_syscall" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" -dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -3026,10 +2778,10 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "http 1.4.0", + "http 1.4.1", "http-body 1.0.1", "http-body-util", - "hyper 1.8.1", + "hyper 1.10.1", "hyper-rustls", "hyper-util", "js-sys", @@ -3119,18 +2871,18 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.37" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ "once_cell", "ring", @@ -3142,9 +2894,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ "web-time", "zeroize", @@ -3152,9 +2904,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.10" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "ring", "rustls-pki-types", @@ -3204,10 +2956,6 @@ dependencies = [ "clap_complete", "colored 2.2.0", "csv", - "dialoguer", - "flate2", - "hex", - "jsonschema", "md5", "mockito", "notify", @@ -3220,7 +2968,6 @@ dependencies = [ "serde", "serde_json", "sha2", - "tar", "tempfile", "tokio", "toml", @@ -3228,7 +2975,6 @@ dependencies = [ "tracing", "tracing-subscriber", "vergen", - "warp", ] @@ -3338,9 +3084,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" dependencies = [ "serde", "serde_core", @@ -3389,9 +3135,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -3423,15 +3169,16 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.18.0" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" +checksum = "e72c1c2cb7b223fafb600a619537a871c2818583d619401b785e7c0b746ccde2" dependencies = [ "base64 0.22.1", + "bs58", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.13.0", + "indexmap 2.14.0", "schemars 0.9.0", "schemars 1.2.1", "serde_core", @@ -3442,9 +3189,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.18.0" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" +checksum = "b90c488738ecb4fb0262f41f43bc40efc5868d9fb744319ddf5f5317f417bfac" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -3458,7 +3205,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.14.0", "itoa", "ryu", "serde", @@ -3489,9 +3236,9 @@ dependencies = [ [[package]] name = "sha3" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" dependencies = [ "digest", "keccak", @@ -3507,16 +3254,16 @@ dependencies = [ ] [[package]] -name = "shell-words" -version = "1.1.1" +name = "shlex" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "signal-hook-registry" @@ -3538,12 +3285,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "simd-adler32" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" - [[package]] name = "similar" version = "2.7.0" @@ -3574,9 +3315,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", "windows-sys 0.61.2", @@ -3643,7 +3384,7 @@ dependencies = [ "num-integer", "num-traits", "p256", - "rand 0.8.5", + "rand 0.8.6", "rand_chacha 0.3.1", "sec1", "sha2", @@ -3696,7 +3437,7 @@ dependencies = [ "ctor", "derive_arbitrary", "ed25519-dalek", - "rand 0.8.5", + "rand 0.8.6", "rustc_version", "serde", "serde_json", @@ -3892,7 +3633,6 @@ dependencies = [ ] [[package]] - name = "tempfile" version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3902,7 +3642,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4003,9 +3743,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -4045,26 +3785,26 @@ dependencies = [ [[package]] name = "tokio" -version = "1.50.0" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", - "mio 1.2.0", + "mio 1.2.1", "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.3", + "socket2 0.6.4", "tokio-macros", "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.6.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -4133,7 +3873,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.14.0", "toml_datetime", "winnow 0.5.40", ] @@ -4144,7 +3884,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.14.0", "serde", "serde_spanned", "toml_datetime", @@ -4175,20 +3915,20 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "bytes", "futures-util", - "http 1.4.0", + "http 1.4.1", "http-body 1.0.1", - "iri-string", "pin-project-lite", "tower", "tower-layer", "tower-service", + "url", ] [[package]] @@ -4293,10 +4033,10 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 1.4.0", + "http 1.4.1", "httparse", "log", - "rand 0.8.5", + "rand 0.8.6", "sha1", "thiserror 1.0.69", "url", @@ -4305,9 +4045,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "ucd-trie" @@ -4333,12 +4073,6 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" -[[package]] -name = "unicode-width" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" - [[package]] name = "unicode-xid" version = "0.2.6" @@ -4387,16 +4121,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" -[[package]] -name = "uuid" -version = "1.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "uups-proxy" version = "0.2.0" @@ -4512,11 +4236,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -4525,14 +4249,14 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] name = "wasm-bindgen" -version = "0.2.115" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6523d69017b7633e396a89c5efab138161ed5aafcbc8d3e5c5a42ae38f50495a" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" dependencies = [ "cfg-if", "once_cell", @@ -4543,9 +4267,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.65" +version = "0.4.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d1faf851e778dfa54db7cd438b70758eba9755cb47403f3496edd7c8fc212f0" +checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" dependencies = [ "js-sys", "wasm-bindgen", @@ -4553,9 +4277,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.115" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3a6c758eb2f701ed3d052ff5737f5bfe6614326ea7f3bbac7156192dc32e67" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4563,9 +4287,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.115" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "921de2737904886b52bcbb237301552d05969a6f9c40d261eb0533c8b055fedf" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" dependencies = [ "bumpalo", "proc-macro2", @@ -4576,18 +4300,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.115" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a93e946af942b58934c604527337bad9ae33ba1d5c6900bbb41c2c07c2364a93" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-bindgen-test" -version = "0.3.65" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1138411301a026d6662dc44e7076a74dbaa76a369312275eea5dee4d7dc68c7c" +checksum = "74fde991ccdc895cb7fbaa14b137d62af74d9011be67b71c694bfc40edd3119c" dependencies = [ "async-trait", "cast", @@ -4607,9 +4331,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.65" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186ddfe8383ba7ae7927bae3bb7343fd1f03ba2dbaf1474410f0d831131c269b" +checksum = "e925354648d2a4d1bf205412e36d520a800280622eef4719678d268e5d40e978" dependencies = [ "proc-macro2", "quote", @@ -4618,9 +4342,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-shared" -version = "0.2.115" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f032e076ceb8d36d5921c6cef5bf447f2ca2bbd5439ce1683d68d1c99cc2be16" +checksum = "684365b586a9a6256c1cc3544eee8680de48d6041142f581776ec7b139622ae9" [[package]] name = "wasm-encoder" @@ -4639,7 +4363,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap 2.13.0", + "indexmap 2.14.0", "wasm-encoder", "wasmparser 0.244.0", ] @@ -4668,7 +4392,7 @@ version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a58e28b80dd8340cb07b8242ae654756161f6fc8d0038123d679b7b99964fa50" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.14.0", "semver", ] @@ -4678,9 +4402,9 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "hashbrown 0.15.5", - "indexmap 2.13.0", + "indexmap 2.14.0", "semver", ] @@ -4695,9 +4419,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.92" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84cde8507f4d7cfcb1185b8cb5890c494ffea65edbe1ba82cfd63661c805ed94" +checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" dependencies = [ "js-sys", "wasm-bindgen", @@ -4715,9 +4439,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" dependencies = [ "rustls-pki-types", ] @@ -4744,32 +4468,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" -dependencies = [ - "windows-core 0.52.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", + "windows-sys 0.61.2", ] [[package]] @@ -4883,6 +4582,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -4916,13 +4624,30 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -4935,6 +4660,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -4947,6 +4678,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -4959,12 +4696,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -4977,6 +4726,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -4989,6 +4744,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -5001,6 +4762,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -5013,6 +4780,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "winnow" version = "0.5.40" @@ -5040,6 +4813,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" @@ -5059,7 +4838,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck", - "indexmap 2.13.0", + "indexmap 2.14.0", "prettyplease", "syn 2.0.117", "wasm-metadata", @@ -5089,8 +4868,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.0", - "indexmap 2.13.0", + "bitflags 2.11.1", + "indexmap 2.14.0", "log", "serde", "serde_derive", @@ -5109,7 +4888,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap 2.13.0", + "indexmap 2.14.0", "log", "semver", "serde", @@ -5121,25 +4900,15 @@ dependencies = [ [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" - -[[package]] -name = "xattr" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" -dependencies = [ - "libc", - "rustix", -] +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -5148,9 +4917,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -5179,18 +4948,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" dependencies = [ "proc-macro2", "quote", @@ -5199,18 +4968,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -5226,9 +4995,9 @@ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -5237,9 +5006,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -5248,9 +5017,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", diff --git a/frontend/app/api/analyze/route.ts b/frontend/app/api/analyze/route.ts index a3c83724..37647a2d 100644 --- a/frontend/app/api/analyze/route.ts +++ b/frontend/app/api/analyze/route.ts @@ -14,6 +14,16 @@ const SUPPORTED_SOURCE_EXTENSIONS = new Set([".rs"]); const MAX_FILE_SIZE_BYTES = 250 * 1024; const EXECUTION_TIMEOUT_MS = 30000; +function getSettingsFromHeaders(request: NextRequest): { + binPath?: string; + customRulesPath?: string; +} { + const binPath = request.headers.get("x-sanctifier-bin-path") || undefined; + const customRulesPath = + request.headers.get("x-sanctifier-custom-rules") || undefined; + return { binPath, customRulesPath }; +} + const rateLimitMap = new Map(); function getClientIP(request: NextRequest): string { @@ -61,16 +71,21 @@ type ProcessResult = { exitCode: number | null; }; -function runAnalyzeCommand(contractPath: string, timeoutMs: number): Promise { +function runAnalyzeCommand( + contractPath: string, + timeoutMs: number, + settings?: { binPath?: string; customRulesPath?: string } +): Promise { return new Promise((resolve, reject) => { - const cliProcess = spawn( - SANCTIFIER_BIN, - ["analyze", "--format", "json", contractPath], - { + const bin = settings?.binPath || SANCTIFIER_BIN; + const args = ["analyze", "--format", "json", contractPath]; + if (settings?.customRulesPath) { + args.push("--custom-rules", settings.customRulesPath); + } + const cliProcess = spawn(bin, args, { cwd: REPO_ROOT, env: { ...process.env, FORCE_COLOR: "0" }, - } - ); + }); let stdout = ""; let stderr = ""; let timeoutId: NodeJS.Timeout | null = null; @@ -260,6 +275,7 @@ export async function POST(request: NextRequest) { ); } + const settingsFromHeaders = getSettingsFromHeaders(request); const tempDir = await mkdtemp(path.join(os.tmpdir(), "sanctifier-contract-")); try { const contentType = request.headers.get("content-type") ?? ""; @@ -308,7 +324,7 @@ export async function POST(request: NextRequest) { const contractPath = path.join(tempDir, sourcePayload.fileName); await writeFile(contractPath, sourcePayload.source, "utf8"); - const { stdout, stderr, exitCode } = await runAnalyzeCommand(contractPath, EXECUTION_TIMEOUT_MS); + const { stdout, stderr, exitCode } = await runAnalyzeCommand(contractPath, EXECUTION_TIMEOUT_MS, settingsFromHeaders); const report = parseJsonResponse(stdout); if (report) { diff --git a/frontend/app/components/CodeSnippet.tsx b/frontend/app/components/CodeSnippet.tsx index 0dfd3aeb..0c9e3f32 100644 --- a/frontend/app/components/CodeSnippet.tsx +++ b/frontend/app/components/CodeSnippet.tsx @@ -1,33 +1,140 @@ "use client"; +import { useMemo } from "react"; + interface CodeSnippetProps { code: string; highlightLine?: number; + highlightEndLine?: number; language?: string; + maxHeight?: string; +} + +const RUST_KEYWORDS = new Set([ + "as", "break", "const", "continue", "crate", "else", "enum", + "extern", "false", "fn", "for", "if", "impl", "in", "let", + "loop", "match", "mod", "move", "mut", "pub", "ref", "return", + "self", "Self", "static", "struct", "super", "trait", "true", + "type", "unsafe", "use", "where", "while", "async", "await", + "dyn", "abstract", "become", "box", "do", "final", "macro", + "override", "priv", "typeof", "unsized", "virtual", "yield", + "try", +]); + +const SOROBAN_TYPES = new Set([ + "Address", "Bytes", "BytesN", "String", "Symbol", "Vec", "Map", + "Option", "Result", "Env", "Val", "RawVal", "Bool", "Void", + "I128", "U128", "I256", "U256", "i128", "u128", "u32", "i32", + "u64", "i64", "u8", "i8", +]); + +function tokenizeLine(line: string): { text: string; className: string }[] { + const tokens: { text: string; className: string }[] = []; + const regex = /(\/\/.*)|("(?:[^"\\]|\\.)*")|('(?:[^'\\]|\\.)*')|(\b\d[\d_]*(?:\.[\d_]+)?(?:[eE][+-]?\d+)?\b)|(\b[a-zA-Z_]\w*\b)|([{}()\[\];,.:<>!=+\-*/%&|^~@#]|\s+)/g; + let match: RegExpExecArray | null; + + while ((match = regex.exec(line)) !== null) { + if (match[1]) { + tokens.push({ text: match[1], className: "text-emerald-500 italic" }); + } else if (match[2]) { + tokens.push({ text: match[2], className: "text-amber-400" }); + } else if (match[3]) { + tokens.push({ text: match[3], className: "text-amber-400" }); + } else if (match[4]) { + tokens.push({ text: match[4], className: "text-cyan-400" }); + } else if (match[5]) { + const word = match[5]; + if (RUST_KEYWORDS.has(word)) { + tokens.push({ text: word, className: "text-purple-400 font-semibold" }); + } else if (SOROBAN_TYPES.has(word)) { + tokens.push({ text: word, className: "text-blue-400" }); + } else if (word === word.toUpperCase() && word.length > 1 && word.includes("_")) { + tokens.push({ text: word, className: "text-orange-300" }); + } else if (word.startsWith("DataKey") || word.endsWith("Key")) { + tokens.push({ text: word, className: "text-orange-300" }); + } else { + tokens.push({ text: word, className: "" }); + } + } else if (match[6]) { + const punct = match[6]; + if (punct.trim() === "") { + tokens.push({ text: punct, className: "" }); + } else { + tokens.push({ text: punct, className: "text-zinc-400" }); + } + } + } + + if (tokens.length === 0) { + tokens.push({ text: line, className: "" }); + } + + return tokens; } -export function CodeSnippet({ code, highlightLine }: CodeSnippetProps) { - const lines = code.split("\n"); +export function CodeSnippet({ + code, + highlightLine, + highlightEndLine, + language = "rust", + maxHeight, +}: CodeSnippetProps) { + const lines = useMemo(() => code.split("\n"), [code]); + const tokenizedLines = useMemo( + () => lines.map((line) => tokenizeLine(line)), + [lines] + ); + + const isInRange = (lineNum: number) => { + if (highlightLine === undefined) return false; + if (highlightEndLine !== undefined) { + return lineNum >= highlightLine && lineNum <= highlightEndLine; + } + return lineNum === highlightLine; + }; return ( -
-      
-        {lines.map((line, i) => (
-          
- - {i + 1} - - {line || " "} -
- ))} -
-
+
+ + + {tokenizedLines.map((tokens, i) => { + const lineNum = i + 1; + const highlighted = isInRange(lineNum); + return ( + + + + + ); + })} + +
+ {lineNum} + + {tokens.length === 1 && tokens[0].text === "" ? ( +   + ) : ( + tokens.map((t, j) => + t.className ? ( + + {t.text} + + ) : ( + {t.text} + ) + ) + )} +
+
); } diff --git a/frontend/app/components/FindingsList.tsx b/frontend/app/components/FindingsList.tsx index 790d34bb..95e7a889 100644 --- a/frontend/app/components/FindingsList.tsx +++ b/frontend/app/components/FindingsList.tsx @@ -4,7 +4,8 @@ import { useState, useEffect, useCallback, useMemo, useRef } from "react"; import { FixedSizeList, type ListChildComponentProps } from "react-window"; import type { Finding, Severity } from "../types"; import { CodeSnippet } from "./CodeSnippet"; -import { Sparkles } from "lucide-react"; +import { SourceViewer } from "./SourceViewer"; +import { Sparkles, FileCode } from "lucide-react"; import { AiFixPanel } from "./AiFixPanel"; import { filterFindings } from "../lib/finding-filters"; @@ -12,6 +13,8 @@ interface FindingsListProps { findings: Finding[]; severityFilter: Severity | "all"; codeFilter?: string; + getSource?: (finding: Finding) => string | undefined; + getFileName?: (finding: Finding) => string | undefined; } /** Height reserved for each finding row in the virtual list (px). */ @@ -38,9 +41,10 @@ const severityLabels: Record = { interface FindingCardProps { finding: Finding; onSelectAiFix: (finding: Finding) => void; + onViewSource?: (finding: Finding) => void; } -function FindingCard({ finding, onSelectAiFix }: FindingCardProps) { +function FindingCard({ finding, onSelectAiFix, onViewSource }: FindingCardProps) { return (
@@ -65,6 +69,17 @@ function FindingCard({ finding, onSelectAiFix }: FindingCardProps) { )}
+ {onViewSource && ( + + )} (null); + const [sourceViewerFinding, setSourceViewerFinding] = useState(null); const listRef = useRef(null); const filtered = useMemo(() => { @@ -98,16 +114,25 @@ export function FindingsList({ findings, severityFilter, codeFilter = "" }: Find listRef.current?.scrollToItem(0); }, [severityFilter, codeFilter]); + const handleViewSource = useCallback((finding: Finding) => { + setSourceViewerFinding(finding); + }, []); + + const handleCloseSource = useCallback(() => { + setSourceViewerFinding(null); + }, []); + const Row = useCallback( ({ index, style }: ListChildComponentProps) => (
setSelectedFinding(f)} + onViewSource={getSource ? handleViewSource : undefined} />
), - [filtered], + [filtered, getSource, handleViewSource], ); if (filtered.length === 0) { @@ -118,37 +143,47 @@ export function FindingsList({ findings, severityFilter, codeFilter = "" }: Find ); } - // For small lists render items directly — no virtualisation overhead. - if (filtered.length < VIRTUALISE_THRESHOLD) { - return ( -
- setSelectedFinding(null)} /> - {filtered.map((f) => ( - setSelectedFinding(finding)} - /> - ))} -
- ); - } - - // For large lists (1000+) use a fixed-size virtual window. - const listHeight = Math.min(filtered.length * ITEM_HEIGHT, MAX_LIST_HEIGHT); + const sourceViewerSource = sourceViewerFinding && getSource ? getSource(sourceViewerFinding) : undefined; return (
+ {sourceViewerSource && sourceViewerFinding && ( + + )} setSelectedFinding(null)} /> - - {Row} - + + {/* For small lists render items directly — no virtualisation overhead. */} + {filtered.length < VIRTUALISE_THRESHOLD ? ( +
+ {filtered.map((f) => ( + setSelectedFinding(finding)} + onViewSource={getSource ? handleViewSource : undefined} + /> + ))} +
+ ) : ( + <> + {/* For large lists (1000+) use a fixed-size virtual window. */} + + {Row} + + + )}
); } diff --git a/frontend/app/components/SourceViewer.tsx b/frontend/app/components/SourceViewer.tsx new file mode 100644 index 00000000..56be5af1 --- /dev/null +++ b/frontend/app/components/SourceViewer.tsx @@ -0,0 +1,90 @@ +"use client"; + +import { useEffect, useCallback } from "react"; +import { CodeSnippet } from "./CodeSnippet"; +import { X } from "lucide-react"; + +interface SourceViewerProps { + source: string; + fileName?: string; + highlightLine?: number; + highlightEndLine?: number; + onClose: () => void; +} + +export function SourceViewer({ + source, + fileName, + highlightLine, + highlightEndLine, + onClose, +}: SourceViewerProps) { + const handleKeyDown = useCallback( + (e: KeyboardEvent) => { + if (e.key === "Escape") { + onClose(); + } + }, + [onClose] + ); + + useEffect(() => { + document.addEventListener("keydown", handleKeyDown); + document.body.style.overflow = "hidden"; + return () => { + document.removeEventListener("keydown", handleKeyDown); + document.body.style.overflow = ""; + }; + }, [handleKeyDown]); + + return ( +
{ + if (e.target === e.currentTarget) onClose(); + }} + role="dialog" + aria-modal="true" + aria-label={fileName ? `Source: ${fileName}` : "Source viewer"} + > +
+ {/* Header */} +
+
+ + Source + + {fileName && ( + + {fileName} + + )} + {highlightLine && ( + + Lines {highlightLine} + {highlightEndLine ? `-${highlightEndLine}` : ""} + + )} +
+ +
+ + {/* Code */} +
+ +
+
+
+ ); +} diff --git a/frontend/app/components/SummaryChart.tsx b/frontend/app/components/SummaryChart.tsx index 13800e8c..908309c2 100644 --- a/frontend/app/components/SummaryChart.tsx +++ b/frontend/app/components/SummaryChart.tsx @@ -2,12 +2,23 @@ import { useMemo } from "react"; import type { Finding, Severity } from "../types"; +import type { ScanRecord } from "../lib/scan-history"; +import { TrendChart } from "./TrendChart"; interface SummaryChartProps { findings: Finding[]; + trendRecords?: ScanRecord[]; + onClearHistory?: () => void; } -export function SummaryChart({ findings }: SummaryChartProps) { +const trendColors: Record = { + critical: "#ef4444", + high: "#f97316", + medium: "#f59e0b", + low: "#71717a", +}; + +export function SummaryChart({ findings, trendRecords = [], onClearHistory }: SummaryChartProps) { const counts = useMemo(() => { const s: Record = { critical: 0, @@ -31,6 +42,8 @@ export function SummaryChart({ findings }: SummaryChartProps) { { label: "low", count: counts.low, color: "bg-zinc-500" }, ]; + const hasTrend = trendRecords.length > 0; + return (

@@ -58,6 +71,35 @@ export function SummaryChart({ findings }: SummaryChartProps) {

Total: {total} findings

+ + {hasTrend && ( +
+
+

+ Severity Trend ({trendRecords.length} scans) +

+ {onClearHistory && ( + + )} +
+
+ {(["critical", "high", "medium", "low"] as const).map((sev) => ( + + ))} +
+
+ )}

); } diff --git a/frontend/app/components/TrendChart.tsx b/frontend/app/components/TrendChart.tsx new file mode 100644 index 00000000..6fa66ee8 --- /dev/null +++ b/frontend/app/components/TrendChart.tsx @@ -0,0 +1,130 @@ +"use client"; + +import { useMemo } from "react"; +import type { ScanRecord } from "../lib/scan-history"; + +interface TrendChartProps { + records: ScanRecord[]; + severity: "critical" | "high" | "medium" | "low"; + color: string; + onClear?: () => void; +} + +const CHART_HEIGHT = 80; +const CHART_WIDTH = 280; +const POINT_RADIUS = 3; + +export function TrendChart({ records, severity, color, onClear }: TrendChartProps) { + const pathData = useMemo(() => { + if (records.length < 2) return null; + + const values = records.map((r) => { + switch (severity) { + case "critical": return r.critical; + case "high": return r.high; + case "medium": return r.medium; + case "low": return r.low; + } + }); + + const maxVal = Math.max(...values, 1); + const padding = 4; + const graphHeight = CHART_HEIGHT - padding * 2; + const graphWidth = CHART_WIDTH - padding * 2; + + const points = values.map((v, i) => { + const x = padding + (i / Math.max(values.length - 1, 1)) * graphWidth; + const y = padding + graphHeight - (v / maxVal) * graphHeight; + return `${x},${y}`; + }); + + return { + points: points.map((p) => p.split(",").map(Number)), + line: `M${points.join(" L")}`, + area: `M${points[0]} L${points.join(" L")} L${points[points.length - 1].split(",")[0]},${CHART_HEIGHT - padding} Z`, + maxVal, + }; + }, [records, severity]); + + if (records.length === 0) { + return ( +
+ No data yet +
+ ); + } + + const label = + severity.charAt(0).toUpperCase() + severity.slice(1); + + return ( +
+
+ + {label} + + + {records.length} scan{records.length !== 1 ? "s" : ""} + +
+ + {pathData && ( + <> + {/* Area fill */} + + {/* Line */} + + {/* Points */} + {pathData.points.map(([x, y], i) => ( + + + {new Date(records[i].timestamp).toLocaleDateString()}:{" "} + {severity === "critical" + ? records[i].critical + : severity === "high" + ? records[i].high + : severity === "medium" + ? records[i].medium + : records[i].low}{" "} + findings + + + ))} + + )} + + {onClear && records.length > 0 && ( + + )} +
+ ); +} diff --git a/frontend/app/dashboard/page.tsx b/frontend/app/dashboard/page.tsx index 54400449..642f663a 100644 --- a/frontend/app/dashboard/page.tsx +++ b/frontend/app/dashboard/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useCallback, useMemo, useTransition } from "react"; +import { useState, useCallback, useMemo, useTransition, useEffect } from "react"; import dynamic from "next/dynamic"; import type { Severity } from "../types"; import { transformReport, extractCallGraph, normalizeReport } from "../lib/transform"; @@ -8,7 +8,7 @@ import { normalizeFindingCodeQuery, validateFindingCodeQuery } from "../lib/find import { validateContractBatch } from "../lib/upload-validation"; import type { RejectedFile } from "../lib/upload-validation"; import type { FileProgress } from "../components/DashboardHeader"; -import type { WorkspaceSummary, AnalysisReport } from "../types"; +import type { WorkspaceSummary, AnalysisReport, Finding } from "../types"; import { createWorkspaceFromSingleReport, extractErrorMessage, @@ -27,6 +27,8 @@ import { ErrorBoundary } from "../components/ErrorBoundary"; import { useWorkspace } from "../providers/WorkspaceProvider"; import { WorkspaceSidebar } from "../components/WorkspaceSidebar"; import { DashboardHeader } from "../components/DashboardHeader"; +import { getSettingsHeaders } from "../lib/settings"; +import { saveScanRecord, getScanHistory, clearScanHistory, type ScanRecord } from "../lib/scan-history"; const CallGraph = dynamic(() => import("../components/CallGraph").then((m) => m.CallGraph), { ssr: false, @@ -40,7 +42,7 @@ const CallGraph = dynamic(() => import("../components/CallGraph").then((m) => m. type Tab = "findings" | "callgraph" | "diff"; export default function DashboardPage() { - const { selectedContract, setWorkspace, updateContractReport } = useWorkspace(); + const { workspace, selectedContract, setWorkspace, updateContractReport } = useWorkspace(); const [severityFilter, setSeverityFilter] = useState("all"); const [error, setError] = useState(null); const [jsonInput, setJsonInput] = useState(""); @@ -54,8 +56,17 @@ export default function DashboardPage() { const [sidebarOpen, setSidebarOpen] = useState(false); const [batchProgress, setBatchProgress] = useState>({}); const [rejectedFiles, setRejectedFiles] = useState([]); + const [trendRecords, setTrendRecords] = useState([]); + const [sourceCache, setSourceCache] = useState>({}); const currentReport = selectedContract?.report; + const currentContractName = selectedContract?.name ?? "default"; + + // Load trend data on mount and when workspace changes + useEffect(() => { + const wsName = workspace?.workspace ?? currentContractName; + getScanHistory(wsName).then(setTrendRecords).catch(() => {}); + }, [workspace, currentContractName]); const { findings, nodes: callGraphNodes, edges: callGraphEdges } = useMemo(() => { if (!currentReport) { @@ -82,26 +93,50 @@ export default function DashboardPage() { } }, [baselineJsonInput]); - const applyReport = useCallback((rawReport: unknown) => { + const applyReport = useCallback((rawReport: unknown, source?: string) => { startTransition(() => { if (isWorkspaceSummary(rawReport)) { setWorkspace(rawReport); } else { - setWorkspace(createWorkspaceFromSingleReport(rawReport)); + const ws = createWorkspaceFromSingleReport(rawReport); + if (source && ws.contracts.length > 0) { + ws.contracts[0].source = source; + } + setWorkspace(ws); } }); }, [setWorkspace]); + const persistScanRecord = useCallback((findingsList: Finding[]) => { + const wsName = workspace?.workspace ?? currentContractName; + const counts = { critical: 0, high: 0, medium: 0, low: 0 }; + findingsList.forEach((f) => { + if (counts[f.severity] !== undefined) counts[f.severity]++; + }); + saveScanRecord({ + workspace: wsName, + timestamp: Date.now(), + ...counts, + total: findingsList.length, + }).then(() => { + getScanHistory(wsName).then(setTrendRecords).catch(() => {}); + }).catch(() => {}); + }, [workspace, currentContractName]); + const parseReport = useCallback((text: string) => { setError(null); setUploadStatus(null); try { - applyReport(parseJsonInput(text)); + const parsed = parseJsonInput(text); + applyReport(parsed); + const report = normalizeReport(parsed); + const findingsList = transformReport(report); + persistScanRecord(findingsList); } catch (e) { setError("Invalid JSON"); setWorkspace(null); } - }, [applyReport, setWorkspace]); + }, [applyReport, persistScanRecord, setWorkspace]); const loadReport = useCallback(() => { parseReport(jsonInput); @@ -120,19 +155,41 @@ export default function DashboardPage() { e.target.value = ""; }, [parseReport]); - const analyzeFile = useCallback(async (file: File): Promise => { + const analyzeFile = useCallback(async (file: File): Promise<{ payload: unknown; source: string }> => { const formData = new FormData(); formData.append("contract", file); - const response = await fetch("/api/analyze", { method: "POST", body: formData }); + const settingsHeaders = getSettingsHeaders(); + const response = await fetch("/api/analyze", { + method: "POST", + body: formData, + headers: settingsHeaders as Record, + }); const rawBody = await response.text(); let payload: unknown = null; if (rawBody) { try { payload = JSON.parse(rawBody); } catch { payload = rawBody; } } if (!response.ok) throw new Error(extractErrorMessage(payload, "Contract analysis failed")); - return payload; + // Read the source from the File object + const source = await file.text(); + return { payload, source }; }, []); + const getSourceForFinding = useCallback((finding: Finding): string | undefined => { + const contractName = workspace?.contracts.find( + (c) => c.source && (finding.location.includes(c.name) || finding.location === c.name) + )?.name; + if (contractName && sourceCache[contractName]) { + return sourceCache[contractName]; + } + // Fall back to selected contract's source + return selectedContract?.source; + }, [workspace, selectedContract, sourceCache]); + + const getFileNameForFinding = useCallback((finding: Finding): string | undefined => { + return selectedContract?.name ?? "contract.rs"; + }, [selectedContract]); + const handleContractFiles = useCallback(async (files: File[]) => { const { valid, rejected } = validateContractBatch(files); @@ -151,9 +208,14 @@ export default function DashboardPage() { setBatchProgress({ [file.name]: "analyzing" }); setUploadStatus(`Analyzing ${file.name}…`); try { - const payload = await analyzeFile(file); + const { payload, source } = await analyzeFile(file); + setSourceCache((prev) => ({ ...prev, [file.name]: source })); setJsonInput(JSON.stringify(payload, null, 2)); - applyReport(payload); + applyReport(payload, source); + // Persist scan record for trend chart + const report = normalizeReport(payload); + const transformedFindings = transformReport(report); + persistScanRecord(transformedFindings); setBatchProgress({ [file.name]: "done" }); setUploadStatus(`Analysis report ready for ${file.name}.`); } catch (err) { @@ -174,7 +236,7 @@ export default function DashboardPage() { const skeleton: WorkspaceSummary = { workspace: "batch-upload", - contracts: valid.map((f) => ({ name: f.name, total_findings: 0 })), + contracts: valid.map((f) => ({ name: f.name, total_findings: 0, source: "" })), shared_libs: [], grand_total_findings: 0, }; @@ -182,12 +244,14 @@ export default function DashboardPage() { let doneCount = 0; let errorCount = 0; + const newSourceCache: Record = {}; await Promise.all( valid.map(async (file) => { setBatchProgress((prev) => ({ ...prev, [file.name]: "analyzing" })); try { - const payload = await analyzeFile(file); + const { payload, source } = await analyzeFile(file); + newSourceCache[file.name] = source; const report = normalizeReport(payload); updateContractReport(file.name, report); setBatchProgress((prev) => ({ ...prev, [file.name]: "done" })); @@ -199,11 +263,19 @@ export default function DashboardPage() { }) ); + setSourceCache((prev) => ({ ...prev, ...newSourceCache })); setIsUploadingContract(false); setUploadStatus( `Batch complete: ${doneCount} analyzed${errorCount > 0 ? `, ${errorCount} failed` : ""}.` ); - }, [analyzeFile, applyReport, setWorkspace, updateContractReport]); + }, [analyzeFile, applyReport, setWorkspace, updateContractReport, persistScanRecord]); + + const handleClearHistory = useCallback(() => { + const wsName = workspace?.workspace ?? currentContractName; + clearScanHistory(wsName).then(() => { + setTrendRecords([]); + }).catch(() => {}); + }, [workspace, currentContractName]); const handleCodeFilterChange = useCallback((input: string) => { const normalized = normalizeFindingCodeQuery(input); @@ -275,7 +347,11 @@ export default function DashboardPage() { - + @@ -362,6 +438,8 @@ export default function DashboardPage() { findings={findings} severityFilter={severityFilter} codeFilter={codeFilterError ? "" : codeFilterInput} + getSource={getSourceForFinding} + getFileName={getFileNameForFinding} /> diff --git a/frontend/app/lib/scan-history.ts b/frontend/app/lib/scan-history.ts new file mode 100644 index 00000000..4a80a611 --- /dev/null +++ b/frontend/app/lib/scan-history.ts @@ -0,0 +1,124 @@ +export interface ScanRecord { + id?: number; + workspace: string; + timestamp: number; + critical: number; + high: number; + medium: number; + low: number; + total: number; +} + +const DB_NAME = "sanctifier-scan-history"; +const DB_VERSION = 1; +const STORE_NAME = "scans"; +const MAX_RECORDS_PER_WORKSPACE = 20; + +function openDB(): Promise { + return new Promise((resolve, reject) => { + const request = indexedDB.open(DB_NAME, DB_VERSION); + request.onupgradeneeded = () => { + const db = request.result; + if (!db.objectStoreNames.contains(STORE_NAME)) { + const store = db.createObjectStore(STORE_NAME, { + keyPath: "id", + autoIncrement: true, + }); + store.createIndex("workspace", "workspace", { unique: false }); + store.createIndex("timestamp", "timestamp", { unique: false }); + } + }; + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error); + }); +} + +export async function saveScanRecord(record: Omit): Promise { + const db = await openDB(); + const tx = db.transaction(STORE_NAME, "readwrite"); + const store = tx.objectStore(STORE_NAME); + + // Get existing count for this workspace + const workspaceIndex = store.index("workspace"); + const countRequest = workspaceIndex.count(record.workspace); + const existingIds: number[] = await new Promise((resolve, reject) => { + const req = workspaceIndex.getAllKeys(record.workspace); + req.onsuccess = () => resolve(req.result as number[]); + req.onerror = () => reject(req.error); + }); + + // Remove oldest records if over the limit + if (existingIds.length >= MAX_RECORDS_PER_WORKSPACE) { + const excess = existingIds.length - MAX_RECORDS_PER_WORKSPACE + 1; + // Sort by id (insertion order approximates chronological order) + existingIds.sort((a, b) => a - b); + for (let i = 0; i < excess; i++) { + store.delete(existingIds[i]); + } + } + + store.add(record); + return new Promise((resolve, reject) => { + tx.oncomplete = () => resolve(); + tx.onerror = () => reject(tx.error); + }); +} + +export async function getScanHistory(workspace: string): Promise { + const db = await openDB(); + const tx = db.transaction(STORE_NAME, "readonly"); + const store = tx.objectStore(STORE_NAME); + const workspaceIndex = store.index("workspace"); + + return new Promise((resolve, reject) => { + const request = workspaceIndex.getAll(workspace); + request.onsuccess = () => { + const records = (request.result as ScanRecord[]).sort( + (a, b) => a.timestamp - b.timestamp + ); + resolve(records); + }; + request.onerror = () => reject(request.error); + }); +} + +export async function getAllWorkspaces(): Promise { + const db = await openDB(); + const tx = db.transaction(STORE_NAME, "readonly"); + const store = tx.objectStore(STORE_NAME); + const workspaceIndex = store.index("workspace"); + + return new Promise((resolve, reject) => { + const request = workspaceIndex.getAllKeys(); + request.onsuccess = () => { + const keys = request.result as string[]; + resolve([...new Set(keys)]); + }; + request.onerror = () => reject(request.error); + }); +} + +export async function clearScanHistory(workspace?: string): Promise { + const db = await openDB(); + const tx = db.transaction(STORE_NAME, "readwrite"); + const store = tx.objectStore(STORE_NAME); + + if (workspace) { + const workspaceIndex = store.index("workspace"); + const request = workspaceIndex.getAllKeys(workspace); + const keys: number[] = await new Promise((resolve, reject) => { + request.onsuccess = () => resolve(request.result as number[]); + request.onerror = () => reject(request.error); + }); + for (const key of keys) { + store.delete(key); + } + } else { + store.clear(); + } + + return new Promise((resolve, reject) => { + tx.oncomplete = () => resolve(); + tx.onerror = () => reject(tx.error); + }); +} diff --git a/frontend/app/lib/settings.ts b/frontend/app/lib/settings.ts new file mode 100644 index 00000000..db3d6885 --- /dev/null +++ b/frontend/app/lib/settings.ts @@ -0,0 +1,65 @@ +const SETTINGS_KEY = "sanctifier-settings"; + +export interface AppSettings { + analyzerPath: string; + sanctifyToml: string; + customRulesPath: string; + apiEndpoint: string; + aiProvider: "stub" | "anthropic" | "openai" | "local"; + aiApiKey: string; + dailyCostCap: string; + theme: string; + telemetryEnabled: boolean; +} + +export const DEFAULT_SETTINGS: AppSettings = { + analyzerPath: "", + sanctifyToml: "", + customRulesPath: "", + apiEndpoint: "", + aiProvider: "stub", + aiApiKey: "", + dailyCostCap: "10.00", + theme: "system", + telemetryEnabled: false, +}; + +export function getSettings(): AppSettings { + if (typeof window === "undefined") return DEFAULT_SETTINGS; + try { + const stored = localStorage.getItem(SETTINGS_KEY); + if (stored) { + return { ...DEFAULT_SETTINGS, ...JSON.parse(stored) }; + } + } catch (error) { + console.error("Failed to load settings:", error); + } + return DEFAULT_SETTINGS; +} + +export function getSettingsHeaders(): Record { + const settings = getSettings(); + const headers: Record = {}; + + if (settings.analyzerPath) { + headers["x-sanctifier-bin-path"] = settings.analyzerPath; + } + if (settings.sanctifyToml) { + headers["x-sanctifier-toml"] = btoa(settings.sanctifyToml); + } + if (settings.customRulesPath) { + headers["x-sanctifier-custom-rules"] = settings.customRulesPath; + } + if (settings.apiEndpoint) { + headers["x-sanctifier-api-endpoint"] = settings.apiEndpoint; + } + if (settings.aiProvider !== "stub") { + headers["x-sanctifier-ai-provider"] = settings.aiProvider; + if (settings.aiApiKey) { + headers["x-sanctifier-ai-key"] = settings.aiApiKey; + } + } + headers["x-sanctifier-telemetry"] = settings.telemetryEnabled ? "1" : "0"; + + return headers; +} diff --git a/frontend/app/lib/transform.ts b/frontend/app/lib/transform.ts index 7743678a..d1d0606a 100644 --- a/frontend/app/lib/transform.ts +++ b/frontend/app/lib/transform.ts @@ -291,11 +291,12 @@ export function transformReport(report: AnalysisReport): Finding[] { }); (report.upgrade_reports ?? []).forEach((up: UpgradeFinding) => { + const sev: Severity = (up.severity as Severity) || "high"; findings.push( toFinding( `upgrade-${idx++}`, up.code ?? "S010", - "high", + sev, "Upgrade Issue", up.finding_type, up.location, diff --git a/frontend/app/playground/page.tsx b/frontend/app/playground/page.tsx index b3a99daf..e037c276 100644 --- a/frontend/app/playground/page.tsx +++ b/frontend/app/playground/page.tsx @@ -6,6 +6,7 @@ import { AnalysisTerminal } from "../components/AnalysisTerminal"; import { FindingsList } from "../components/FindingsList"; import { Play, RotateCcw, Save, Share2, Sparkles, Terminal, Copy, Check, Trash2, X } from "lucide-react"; import type { Finding } from "../types"; +import { getSettingsHeaders } from "../lib/settings"; const DEFAULT_CODE = `use soroban_sdk::{contract, contractimpl, Env, Symbol}; @@ -221,6 +222,7 @@ export default function PlaygroundPage() { method: "POST", headers: { "Content-Type": "application/json", + ...getSettingsHeaders(), }, body: JSON.stringify({ source: code }), }); diff --git a/frontend/app/scan/page.tsx b/frontend/app/scan/page.tsx index 186626cf..5c1a70f1 100644 --- a/frontend/app/scan/page.tsx +++ b/frontend/app/scan/page.tsx @@ -68,6 +68,7 @@ export default function ScanPage() { const response = await fetch("/api/analyze", { method: "POST", body: formData, + headers: getSettingsHeaders() as Record, }); clearInterval(logsTimer); diff --git a/frontend/app/settings/page.tsx b/frontend/app/settings/page.tsx index 00c82add..44425dc7 100644 --- a/frontend/app/settings/page.tsx +++ b/frontend/app/settings/page.tsx @@ -5,6 +5,8 @@ import { useState, useEffect, FormEvent } from "react"; interface Settings { analyzerPath: string; sanctifyToml: string; + customRulesPath: string; + apiEndpoint: string; aiProvider: "stub" | "anthropic" | "openai" | "local"; aiApiKey: string; dailyCostCap: string; @@ -15,6 +17,8 @@ interface Settings { const DEFAULT_SETTINGS: Settings = { analyzerPath: "", sanctifyToml: "", + customRulesPath: "", + apiEndpoint: "", aiProvider: "stub", aiApiKey: "", dailyCostCap: "10.00", @@ -143,6 +147,50 @@ export default function SettingsPage() { />
+
+ + + setSettings({ ...settings, apiEndpoint: e.target.value }) + } + placeholder="https://api.sanctifier.dev/v1" + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent" + /> +

+ Custom API endpoint for report submission +

+
+ +
+ + + setSettings({ ...settings, customRulesPath: e.target.value }) + } + placeholder="/etc/sanctifier/custom-rules.yaml" + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent" + /> +

+ Path to custom rules YAML file (SANCTIFIER_CUSTOM_RULES) +

+
+