rustsec-0.33.0/.cargo_vcs_info.json0000644000000001451046102023000126310ustar { "git": { "sha1": "965fd1e963210776ecbc64b6cb658dd0498fce24" }, "path_in_vcs": "rustsec" }rustsec-0.33.0/CHANGELOG.md000064400000000000000000000455211046102023000132170ustar 00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## 0.30.2 (2025-02-28) ### Fixed - Upgraded to `tame-index` v0.18.1 to fix an incompatibility with Rust 1.85 and later ([#1333]) [#1333]: https://github.com/RustSec/rustsec/pull/1333 ## 0.30.1 (2025-01-18) ### Added - Added public APIs for scanning binary files that were previously private to `cargo audit` ([#1291]) ### Fixed - Fixed OSV spec compliance issue with schema version being serialized as `null` if not set ([#1287]) - Upgraded `cargo-lock` to fix an issue with Cargo.lock v4 format parsing in presence of git tags ([#1298]) [#1287]: https://github.com/rustsec/rustsec/pull/1287 [#1291]: https://github.com/rustsec/rustsec/pull/1291 [#1298]: https://github.com/RustSec/rustsec/pull/1298 ## 0.30.0 (2024-10-29) ### Changed - MSRV 1.73 ([#1222]) - Bump `cargo-lock` to v0.10; adds V4 lockfile support ([#1224], [#1264]) - Bump `gix` to 0.66 ([#1251]) - Bump `tame-index` to v0.14 ([#1251]) [#1222]: https://github.com/rustsec/rustsec/pull/1222 [#1224]: https://github.com/rustsec/rustsec/pull/1224 [#1251]: https://github.com/rustsec/rustsec/pull/1251 [#1264]: https://github.com/rustsec/rustsec/pull/1264 ## 0.29.2 (2024-05-01) ### Changed - Upgraded to `gix` v0.62. This fixes [RUSTSEC-2024-0335](https://rustsec.org/advisories/RUSTSEC-2024-0335.html). It also transitively upgrades to `reqwest` v0.12 and `hyper` v1.0. ([#1174]) [#1174]: https://github.com/rustsec/rustsec/pull/1174 ## 0.29.1 (2024-02-16) ### Changed - Upgraded to `gix` v0.60. This fixes build issues due to a semver-incompatible change in the interaction of `gix` and `tame-index`. ([#1143]) [#1143]: https://github.com/rustsec/rustsec/pull/1143 ## 0.29.0 (2024-02-16) ### Changed - Completely rewritten the `fix` module. ([#1113]) - Now it edits `Cargo.lock` as opposed to `Cargo.toml`, and performs only semver-compatible upgrades. - Fixes are performed by calling `cargo update`, migrating away from the unmaintained `cargo-edit-9` crate. - The `fix` feature is removed and the module is always enabled now that it requires no additional dependencies. - The module is still experimental, and its behavior may change in the future. - Require `tame-index` 0.9.3 or later, which fixes [issues with some enterprise firewalls](https://github.com/rustsec/rustsec/issues/1058). ([#1103]) [#1103]: https://github.com/rustsec/rustsec/pull/1103 [#1113]: https://github.com/rustsec/rustsec/pull/1113 ## 0.28.6 (2024-02-11) ### Changed - Additions to the OSV advisory struct ([#656]) - Add the `schema_version` field to `OsvAdvisory` - Add a `Deserialize` implementation for `OsvAdvisory` - Add getter methods for `OsvAdvisory` content [#656]: https://github.com/rustsec/rustsec/pull/656 ## 0.28.4 (2024-02-03) ### Changed - Upgraded dependencies `gix` to 0.58.x and `tame-index` to 0.9.x ([#1099]) [#1099]: https://github.com/rustsec/rustsec/pull/1099 ## 0.28.4 (2023-11-17) ### Changed - Upgraded dependencies `gix` to 0.55.x and `tame-index` to 0.8.x ([#1061]) [#1061]: https://github.com/rustsec/rustsec/pull/1061 ## 0.28.3 (2023-10-14) ### Changed - Switched from Git-compatible lockfiles to locks provided by the operating system. This fixes issues around stale lockfiles being left behind on power loss. ([#1032]) - `CachedIndex` now acquires the global Cargo package lock. This was necessary to avoid racing with Cargo when updating crates.io index via Git or writing sparse index entries. Note that this also prevents most Cargo operations for the current user until `CachedIndex` is dropped. ([#1032]) - The `gix` crate is now used in the `max-performance-safe` configuration, enabling multi-threading. ([#1045]) ### Added - The `Severity` type now implements `Hash` ([#1042]) [#1032]: https://github.com/rustsec/rustsec/pull/1032 [#1042]: https://github.com/rustsec/rustsec/pull/1042 [#1045]: https://github.com/rustsec/rustsec/pull/1045 ## 0.28.2 (2023-09-25) ### Fixed - Upgraded to `tame-index` 0.6.0 and `gix` 0.53.1 to fix a vulnerability in `gix`, see [RUSTSEC-2023-0064](https://rustsec.org/advisories/RUSTSEC-2023-0064.html) ([#1015]) - Correctly report error when encountering a stale git lockfile ([#1012]) [#1012]: https://github.com/rustsec/rustsec/pull/1012 [#1015]: https://github.com/rustsec/rustsec/pull/1015 ## 0.28.1 (2023-09-06) ### Changed - No longer require HTTP/2 for accessing sparse crates.io index. This degrades performance somewhat due to an additional roundtrip to crates.io, but allows index access through corporate transparent proxies that do not support HTTP/2. ([#992]) ### Fixed - Fixed a performance regression where the cached crates.io index would re-request items that are already cached ([#987]) [#987]: https://github.com/rustsec/rustsec/pull/987 [#992]: https://github.com/rustsec/rustsec/pull/992 ## 0.28.0 (2023-08-31) ### Added - [Sparse crates.io index](https://blog.rust-lang.org/inside-rust/2023/01/30/cargo-sparse-protocol.html) is now supported. This dramatically speeds up the checks for yanked crates. This crate honors the [Cargo settings for the use of sparse index](https://doc.rust-lang.org/cargo/reference/config.html#registriescrates-ioprotocol), should you need to opt out. ([#923]) - Added directory locking and explicit locking controls to the API to avoid several processes modifying local data at the same time. ([#923], [#944]) - Added `affected` field to `Warning`, to communicate e.g. warnings specific to a particular platform. ([#964]) - Added `license` field to the advisory format in preparation for data import from GHSA. ([#682]) - Added a `CommitHash` type to represent git commit hashes independently from the git implementation used. ([#961]) ### Changed - Switched from OpenSSL to [rustls](https://crates.io/crates/rustls) as the TLS implementation. ([#923], [#925]) - Due to this change CPU platforms other than x86 and ARM are no longer supported. This issue is tracked as [#962](https://github.com/rustsec/rustsec/issues/962). - The `fix` feature is not yet converted; enabling it will pull in OpenSSL. - Switched from `libgit2` to `gitoxide` as the git implementation. ([#925]) - Switched from `crates-index` to `tame-index` for crates.io access. ([#923]) - Increased the minimum supported rust version to 1.67. ([#923]) ### Removed - Removed `rustsec::registry::Index` because it is impractically slow when the sparse crates.io index is used. Use `rustsec::registry::CachedIndex` instead. ([#923]) - Removed `rustsec::registry::CachedIndex.is_yanked()`. Use `.find_yanked()` instead. Checking a large number of crates at once is orders of magnitude faster when using the sparse index. ([#937]) - Removed many `From` implementations from `rustsec::Error` to avoid tying `rustsec` SemVer to that of dependency crates. This should result in less frequent SemVer bumps for `rustsec` in the future. ([#961]) ### Fixed - `rustsec` can now be used in Alpine Linux containers ([#466](https://github.com/rustsec/rustsec/issues/466)). - Several users of `rustsec` running in parallel can now fetch Git repositories without races ([#490](https://github.com/rustsec/rustsec/issues/490)). - Accessing Git repositories over SSH is now supported ([#292](https://github.com/rustsec/rustsec/issues/292)). - Credential helpers to access private repositories are now supported [#555](https://github.com/rustsec/rustsec/issues/555). - Fix an edge case in git source dependency resolution when dependencies differ only in their hash. ([#889]) [#682]: https://github.com/rustsec/rustsec/pull/682 [#889]: https://github.com/rustsec/rustsec/pull/889 [#905]: https://github.com/rustsec/rustsec/pull/905 [#923]: https://github.com/rustsec/rustsec/pull/923 [#925]: https://github.com/rustsec/rustsec/pull/925 [#937]: https://github.com/rustsec/rustsec/pull/937 [#944]: https://github.com/rustsec/rustsec/pull/944 [#961]: https://github.com/rustsec/rustsec/pull/961 [#964]: https://github.com/rustsec/rustsec/pull/964 ## 0.27.0 (2023-05-10) ### Added - Upgraded to `cargo-lock` v9.0.0, which enables support for sparse registries. ## 0.26.5 (2023-03-22) ### Changed - Migrated to a maintained fork of `cargo-edit` v0.9.x to fix [CVE-2023-22742] in the transitive dependency `libgit2-sys` ([#831]) - Removed the experimental check for the presence of a signature on the advisory-db repository. It only verified the presence of a signature without checking for any particular key, so it provided no additional security. ([#816]) - Fixed a build failure with certain dependency versions on recent compilers due to failing type inference ([#836]) [CVE-2023-22742]: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-22742 [#816]: https://github.com/rustsec/rustsec/pull/816 [#831]: https://github.com/rustsec/rustsec/pull/831 [#836]: https://github.com/rustsec/rustsec/pull/836 ## 0.26.4 (2022-11-15) ### Fixed - `registry::CachedIndex` now correctly handles invalid semver versions in crates.io registry, which crates.io allows for some reason ([#762]) [#762]: https://github.com/rustsec/rustsec/pull/762 ## 0.26.3 (2022-11-01) ### Added - `registry::CachedIndex` which is orders of magnitude faster than `registry::Index` when scanning multiple `Cargo.lock` files or binaries ([#730]) [#730]: https://github.com/rustsec/rustsec/pull/730 ## 0.26.2 (2022-08-15) ### Fixed - Fixed `withdrawn` ([#642]) [#642]: https://github.com/RustSec/rustsec/pull/642 ## 0.26.1 (2022-08-14) ### Changed - Deprecate `yanked` ([#631]) [#631]: https://github.com/RustSec/rustsec/pull/631 ## 0.26.0 (2022-05-21) ### Added - `[advisory.source]` ([#541]) - `doc_cfg` annotations when building on docs.rs ([#571]) ### Changed - Bump `git2` dependency to v0.14; MSRV 1.57 ([#524]) - Bump `platforms` dependency to v3.0 ([#532]) - Update to 2021 edition ([#538]) - Use `Query::crate_scope()` as the `Default` ([#544]) - Bump `cvss` dependency to v2.0 ([#550]) - Bump `cargo-lock` dependency to v8.0 ([#561]) - Flatten `warnings` module; rename `WarningKind` ([#572]) - Flatten `advisory::id` module; rename `IdKind` ([#573]) ### Removed - Legacy database scopes ([#541]) [#524]: https://github.com/RustSec/rustsec/pull/524 [#532]: https://github.com/RustSec/rustsec/pull/532 [#538]: https://github.com/RustSec/rustsec/pull/538 [#541]: https://github.com/RustSec/rustsec/pull/541 [#544]: https://github.com/RustSec/rustsec/pull/544 [#550]: https://github.com/RustSec/rustsec/pull/550 [#561]: https://github.com/RustSec/rustsec/pull/561 [#571]: https://github.com/RustSec/rustsec/pull/571 [#572]: https://github.com/RustSec/rustsec/pull/572 [#573]: https://github.com/RustSec/rustsec/pull/573 ## 0.25.1 (2021-11-15) ### Changed - Bump `platforms` dependency to v2.0.0 ([#485]) [#485]: https://github.com/RustSec/rustsec/pull/485 ## 0.25.0 (2021-11-12) [YANKED] ### Changed - Bump `cargo-edit` dependency from 0.7.0 to 0.8.0 ([#439]) - Make `advisory::id::Kind` lowercase ([#471]) - Bump MSRV to 1.52 ([#476]) - Flatten API: make modules with one type non-`pub`; re-export type from parent ([#478]) [#439]: https://github.com/RustSec/rustsec/pull/439 [#471]: https://github.com/RustSec/rustsec/pull/471 [#476]: https://github.com/RustSec/rustsec/pull/476 [#478]: https://github.com/RustSec/rustsec/pull/478 ## 0.24.3 (2021-09-11) ### Added - `vendored-libgit2` feature ([#432]) ### Changed - OSV v1.0 ([#421]) [#421]: https://github.com/RustSec/rustsec/pull/421 [#432]: https://github.com/RustSec/rustsec/pull/432 ## 0.24.2 (2021-07-20) ### Changed - Support `~` and `=` operators in version specification ([#402]) - Bump `crates-index` from 0.16.7 to 0.17.0 ([#403]) [#402]: https://github.com/RustSec/rustsec/pull/402 [#403]: https://github.com/RustSec/rustsec/pull/403 ## 0.24.1 (2021-07-02) ### Changed - Do not lint year in CVE IDs ([#393]) [#393]: https://github.com/RustSec/rustsec/pull/393 ## 0.24.0 (2021-06-28) ### Added - OSV export ([#366]) ### Changed - Bump `cargo-lock` to v7.0 ([#379]) [#366]: https://github.com/RustSec/rustsec/pull/366 [#379]: https://github.com/RustSec/rustsec/pull/379 ## 0.23.3 (2021-03-08) ### Fixed - Workaround for stale git refs ## 0.23.2 (2021-03-07) ### Changed - Rename advisory-db `master` branch to `main` ## 0.23.1 (2021-02-24) ### Fixed - Parsing error on Windows ## 0.23.0 (2021-01-26) ### Added - Advisory `references` as a URL list - Support for omitting leading `[advisory]` table - `thread-safety` category ### Changed - Rename previous `references` field to `related` - Use `url` crate to parse metadata URL - Bump `smol_str` to v0.1.17; MSRV 1.46+ - Replace `chrono` with `humantime` - Mark enums as non_exhaustive - Use `SystemTime` instead of a `git::Timestamp` type - Rename `fetch` Cargo feature to `git` - Rename `repository::GitRepository` to `repository::git::Repository` ### Removed - `markdown` feature ## 0.22.2 (2020-10-27) ### Changed - Revert "Refactor Advisory type handling" ## 0.22.1 (2020-10-26) [YANKED] ### Changed - Refactor `Advisory` and `VulnerabilityInfo` ## 0.22.0 (2020-10-25) [YANKED] ### Added - `fetch` feature ### Changed - Bump `cargo-lock` to v6; `semver` to v0.11 - Make `advisory.title` and `advisory.description` struct fields - Remove support for the V2 advisory format - Mark the `advisory::parser` module as `pub` - Bump `cargo-edit` to 0.7.0 - Bump `crates-index` from 0.15.4 to 0.16.0 - `advisory`: laxer function path handling - `linter`: fully deprecate `obsolete` in favor of `yanked` - `advisory`: `markdown` feature and `Advisory::description_html` - `linter`: add support for V3 advisory format - MSRV 1.41+ - Bump `platforms` crate to v1 ### Fixed - `linter`: correctly handle crates with dashes in names ### Removed - `advisory.metadata.title` and `advisory.metadata.description` ## 0.21.0 (2020-06-23) ### Added - `year`, `month`, and `day` methods to `advisory::Date` - `unsound` informational advisory kind ### Changed - Bump `crates-index` from 0.14 to 0.15 - Rename `obsolete` advisories to `yanked` - Rename `warning::Kind::Informational` to `::Notice` - Make `warning::Kind` a `#[non_exhausive]` enum - Make `Informational` a `#[non_exhausive]` enum ### Removed - Legacy `patched_versions` and `unaffected_versions` ## 0.20.1 (2020-06-14) ### Added - `advisory::Id::numerical_part()` ## 0.20.0 (2020-05-06) ### Changed - Make `WarningInfo` into a simple type alias ## 0.19.0 (2020-05-04) - Refactor package scopes - Prototype V3 Advisory Format - Bump dependencies to link `libgit2` dynamically - Add `WarningInfo` and modify `Warning` struct - Drop support for the V1 advisory format ## 0.18.0 (2020-02-05) - Move yanked crate auditing to `cargo-audit` ## 0.17.1 (2020-01-22) - Update `cargo-lock` requirement from 3.0 to 4.0 ## 0.17.0 (2020-01-19) - Bump MSRV to 1.39 - Extract `cargo audit fix` logic into `Fixer` - Warn for yanked crates - Add `vendored-openssl` feature - Support crate sources as a vulnerability query attribute - Try to auto-detect proxy setting ## 0.16.0 (2019-10-13) - Remove `support.toml` parsing ## 0.15.2 (2019-10-08) - version: Fix matching bug for `>` version requirements ## 0.15.1 (2019-10-07) - linter: Add `informational` as an allowable `[advisory]` key - repository: Expose `authentication` module ## 0.15.0 (2019-10-01) - Upgrade to `cargo-lock` crate v3.0 ## 0.14.1 (2019-09-25) - Upgrade to `cargo-lock` crate v2.0 ## 0.14.0 (2019-09-24) - warning: Extract into module; make more like `Vulnerability` - Upgrade to `cvss` crate v1.0 - Upgrade to `cargo-lock` crate v1.0 ## 0.13.0 (2019-09-23) - linter: Ensure advisory date's year matches year in advisory ID - Use the `cargo-lock` crate - lockfile: Add (optional) DependencyGraph analysis - Rename `rustsec::db` module to `rustsec::database` - report: Generate warnings for selected informational advisories - vulnerability: Add `affected_functions()` - Add `rustsec::advisory::Linter` - package: Parse dependencies from Cargo.lock - Initial `report` module and built-in report-generating - Basic query support - Index the `rust` advisory directory from `RustSec/advisory-db` - Add first-class support for GitHub Security Advisories (GHSA) - Re-vendor Cargo's git authentication code - `support.toml` for indicating supported versions - Add support for "informational" advisories - Add `rustsec::advisory::Category` - Refactor advisory types: add `[affected]` and `[versions]` sections - advisory: Add (optional) `cvss` field with CVSS v3.1 score - Freshen deps: add `home`, remove `directories` and `failure` - Improved handling of prereleases; MSRV 1.35+ - Add `Version` and `VersionReq` newtypes ## 0.12.1 (2019-07-29) - Use new inclusive range syntax ## 0.12.0 (2019-07-15) - Update dependencies and use 2018 import conventions; Rust 1.32+ - Re-export all types in `advisory::paths::*` ## 0.11.0 (2019-01-13) - Cargo.toml: Update `platforms` crate to v0.2 - Redo advisory's `affected_functions` as `affected_paths` ## 0.10.0 (2018-12-14) - Implement `affected_functions` advisory attribute - Fix handling of `unaffected_versions` - Update to Rust 2018 edition ## 0.9.3 (2018-10-14) - Create parents of the `advisory-db` repo dir ## 0.9.2 (2018-10-14) - Handle cloning `advisory-db` into existing, empty dir ## 0.9.1 (2018-07-29) - Use Cargo's git authentication helper ## 0.9.0 (2018-07-26) - Use `platforms` crate for platform-related functionality ## 0.8.0 (2018-07-24) - Advisory platform requirements - Cargo-like keyword support ## 0.7.5 (2018-07-24) - Allow `AdvisoryId::new()` to parse `RUSTSEC-0000-0000` ## 0.7.4 (2018-07-23) - Add link to logo image for docs.rs ## 0.7.3 (2018-07-23) - Fix builds with `--no-default-features` ## 0.7.2 (2018-07-23) - README.md: Badge fixups, add gitter badge ## 0.7.1 (2018-07-23) - Cargo.toml: Formatting fixups, add `readme` attribute ## 0.7.0 (2018-07-22) - Validate dates are well-formed - Add `AdvisoryIdKind` and limited support for parsing advisory IDs - Add a `Vulnerabilities` collection struct - Parse aliases, references, and unaffected versions - Parse (but do not yet verify) signatures on advisory-db commits - Parse individual advisory `.toml` files rather than Advisories.toml - Switch to `git2`-based fetcher for `advisory-db` - Use serde to parse advisories TOML and `Cargo.lock` files - Use `failure` crate for error handling ## 0.6.0 (2017-03-05) - Use `semver::Version` for `lockfile::Package` versions - Move `AdvisoryDatabase` under the `::db` module - Lockfile support ## 0.5.2 (2017-02-26) - Add `AdvisoryDatabase::fetch_from_url()` ## 0.5.1 (2017-02-26) - Make `advisory` and `error` modules public ## 0.5.0 (2017-02-26) - Use str version param for `AdvisoryDatabase::find_vulns_for_crate()` ## 0.4.0 (2017-02-26) - Add `AdvisoryDatabase::find_vulns_for_crate()` ## 0.3.0 (2017-02-26) - Rename `crate_name` TOML attribute back to `package` ## 0.2.0 (2017-02-25) - Rename `package` TOML attribute to `crate_name` - Add iterator support to `AdvisoryDatabase` ## 0.1.0 (2017-02-25) - Initial release rustsec-0.33.0/Cargo.lock0000644000002622241046102023000106140ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "adler2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aho-corasick" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] [[package]] name = "allocator-api2" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "anyhow" version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "arc-swap" version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" dependencies = [ "rustversion", ] [[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "async-compression" version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e79b3f8a79cccc2898f31920fc69f304859b3bd567490f75ebf51ae1c792a9ac" dependencies = [ "compression-codecs", "compression-core", "pin-project-lite", "tokio", ] [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "auditable-extract" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44371e9f9759dea49c42b6c6fe4c64ea216ee2af325a4524a7180823e00d3e7a" dependencies = [ "binfarce", "wasmparser 0.207.0", ] [[package]] name = "auditable-info" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c692b37b578433ebc75db30941a7ff137c381a204beb2429a30b7587d4d4dff3" dependencies = [ "auditable-extract", "auditable-serde", "miniz_oxide", "serde_json", ] [[package]] name = "auditable-serde" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d026218ae25ba5c72834245412dd1338f6d270d2c5109ee03a4badec288d4056" dependencies = [ "semver", "serde", "serde_json", "topological-sort", ] [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" dependencies = [ "aws-lc-sys", "zeroize", ] [[package]] name = "aws-lc-sys" version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" dependencies = [ "cc", "cmake", "dunce", "fs_extra", ] [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "binfarce" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18464ccbb85e5dede30d70cc7676dc9950a0fb7dbf595a43d765be9123c616a2" [[package]] name = "bitflags" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "borsh" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" dependencies = [ "bytes", "cfg_aliases", ] [[package]] name = "bstr" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" dependencies = [ "memchr", "regex-automata", "serde", ] [[package]] name = "bumpalo" version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "camino" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" [[package]] name = "cargo-lock" version = "11.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63585cdf8572aa7adf0e30a253f988f2b77233bfac1973d52efb6dd53a75920e" dependencies = [ "petgraph", "semver", "serde", "toml 0.9.12+spec-1.1.0", "url", ] [[package]] name = "cc" version = "1.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" dependencies = [ "find-msvc-tools", "jobserver", "libc", "shlex", ] [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clru" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "197fd99cb113a8d5d9b6376f3aa817f32c1078f2343b714fff7d2ca44fdf67d5" dependencies = [ "hashbrown 0.16.1", ] [[package]] name = "cmake" version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ "cc", ] [[package]] name = "combine" version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ "bytes", "memchr", ] [[package]] name = "compression-codecs" version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf" dependencies = [ "compression-core", "flate2", "memchr", ] [[package]] name = "compression-core" version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" [[package]] name = "core-foundation" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crc32fast" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-channel" version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crypto-common" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", ] [[package]] name = "curl" version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79fc3b6dd0b87ba36e565715bf9a2ced221311db47bd18011676f24a6066edbc" dependencies = [ "curl-sys", "libc", "schannel", "socket2", "windows-sys 0.59.0", ] [[package]] name = "curl-sys" version = "0.4.88+curl-8.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "644816de6547255eff4e491a1dda1c19b7237f00b62a61e6e64859ce4f2906d0" dependencies = [ "cc", "libc", "libz-sys", "pkg-config", "vcpkg", "windows-sys 0.61.2", ] [[package]] name = "cvss" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fb220d3ce1b565af39cee5b89e47fd8dd1dab162900ee4363c8ee4169ee8a2" dependencies = [ "serde", ] [[package]] name = "deranged" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", "serde_core", ] [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", ] [[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "dunce" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "encoding_rs" version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", "windows-sys 0.61.2", ] [[package]] name = "faster-hex" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73" dependencies = [ "heapless", "serde", ] [[package]] name = "fastrand" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "filetime" version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d5b2eef6fafbf69f877e55509ce5b11a760690ac9700a2921be067aa6afaef6" dependencies = [ "cfg-if", "libc", ] [[package]] name = "find-msvc-tools" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fixedbitset" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[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 = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "foldhash" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "form_urlencoded" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] [[package]] name = "fs-err" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73fde052dbfc920003cfd2c8e2c6e6d4cc7c1091538c3a24226cec0665ab08c0" dependencies = [ "autocfg", ] [[package]] name = "fs_extra" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures-channel" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-io" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-sink" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-core", "futures-io", "futures-sink", "futures-task", "memchr", "pin-project-lite", "slab", ] [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "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.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi 5.3.0", "wasip2", "wasm-bindgen", ] [[package]] name = "getrandom" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", "wasip2", "wasip3", ] [[package]] name = "gix" version = "0.84.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae54ae0ebd1a5a3c3f8d95dd3b5ca6e63f4fed9bfd585e13801a97d7bde8f9ce" dependencies = [ "gix-actor", "gix-attributes", "gix-command", "gix-commitgraph", "gix-config", "gix-credentials", "gix-date", "gix-diff", "gix-discover", "gix-error", "gix-features", "gix-filter", "gix-fs", "gix-glob", "gix-hash", "gix-hashtable", "gix-ignore", "gix-index", "gix-lock", "gix-negotiate", "gix-object", "gix-odb", "gix-pack", "gix-path", "gix-pathspec", "gix-prompt", "gix-protocol", "gix-ref", "gix-refspec", "gix-revision", "gix-revwalk", "gix-sec", "gix-shallow", "gix-submodule", "gix-tempfile", "gix-trace", "gix-transport", "gix-traverse", "gix-url", "gix-utils", "gix-validate", "gix-worktree", "gix-worktree-state", "gix-worktree-stream", "nonempty", "smallvec", "thiserror", ] [[package]] name = "gix-actor" version = "0.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bc998b8f746dda8565450d08a63b792ced9165d8c27a1ed3f02799ec6a7820f" dependencies = [ "bstr", "gix-date", "gix-error", ] [[package]] name = "gix-attributes" version = "0.33.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d43f12e246d3bf7ec624c8fc15ac4a4b62b7c4c6f586cb82be6c90bf84c9d02" dependencies = [ "bstr", "gix-glob", "gix-path", "gix-quote", "gix-trace", "kstring", "smallvec", "thiserror", "unicode-bom", ] [[package]] name = "gix-bitmap" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52ebef0c26ad305747649e727bbcd56a7b7910754eb7cea88f6dff6f93c51283" dependencies = [ "gix-error", ] [[package]] name = "gix-chunk" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9faee47943b638e58ddd5e275a4906ad3e4b6c8584f1d41bd18ab9032ec52afb" dependencies = [ "gix-error", ] [[package]] name = "gix-command" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00706d4fef135ef4b01680d5218c6ee40cda8baf697b864296cbc887d19118f6" dependencies = [ "bstr", "gix-path", "gix-quote", "gix-trace", "shell-words", ] [[package]] name = "gix-commitgraph" version = "0.37.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f675d0df484a7f6a47e64bd6f311af489d947c0323b0564f36d14f3d7762abb" dependencies = [ "bstr", "gix-chunk", "gix-error", "gix-hash", "memmap2", "nonempty", ] [[package]] name = "gix-config" version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f2372d4b49ca28431e7d150cab9d25edc1890f0184bd57eb0e917c7799e63de" dependencies = [ "bstr", "gix-config-value", "gix-features", "gix-glob", "gix-path", "gix-ref", "gix-sec", "smallvec", "thiserror", "unicode-bom", ] [[package]] name = "gix-config-value" version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed42168329552f6c2e5df09665c104199d45d84bedb53683738a49b57fe1baab" dependencies = [ "bitflags", "bstr", "gix-path", "libc", "thiserror", ] [[package]] name = "gix-credentials" version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40cd22f0dd71988be12d6e78b1709de2370e1957c5f107ff31e56caeba3745d" dependencies = [ "bstr", "gix-command", "gix-config-value", "gix-date", "gix-path", "gix-prompt", "gix-sec", "gix-trace", "gix-url", "thiserror", ] [[package]] name = "gix-date" version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3ecab64a98bbac9f8e02990a9ea5e3c974a7d49b95f2bd70ad94ad22fa6b48c" dependencies = [ "bstr", "gix-error", "itoa", "jiff", ] [[package]] name = "gix-diff" version = "0.64.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b6d9528f32d94cef2edf39a1ac01fe5a0fc44ddbb18d9e44099936047c3302b" dependencies = [ "bstr", "gix-hash", "gix-object", "thiserror", ] [[package]] name = "gix-discover" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77bacdd12b7879d2178a80c58c2f319995e4654e1a7a23e3181e5c8a12b824f7" dependencies = [ "bstr", "dunce", "gix-fs", "gix-path", "gix-ref", "gix-sec", "thiserror", ] [[package]] name = "gix-error" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e57831e199be480af90dcd7e459abed8a174c09ec9a6e2cc8f7ca6c54598b06b" dependencies = [ "bstr", ] [[package]] name = "gix-features" version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1849ae154d38bc403185be14fa871e38e3c93ee606875d94e207fdb9fba52dbc" dependencies = [ "bytes", "crc32fast", "crossbeam-channel", "gix-path", "gix-trace", "gix-utils", "libc", "once_cell", "parking_lot", "prodash", "thiserror", "walkdir", "zlib-rs", ] [[package]] name = "gix-filter" version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecf74b7d16f6694ce4a3049074c41be0c7987105743674f1671807bd6dce09fa" dependencies = [ "bstr", "encoding_rs", "gix-attributes", "gix-command", "gix-hash", "gix-object", "gix-packetline", "gix-path", "gix-quote", "gix-trace", "gix-utils", "smallvec", "thiserror", ] [[package]] name = "gix-fs" version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6cdff46db8798e47e2f727d84b9379aac5add3dd3d9d0b07bb4d7d5d640771fe" dependencies = [ "bstr", "fastrand", "gix-features", "gix-path", "gix-utils", "thiserror", ] [[package]] name = "gix-glob" version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1fcb8ef5b16bcf874abe9b68d8abb3c0493c876d367ab824151f30a0f3f3756" dependencies = [ "bitflags", "bstr", "gix-features", "gix-path", ] [[package]] name = "gix-hash" version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0926d3819c837750b4e03c7754901e73f68b8c9b690753a6372a1bed4eedce" dependencies = [ "faster-hex", "gix-features", "sha1-checked", "thiserror", ] [[package]] name = "gix-hashtable" version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0e30b93eea8718baf7d8153fcb938e2926175bbf18097c09f1c01b6f0be0563" dependencies = [ "gix-hash", "hashbrown 0.17.1", "parking_lot", ] [[package]] name = "gix-ignore" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d491bab9bf2c9f341dc754f425c31d5d3f63aca615312167b82e1deeaca97d8d" dependencies = [ "bstr", "gix-glob", "gix-path", "gix-trace", "unicode-bom", ] [[package]] name = "gix-index" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e6b28cc592dc753adb58302bb14a64e412ee591a3bec77aa4df87bff74fa80d" dependencies = [ "bitflags", "bstr", "filetime", "fnv", "gix-bitmap", "gix-features", "gix-fs", "gix-hash", "gix-lock", "gix-object", "gix-traverse", "gix-utils", "gix-validate", "hashbrown 0.17.1", "itoa", "libc", "memmap2", "rustix", "smallvec", "thiserror", ] [[package]] name = "gix-lock" version = "23.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09b3bc074e5723027b482dcd9ab99d95804a53742f6de812d0172fbba4a186c1" dependencies = [ "gix-tempfile", "gix-utils", "thiserror", ] [[package]] name = "gix-negotiate" version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "890c936a215bae25818c076cb881cb2e54d2c66ba947ba58b8dd47cff921bf55" dependencies = [ "bitflags", "gix-commitgraph", "gix-date", "gix-hash", "gix-object", "gix-revwalk", ] [[package]] name = "gix-object" version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5cd857e29429c7213bdef3f5aef83f8cc124774fe8ae0d27b1607d218d6d525" dependencies = [ "bstr", "gix-actor", "gix-date", "gix-features", "gix-hash", "gix-hashtable", "gix-utils", "gix-validate", "itoa", "smallvec", "thiserror", ] [[package]] name = "gix-odb" version = "0.81.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d004c32858b1556f2d7874405edb3c97dc78fc09beaa87d57bb077ee2858a7d" dependencies = [ "arc-swap", "gix-features", "gix-fs", "gix-hash", "gix-hashtable", "gix-object", "gix-pack", "gix-path", "gix-quote", "memmap2", "parking_lot", "tempfile", "thiserror", ] [[package]] name = "gix-pack" version = "0.71.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e43626f2a27d1033674ec1a196b845614231e6bbd949d5e21c133045ff56b174" dependencies = [ "clru", "gix-chunk", "gix-error", "gix-features", "gix-hash", "gix-hashtable", "gix-object", "gix-path", "gix-tempfile", "memmap2", "parking_lot", "smallvec", "thiserror", "uluru", ] [[package]] name = "gix-packetline" version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb18337ba2830bb43367d1af43819c8c78f31337f079fc76d0f1f1750a173126" dependencies = [ "bstr", "faster-hex", "gix-trace", "thiserror", ] [[package]] name = "gix-path" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afa6ac14cd14939ea94a496ce7460daa6511c09f5b84757e9cfc6f9c8d0f93a6" dependencies = [ "bstr", "gix-trace", "gix-validate", "thiserror", ] [[package]] name = "gix-pathspec" version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3050783b41ee11511e1e8fb35623df81806194f4030395f14f48ea37c2798c9f" dependencies = [ "bitflags", "bstr", "gix-attributes", "gix-config-value", "gix-glob", "gix-path", "thiserror", ] [[package]] name = "gix-prompt" version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ee604d7746080ae7e1023bf47204bcc2c5f307bfbe2306a3c90b1bfd1a2c6d8" dependencies = [ "gix-command", "gix-config-value", "parking_lot", "rustix", "thiserror", ] [[package]] name = "gix-protocol" version = "0.62.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51dea3acb390707ab868f1f9584f18449eb95d869deffae96768e47d303595ee" dependencies = [ "bstr", "gix-credentials", "gix-date", "gix-features", "gix-hash", "gix-lock", "gix-negotiate", "gix-object", "gix-ref", "gix-refspec", "gix-revwalk", "gix-shallow", "gix-trace", "gix-transport", "gix-utils", "maybe-async", "nonempty", "thiserror", ] [[package]] name = "gix-quote" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6e541fc33cc2b783b7979040d445a0c86a2eca747c8faea4ca84230d06ae6ef" dependencies = [ "bstr", "gix-error", "gix-utils", ] [[package]] name = "gix-ref" version = "0.64.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c04f64c37eb7e6feb73c7060f8dc6f381cc5de5d53249bfd450bc48a86b2e8b" dependencies = [ "gix-actor", "gix-features", "gix-fs", "gix-hash", "gix-lock", "gix-object", "gix-path", "gix-tempfile", "gix-utils", "gix-validate", "memmap2", "thiserror", ] [[package]] name = "gix-refspec" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b216ae06ec74b5f24ad0142026a997fb0a935b7410eaf9c1616fc3f0e6c5a6d3" dependencies = [ "bstr", "gix-error", "gix-glob", "gix-hash", "gix-revision", "gix-validate", "smallvec", "thiserror", ] [[package]] name = "gix-revision" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b47c88884dd3c1a19a39da19d10211fcdea2809aadc86869b6e824a1774340f" dependencies = [ "bitflags", "bstr", "gix-commitgraph", "gix-date", "gix-error", "gix-hash", "gix-hashtable", "gix-object", "gix-revwalk", "gix-trace", "nonempty", ] [[package]] name = "gix-revwalk" version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85f5756abffe0917827aac683b13684ed99875bc398fa1f9b8f479b0681ef9e6" dependencies = [ "gix-commitgraph", "gix-date", "gix-error", "gix-hash", "gix-hashtable", "gix-object", "smallvec", "thiserror", ] [[package]] name = "gix-sec" version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab8519976e4c7e486270740a5400369f37940779b80bd1377d94cfa1125d01b3" dependencies = [ "bitflags", "gix-path", "libc", "windows-sys 0.61.2", ] [[package]] name = "gix-shallow" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a292fc2fe548c5dfa575479d16b445b0ddf1dd2f56f1fec6aed386f82553cd97" dependencies = [ "bstr", "gix-hash", "gix-lock", "nonempty", "thiserror", ] [[package]] name = "gix-submodule" version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3059890ef054066c22a94bfc6a3eaba0d806aedcd630a0bc9e5783fd88884781" dependencies = [ "bstr", "gix-config", "gix-path", "gix-pathspec", "gix-refspec", "gix-url", "thiserror", ] [[package]] name = "gix-tempfile" version = "23.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "691ea1e31435c7e7d4d04705ec9d1c0d9482c46b2acf512bc723939d8f0af7fb" dependencies = [ "gix-fs", "libc", "parking_lot", "tempfile", ] [[package]] name = "gix-trace" version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44dc45eae785c0eb14173e0f152e6e224dcf4d45b6a6999a3aed22af541ad678" [[package]] name = "gix-transport" version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd0e34995b1aab0fa8dff2af8db726a0bfad3e119c89302604463264046e7ff" dependencies = [ "base64", "bstr", "curl", "gix-command", "gix-credentials", "gix-features", "gix-packetline", "gix-quote", "gix-sec", "gix-url", "reqwest", "thiserror", ] [[package]] name = "gix-traverse" version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8de590ecc86a3b2870665f2288324fa9f7f8672c7fc2d4e020fdd81cd1f7aed" dependencies = [ "bitflags", "gix-commitgraph", "gix-date", "gix-hash", "gix-hashtable", "gix-object", "gix-revwalk", "smallvec", "thiserror", ] [[package]] name = "gix-url" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65bb01ec69d55e82ccb7a19e264501ead4e6aac38463a8cebfdd81e22bb67ab2" dependencies = [ "bstr", "gix-path", "percent-encoding", "thiserror", ] [[package]] name = "gix-utils" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66c50966184123caf580ffa64e28031a878597f1c7fceb8fe19566c38eb1b771" dependencies = [ "fastrand", "unicode-normalization", ] [[package]] name = "gix-validate" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bc6fc771c4063ba7cd2f47b91fb6076251c6a823b64b7fe7b8874b0fe4afae3" dependencies = [ "bstr", ] [[package]] name = "gix-worktree" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cef414ed275e8407cd5d53d301e83be19700b0dd3f859d2434417b58f454a2d1" dependencies = [ "bstr", "gix-attributes", "gix-fs", "gix-glob", "gix-hash", "gix-ignore", "gix-index", "gix-object", "gix-path", "gix-validate", ] [[package]] name = "gix-worktree-state" version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bffae8b3ca258fdd50370cd51f06deb4c76a3b43db3868bc28dde45ffa77d69" dependencies = [ "bstr", "gix-features", "gix-filter", "gix-fs", "gix-index", "gix-object", "gix-path", "gix-worktree", "io-close", "thiserror", ] [[package]] name = "gix-worktree-stream" version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d25e9ed30100c63f7590bc581c225e53f731a53e06aa79a245739c07f7dcc557" dependencies = [ "gix-attributes", "gix-error", "gix-features", "gix-filter", "gix-fs", "gix-hash", "gix-object", "gix-path", "gix-traverse", "parking_lot", ] [[package]] name = "h2" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", "http", "indexmap", "slab", "tokio", "tokio-util", "tracing", ] [[package]] name = "hash32" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" dependencies = [ "byteorder", ] [[package]] name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "foldhash 0.1.5", ] [[package]] name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", "foldhash 0.2.0", ] [[package]] name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" dependencies = [ "allocator-api2", "equivalent", "foldhash 0.2.0", ] [[package]] name = "heapless" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" dependencies = [ "hash32", "stable_deref_trait", ] [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "home" version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" dependencies = [ "windows-sys 0.61.2", ] [[package]] name = "http" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", "itoa", ] [[package]] name = "http-body" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", ] [[package]] name = "http-body-util" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" 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.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", "h2", "http", "http-body", "httparse", "itoa", "pin-project-lite", "smallvec", "tokio", "want", ] [[package]] name = "hyper-rustls" version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http", "hyper", "hyper-util", "rustls", "tokio", "tokio-rustls", "tower-service", ] [[package]] name = "hyper-util" version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ "base64", "bytes", "futures-channel", "futures-util", "http", "http-body", "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", "socket2", "tokio", "tower-service", "tracing", ] [[package]] name = "icu_collections" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" dependencies = [ "displaydoc", "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_locale_core" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_normalizer" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", "zerovec", ] [[package]] name = "icu_normalizer_data" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", "icu_locale_core", "writeable", "yoke", "zerofrom", "zerotrie", "zerovec", ] [[package]] name = "id-arena" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" [[package]] name = "idna" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", "utf8_iter", ] [[package]] name = "idna_adapter" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", ] [[package]] name = "indexmap" version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown 0.17.1", "serde", "serde_core", ] [[package]] name = "io-close" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cadcf447f06744f8ce713d2d6239bb5bde2c357a452397a9ed90c625da390bc" dependencies = [ "libc", "winapi", ] [[package]] name = "ipnet" version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4603d3033e49e2b0e31229fcab20a5d40089c607d975cd9c80551dc69eed9102" dependencies = [ "jiff-static", "jiff-tzdb-platform", "log", "portable-atomic", "portable-atomic-util", "serde_core", "windows-link", ] [[package]] name = "jiff-static" version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "jiff-tzdb" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c900ef84826f1338a557697dc8fc601df9ca9af4ac137c7fb61d4c6f2dfd3076" [[package]] name = "jiff-tzdb-platform" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" dependencies = [ "jiff-tzdb", ] [[package]] name = "jni" version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" dependencies = [ "cfg-if", "combine", "jni-macros", "jni-sys", "log", "simd_cesu8", "thiserror", "walkdir", "windows-link", ] [[package]] name = "jni-macros" version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" dependencies = [ "proc-macro2", "quote", "rustc_version", "simd_cesu8", "syn", ] [[package]] name = "jni-sys" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" dependencies = [ "jni-sys-macros", ] [[package]] name = "jni-sys-macros" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", "syn", ] [[package]] name = "jobserver" version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ "getrandom 0.3.4", "libc", ] [[package]] name = "js-sys" version = "0.3.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" dependencies = [ "cfg-if", "futures-util", "once_cell", "wasm-bindgen", ] [[package]] name = "kstring" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "558bf9508a558512042d3095138b1f7b8fe90c5467d94f9f1da28b3731c5dbd1" dependencies = [ "static_assertions", ] [[package]] name = "leb128fmt" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libz-sys" version = "1.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc3a226e576f50782b3305c5ccf458698f92798987f551c6a02efe8276721e22" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "linux-raw-sys" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "lock_api" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ "scopeguard", ] [[package]] name = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lru-slab" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "maybe-async" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "memchr" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memmap2" version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" dependencies = [ "libc", ] [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", "simd-adler32", ] [[package]] name = "mio" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "wasi", "windows-sys 0.61.2", ] [[package]] name = "nonempty" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9737e026353e5cd0736f98eddae28665118eb6f6600902a7f50db585621fecb6" [[package]] name = "num-conv" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "openssl-probe" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "parking_lot" version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-link", ] [[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "petgraph" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset", "hashbrown 0.15.5", "indexmap", "serde", ] [[package]] name = "pin-project-lite" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkg-config" version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "platforms" version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6001d2ac55b4eb1ca634c65fc06555068b8dd89c9f20fd92064e5341a436e63" dependencies = [ "serde", ] [[package]] name = "portable-atomic" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" dependencies = [ "portable-atomic", ] [[package]] name = "potential_utf" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "prettyplease" version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", "syn", ] [[package]] name = "proc-macro2" version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "prodash" version = "31.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "962200e2d7d551451297d9fdce85138374019ada198e30ea9ede38034e27604c" dependencies = [ "parking_lot", ] [[package]] name = "quinn" version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ "bytes", "cfg_aliases", "pin-project-lite", "quinn-proto", "quinn-udp", "rustc-hash", "rustls", "socket2", "thiserror", "tokio", "tracing", "web-time", ] [[package]] name = "quinn-proto" version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ "aws-lc-rs", "bytes", "getrandom 0.3.4", "lru-slab", "rand", "ring", "rustc-hash", "rustls", "rustls-pki-types", "slab", "thiserror", "tinyvec", "tracing", "web-time", ] [[package]] name = "quinn-udp" version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" dependencies = [ "cfg_aliases", "libc", "once_cell", "socket2", "tracing", "windows-sys 0.60.2", ] [[package]] name = "quitters" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88ccde7d84d2115b250b5cba923973fc42fe23ad42d9d63bb129d956a6db014" dependencies = [ "once_cell", "regex", "semver", ] [[package]] name = "quote" version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[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.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] [[package]] name = "rayon" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] [[package]] name = "regex" version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" dependencies = [ "base64", "bytes", "encoding_rs", "futures-channel", "futures-core", "futures-util", "h2", "http", "http-body", "http-body-util", "hyper", "hyper-rustls", "hyper-util", "js-sys", "log", "mime", "percent-encoding", "pin-project-lite", "quinn", "rustls", "rustls-pki-types", "rustls-platform-verifier", "sync_wrapper", "tokio", "tokio-rustls", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", ] [[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.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc-stable-hash" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "781442f29170c5c93b7185ad559492601acdc71d5bb0706f5868094f45cfcd08" [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustix" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.61.2", ] [[package]] name = "rustls" version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ "aws-lc-rs", "once_cell", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls-native-certs" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", "security-framework", ] [[package]] name = "rustls-pki-types" version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ "web-time", "zeroize", ] [[package]] name = "rustls-platform-verifier" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ "core-foundation", "core-foundation-sys", "jni", "log", "once_cell", "rustls", "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki", "security-framework", "security-framework-sys", "webpki-root-certs", "windows-sys 0.61.2", ] [[package]] name = "rustls-platform-verifier-android" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", ] [[package]] name = "rustsec" version = "0.33.0" dependencies = [ "auditable-info", "auditable-serde", "binfarce", "cargo-lock", "cvss", "fs-err", "gix", "home", "jiff", "once_cell", "platforms", "quitters", "semver", "serde", "serde_json", "tame-index", "tempfile", "thiserror", "time", "toml 1.1.2+spec-1.1.0", "url", ] [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "schannel" version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ "windows-sys 0.61.2", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags", "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", ] [[package]] name = "security-framework-sys" version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "semver" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" dependencies = [ "serde", "serde_core", ] [[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", ] [[package]] name = "serde_core" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", "serde", "serde_core", "zmij", ] [[package]] name = "serde_spanned" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] [[package]] name = "sha1" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "sha1-checked" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423" dependencies = [ "digest", "sha1", ] [[package]] name = "shell-words" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "simd-adler32" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "simd_cesu8" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" dependencies = [ "rustc_version", "simdutf8", ] [[package]] name = "simdutf8" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "slab" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "smol_str" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9676b89cd56310a87b93dec47b11af744f34d5fc9f367b829474eec0a891350d" dependencies = [ "borsh", "serde", ] [[package]] name = "socket2" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", "windows-sys 0.61.2", ] [[package]] name = "stable_deref_trait" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[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.117" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" 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.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tame-index" version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c51563c6639245219c077420e0a207e99b5e82e2e6bb78fd4898d42028ea7dde" dependencies = [ "camino", "crossbeam-channel", "http", "libc", "memchr", "rayon", "reqwest", "rustc-stable-hash", "semver", "serde", "serde_json", "smol_str", "thiserror", "tokio", "toml-span", "twox-hash", ] [[package]] name = "tempfile" version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", "getrandom 0.4.2", "once_cell", "rustix", "windows-sys 0.61.2", ] [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "time" version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", ] [[package]] name = "tinystr" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", ] [[package]] name = "tinyvec" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", "mio", "pin-project-lite", "socket2", "windows-sys 0.61.2", ] [[package]] name = "tokio-rustls" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ "rustls", "tokio", ] [[package]] name = "tokio-util" version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", ] [[package]] name = "toml" version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ "indexmap", "serde_core", "serde_spanned", "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", "winnow 0.7.15", ] [[package]] name = "toml" version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" dependencies = [ "indexmap", "serde_core", "serde_spanned", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", "toml_writer", "winnow 1.0.2", ] [[package]] name = "toml-span" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f22ba417d437b5fa5dcba6c27dbd6c14f38845315b724d89fed73b7a426451b7" dependencies = [ "smallvec", ] [[package]] name = "toml_datetime" version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] [[package]] name = "toml_datetime" version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] [[package]] name = "toml_parser" version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ "winnow 1.0.2", ] [[package]] name = "toml_writer" version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "topological-sort" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea68304e134ecd095ac6c3574494fc62b909f416c4fca77e440530221e549d3d" [[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.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51" dependencies = [ "async-compression", "bitflags", "bytes", "futures-core", "futures-util", "http", "http-body", "http-body-util", "pin-project-lite", "tokio", "tokio-util", "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 = "twox-hash" version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" [[package]] name = "typenum" version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "uluru" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c8a2469e56e6e5095c82ccd3afb98dad95f7af7929aab6d8ba8d6e0f73657da" dependencies = [ "arrayvec", ] [[package]] name = "unicode-bom" version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-normalization" version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" dependencies = [ "tinyvec", ] [[package]] name = "unicode-xid" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", "serde_derive", ] [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "want" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 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 = "wasip2" version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ "wit-bindgen 0.57.1", ] [[package]] name = "wasip3" 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 0.51.0", ] [[package]] name = "wasm-bindgen" version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" dependencies = [ "bumpalo", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-encoder" version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" dependencies = [ "leb128fmt", "wasmparser 0.244.0", ] [[package]] name = "wasm-metadata" version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", "indexmap", "wasm-encoder", "wasmparser 0.244.0", ] [[package]] name = "wasmparser" version = "0.207.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e19bb9f8ab07616da582ef8adb24c54f1424c7ec876720b7da9db8ec0626c92c" dependencies = [ "bitflags", ] [[package]] name = "wasmparser" version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ "bitflags", "hashbrown 0.15.5", "indexmap", "semver", ] [[package]] name = "web-sys" version = "0.3.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" 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-root-certs" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" dependencies = [ "rustls-pki-types", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ "windows-sys 0.61.2", ] [[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-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 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 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "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.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.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.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.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.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.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.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.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" [[package]] name = "winnow" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" [[package]] name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" dependencies = [ "anyhow", "heck", "wit-parser", ] [[package]] name = "wit-bindgen-rust" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck", "indexmap", "prettyplease", "syn", "wasm-metadata", "wit-bindgen-core", "wit-component", ] [[package]] name = "wit-bindgen-rust-macro" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" dependencies = [ "anyhow", "prettyplease", "proc-macro2", "quote", "syn", "wit-bindgen-core", "wit-bindgen-rust", ] [[package]] name = "wit-component" version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", "bitflags", "indexmap", "log", "serde", "serde_derive", "serde_json", "wasm-encoder", "wasm-metadata", "wasmparser 0.244.0", "wit-parser", ] [[package]] name = "wit-parser" version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", "indexmap", "log", "semver", "serde", "serde_derive", "serde_json", "unicode-xid", "wasmparser 0.244.0", ] [[package]] name = "writeable" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "yoke" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zerocopy" version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "zerofrom" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", "zerofrom", ] [[package]] name = "zerovec" version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "zlib-rs" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" [[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" rustsec-0.33.0/Cargo.toml0000644000000073311046102023000106330ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2024" rust-version = "1.88" name = "rustsec" version = "0.33.0" authors = ["Tony Arcieri "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Client library for the RustSec security advisory database" homepage = "https://rustsec.org" readme = "README.md" keywords = [ "audit", "rustsec", "security", "advisory", "vulnerability", ] categories = [ "api-bindings", "development-tools", ] license = "Apache-2.0 OR MIT" repository = "https://github.com/rustsec/rustsec" resolver = "2" [package.metadata.docs.rs] features = [ "dependency-tree", "osv-export", "binary-scanning", ] rustdoc-args = [ "--cfg", "docsrs", ] [package.metadata.cargo_check_external_types] allowed_external_types = [ "binfarce::Format", "cargo_lock", "cargo_lock::*", "cvss::*", "platforms", "platforms::*", "semver", "semver::*", "serde::*", "time::offset_date_time::OffsetDateTime", "url::Url", ] [features] binary-scanning = [ "dep:auditable-info", "dep:auditable-serde", "dep:binfarce", "dep:quitters", "dep:once_cell", ] default = ["gix-reqwest"] dependency-tree = ["cargo-lock/dependency-tree"] git = [ "dep:tame-index", "dep:home", "dep:time", "dep:gix", ] gix-curl = [ "gix/blocking-http-transport-curl", "git", ] gix-reqwest = [ "gix/blocking-http-transport-reqwest-rust-tls", "git", ] osv-export = ["git"] [lib] name = "rustsec" path = "src/lib.rs" [[test]] name = "advisories" path = "tests/advisories.rs" [[test]] name = "database" path = "tests/database.rs" [[test]] name = "integration" path = "tests/integration.rs" [[test]] name = "linter" path = "tests/linter.rs" [[test]] name = "query" path = "tests/query.rs" [dependencies.auditable-info] version = "0.10" features = ["wasm"] optional = true [dependencies.auditable-serde] version = "0.9" optional = true [dependencies.binfarce] version = "0.2" optional = true [dependencies.cargo-lock] version = "11" [dependencies.cvss] version = "2.2" features = ["serde"] [dependencies.fs-err] version = "3" [dependencies.gix] version = "0.84" features = [ "sha1", "worktree-mutation", "revision", "max-performance-safe", ] optional = true default-features = false [dependencies.home] version = "0.5" optional = true [dependencies.jiff] version = "0.2" features = ["std"] default-features = false [dependencies.once_cell] version = "1.15.0" optional = true [dependencies.platforms] version = "3" features = ["serde"] [dependencies.quitters] version = "0.1.0" optional = true [dependencies.semver] version = "1.0.23" features = ["serde"] [dependencies.serde] version = "1" features = ["serde_derive"] [dependencies.tame-index] version = "0.26" features = ["sparse"] optional = true default-features = false [dependencies.thiserror] version = "2" [dependencies.time] version = "0.3" features = [ "formatting", "serde", "parsing", ] optional = true default-features = false [dependencies.toml] version = "1.0" [dependencies.url] version = "2" features = ["serde"] [dev-dependencies.once_cell] version = "1.15.0" [dev-dependencies.serde_json] version = "1" [dev-dependencies.tempfile] version = "3" rustsec-0.33.0/Cargo.toml.orig000064400000000000000000000053121046102023000142670ustar 00000000000000[package] name = "rustsec" description = "Client library for the RustSec security advisory database" version = "0.33.0" authors = ["Tony Arcieri "] license = "Apache-2.0 OR MIT" homepage = "https://rustsec.org" repository = "https://github.com/rustsec/rustsec" readme = "README.md" categories = ["api-bindings", "development-tools"] keywords = ["audit", "rustsec", "security", "advisory", "vulnerability"] edition = "2024" rust-version = "1.88" [dependencies] cargo-lock = { workspace = true } cvss = { workspace = true, features = ["serde"] } fs-err = { workspace = true } platforms = { workspace = true, features = ["serde"] } semver = { workspace = true, features = ["serde"] } serde = { workspace = true, features = ["serde_derive"] } thiserror = { workspace = true } toml = { workspace = true } url = { workspace = true, features = ["serde"] } jiff = { workspace = true, features = ["std"] } # for scanning binary files auditable-info = { workspace = true, features = ["wasm"], optional = true } auditable-serde = { workspace = true, optional = true } quitters = { workspace = true, optional = true } once_cell = { workspace = true, optional = true } binfarce = { workspace = true, optional = true } # optional dependencies tame-index = { workspace = true, features = ["sparse"], optional = true } home = { workspace = true, optional = true } time = { workspace = true, features = ["formatting", "serde", "parsing"], optional = true } gix = { workspace = true, features = ["worktree-mutation", "revision", "max-performance-safe"], optional = true } [dev-dependencies] tempfile = { workspace = true } once_cell = { workspace = true } serde_json = { workspace = true } [features] default = ["gix-reqwest"] git = [ "dep:tame-index", "dep:home", "dep:time", "dep:gix", ] # Exactly one of 'gix-reqwest' or 'gix-curl' must be enabled when using the 'git' feature. gix-reqwest = ["gix/blocking-http-transport-reqwest-rust-tls", "git"] gix-curl = ["gix/blocking-http-transport-curl", "git"] dependency-tree = ["cargo-lock/dependency-tree"] osv-export = ["git"] binary-scanning = ["dep:auditable-info", "dep:auditable-serde", "dep:binfarce", "dep:quitters", "dep:once_cell"] [package.metadata.docs.rs] # All features except gix-curl, which is mutually exclusive with gix-reqwest features = ["dependency-tree", "osv-export", "binary-scanning"] rustdoc-args = ["--cfg", "docsrs"] [package.metadata.cargo_check_external_types] allowed_external_types = [ "binfarce::Format", "cargo_lock", "cargo_lock::*", "cvss::*", "platforms", "platforms::*", "semver", "semver::*", "serde::*", "time::offset_date_time::OffsetDateTime", "url::Url", ] rustsec-0.33.0/LICENSE-APACHE000064400000000000000000000251371046102023000133330ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. rustsec-0.33.0/LICENSE-MIT000064400000000000000000000020641046102023000130350ustar 00000000000000Copyright (c) 2017-2021 The Rust Project Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. rustsec-0.33.0/README.md000064400000000000000000000050361046102023000126620ustar 00000000000000# RustSec: `rustsec` crate [![Latest Version][crate-image]][crate-link] [![Docs][docs-image]][docs-link] [![Build Status][build-image]][build-link] [![Safety Dance][safety-image]][safety-link] ![MSRV][rustc-image] ![Apache 2.0 OR MIT licensed][license-image] [![Project Chat][chat-image]][chat-link] Client library for accessing the [RustSec Security Advisory Database]: fetches the [advisory-db] (or other compatible) git repository and audits `Cargo.lock` files against it. [Documentation] ## About The `rustsec` crate is primarily intended to be used by the [cargo-audit] crate for the purposes of identifying vulnerable crates in Cargo.lock files. However, it may be useful if you would like to consume the RustSec advisory database in other capacities. ## Minimum Supported Rust Version Rust **1.85** or higher. Minimum supported Rust version can be changed in the future, but it will be done with a minor version bump. ## License Licensed under either of: - Apache License, Version 2.0 ([LICENSE-APACHE] or ) - MIT license ([LICENSE-MIT] or ) at your option. ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you shall be dual licensed as above, without any additional terms or conditions. [//]: # (badges) [crate-image]: https://img.shields.io/crates/v/rustsec.svg?logo=rust [crate-link]: https://crates.io/crates/rustsec [docs-image]: https://docs.rs/rustsec/badge.svg [docs-link]: https://docs.rs/rustsec/ [build-image]: https://github.com/RustSec/rustsec/actions/workflows/rustsec.yml/badge.svg [build-link]: https://github.com/RustSec/rustsec/actions/workflows/rustsec.yml [safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg [safety-link]: https://github.com/rust-secure-code/safety-dance/ [rustc-image]: https://img.shields.io/badge/rustc-1.73+-blue.svg [license-image]: https://img.shields.io/badge/license-Apache2.0%2FMIT-blue.svg [chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg [chat-link]: https://rust-lang.zulipchat.com/#narrow/stream/146229-wg-secure-code/ [//]: # (general links) [RustSec Security Advisory Database]: https://rustsec.org/ [advisory-db]: https://github.com/RustSec/advisory-db [Documentation]: https://docs.rs/rustsec/ [cargo-audit]: https://github.com/rustsec/cargo-audit [LICENSE-APACHE]: https://github.com/RustSec/rustsec-crate/blob/main/LICENSE-APACHE [LICENSE-MIT]: https://github.com/RustSec/rustsec-crate/blob/main/LICENSE-MIT rustsec-0.33.0/src/advisory/affected.rs000064400000000000000000000152541046102023000161440ustar 00000000000000//! The `[affected]` subsection of an advisory: metadata specifying the scope //! of impacted systems/functions/usages. use crate::{ Map, error::{Error, ErrorKind}, }; use platforms::target::{Arch, OS}; use semver::VersionReq; use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as DeError}; use std::{ fmt::{self, Display}, slice, str::FromStr, }; /// The `[affected]` subsection of an advisory: additional metadata detailing /// the specifics of what is impacted by this advisory (e.g. operating systems, /// what functions in the crate) #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct Affected { /// CPU architectures that this vulnerability is specific to #[serde(default)] pub arch: Vec, /// Operating systems that this vulnerability is specific to #[serde(default)] pub os: Vec, /// Paths to types and/or functions containing vulnerable code, enumerated /// as canonical Rust paths (i.e. starting with the crate name), sans any /// path parameters. /// /// (e.g. `mycrate::path::to::VulnerableStruct::vulnerable_func`) #[serde(default)] pub functions: Map>, } /// Canonical Rust Paths (sans parameters) to vulnerable types and/or functions /// affected by a particular advisory. /// #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] pub struct FunctionPath(Vec); impl FunctionPath { /// Get the crate name for this path pub fn crate_name(&self) -> &str { self.iter() .next() .expect("path must have 2 or more segments") .as_str() } /// Convert this path into an owned vector of `Identifier`s pub fn into_vec(self) -> Vec { self.0 } /// Iterate over the segments of this path pub fn iter(&self) -> slice::Iter<'_, Identifier> { self.0.iter() } /// Borrow the segments of this path pub fn segments(&self) -> &[Identifier] { self.0.as_slice() } } impl Display for FunctionPath { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut segments = self.iter(); let crate_name = segments.next().expect("path must have 2 or more segments"); write!(f, "{}", crate_name.as_str())?; for segment in segments { write!(f, "::{}", segment.as_str())?; } Ok(()) } } impl FromStr for FunctionPath { type Err = Error; /// Parse a canonical, parameter-free path contained in an advisory fn from_str(path: &str) -> Result { let mut segments = vec![]; for segment in path.split("::") { segments.push(segment.parse()?); } if segments.len() >= 2 { Ok(FunctionPath(segments)) } else { fail!( ErrorKind::Parse, "paths must start with the crate name (i.e. minimum two segments): '{}'", path ) } } } impl<'de> Deserialize<'de> for FunctionPath { fn deserialize>(deserializer: D) -> Result { let string = String::deserialize(deserializer)?; string.parse().map_err(|e| D::Error::custom(format!("{e}"))) } } impl Serialize for FunctionPath { fn serialize(&self, serializer: S) -> Result { serializer.serialize_str(&self.to_string()) } } /// Identifiers within paths. Note that the typical Rust path grammar supports /// multiple types of path segments, however for the purposes of vulnerability /// advisories we only care about identifiers. /// #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] pub struct Identifier(String); impl Identifier { /// Borrow this identifier as a `str` pub fn as_str(&self) -> &str { &self.0 } } impl AsRef for Identifier { fn as_ref(&self) -> &str { self.as_str() } } impl FromStr for Identifier { type Err = Error; /// Parse an `Identifier` within a `path` fn from_str(identifier: &str) -> Result { validate_identifier(identifier)?; Ok(Identifier(identifier.into())) } } /// Validate an identifier within a path is valid fn validate_identifier(identifier: &str) -> Result<(), Error> { let mut chars = identifier.chars(); if let Some(first_char) = chars.next() { match first_char { 'A'..='Z' | 'a'..='z' | '_' | '<' => (), _ => fail!( ErrorKind::Parse, "invalid character at start of ident: '{}'", identifier ), } } else { fail!(ErrorKind::Parse, "empty identifier in affected path"); } for c in chars { match c { 'A'..='Z' | 'a'..='z' | '0'..='9' | '_' | '<' | '>' | ',' => (), '(' | ')' => fail!( ErrorKind::Parse, "omit parameters when specifying affected paths: '{}'", identifier ), _ => fail!( ErrorKind::Parse, "invalid character {c:?} in identifier: '{}'", identifier ), } } Ok(()) } #[cfg(test)] mod tests { use super::FunctionPath; use std::str::FromStr; const EXAMPLE_PATH_STR: &str = "foo::bar::baz"; #[test] fn crate_name_test() { let path = FunctionPath::from_str(EXAMPLE_PATH_STR).unwrap(); assert_eq!(path.crate_name(), "foo"); } #[test] fn display_test() { let path = FunctionPath::from_str(EXAMPLE_PATH_STR).unwrap(); assert_eq!(path.to_string(), EXAMPLE_PATH_STR) } #[test] fn from_str_test() { // Valid paths assert!(FunctionPath::from_str("foo::bar").is_ok()); assert!(FunctionPath::from_str("foo::bar::baz").is_ok()); assert!(FunctionPath::from_str("foo::Bar::baz").is_ok()); assert!(FunctionPath::from_str("foo::::baz").is_ok()); assert!(FunctionPath::from_str("foo::::baz").is_ok()); assert!(FunctionPath::from_str("foo::Bar::quux").is_ok()); assert!(FunctionPath::from_str("foo::Bar::_baz").is_ok()); assert!(FunctionPath::from_str("foo::Bar::_baz_").is_ok()); assert!(FunctionPath::from_str("f00::B4r::_b4z_").is_ok()); // Invalid paths assert!(FunctionPath::from_str("minimum_two_components").is_err()); assert!(FunctionPath::from_str("no-hyphens::foobar").is_err()); assert!(FunctionPath::from_str("no_leading_digits::0rly").is_err()); } } rustsec-0.33.0/src/advisory/category.rs000064400000000000000000000104771046102023000162220ustar 00000000000000//! RustSec Vulnerability Categories use crate::error::Error; use serde::{Deserialize, Serialize, de, ser}; use std::{fmt, str::FromStr}; /// RustSec Vulnerability Categories /// /// The RustSec project maintains its own categorization system for /// vulnerabilities according to our [criteria for acceptable advisories][1]. /// /// This type represents the present list of allowable vulnerability types for /// which we allow advisories to be filed. /// /// [1]: https://github.com/RustSec/advisory-db/blob/main/CONTRIBUTING.md#criteria #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] #[non_exhaustive] pub enum Category { /// Execution of arbitrary code allowing an attacker to gain partial or /// total control of an impacted computer system. CodeExecution, /// Cryptography Failure (e.g. confidentiality breakage, integrity /// breakage, key leakage) CryptoFailure, /// Vulnerabilities an attacker can leverage to cause crashes or excess /// resource consumption such that software ceases to function normally, /// notably panics in code that is advertised as "panic-free" (particularly /// in format parsers for untrusted data) DenialOfService, /// Disclosure of local files (a.k.a. "directory traversal") FileDisclosure, /// Mishandled escaping allowing an attacker to execute code or perform /// otherwise unexpected operations, e.g. shell escaping, SQL injection, XSS. FormatInjection, /// Malicious code or other malware (e.g. credential exfiltration, /// cryptocurrency miners, et al). Malicious, /// Memory unsafety vulnerabilities allowing an attacker to write to /// unintended locations in memory. MemoryCorruption, /// Read-only memory safety vulnerabilities which unintentionally expose data. MemoryExposure, /// Attacks which bypass authentication and/or authorization systems, /// allowing the attacker to obtain unintended privileges. PrivilegeEscalation, /// Thread safety bug, e.g. data races arising from unsafe code that /// misapplies and/or misuses `Send`/`Sync`. ThreadSafety, /// Other types of categories: left open-ended to add more of them in the future. Other(String), } impl Category { /// Get the short "kebab case" identifier for a category pub fn name(&self) -> &str { match self { Category::CodeExecution => "code-execution", Category::CryptoFailure => "crypto-failure", Category::DenialOfService => "denial-of-service", Category::FileDisclosure => "file-disclosure", Category::FormatInjection => "format-injection", Category::Malicious => "malicious", Category::MemoryCorruption => "memory-corruption", Category::MemoryExposure => "memory-exposure", Category::PrivilegeEscalation => "privilege-escalation", Category::ThreadSafety => "thread-safety", Category::Other(other) => other, } } } impl fmt::Display for Category { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.name()) } } impl FromStr for Category { type Err = Error; fn from_str(s: &str) -> Result { Ok(match s { "code-execution" => Category::CodeExecution, "crypto-failure" => Category::CryptoFailure, "denial-of-service" => Category::DenialOfService, "file-disclosure" => Category::FileDisclosure, "format-injection" => Category::FormatInjection, "malicious" => Category::Malicious, "memory-corruption" => Category::MemoryCorruption, "memory-exposure" => Category::MemoryExposure, "privilege-escalation" => Category::PrivilegeEscalation, "thread-safety" => Category::ThreadSafety, other => Category::Other(other.to_owned()), }) } } impl<'de> Deserialize<'de> for Category { fn deserialize>(deserializer: D) -> Result { use de::Error; let string = String::deserialize(deserializer)?; string.parse().map_err(D::Error::custom) } } impl Serialize for Category { fn serialize(&self, serializer: S) -> Result { self.to_string().serialize(serializer) } } rustsec-0.33.0/src/advisory/date.rs000064400000000000000000000073521046102023000153200ustar 00000000000000//! Advisory dates use crate::error::{Error, ErrorKind}; use jiff::civil::Date as CivilDate; use serde::{Deserialize, Serialize, de}; use std::{ fmt::{self, Display}, str::FromStr, }; /// Minimum allowed year on advisory dates pub(crate) const YEAR_MIN: u32 = 2000; /// Maximum allowed year on advisory dates pub(crate) const YEAR_MAX: u32 = YEAR_MIN + 100; /// Dates on advisories (RFC 3339) #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize)] pub struct Date(String); impl Date { /// Get the year for this date pub fn year(&self) -> u32 { self.component(0).expect("has year") } /// Get the month for this date pub fn month(&self) -> u32 { self.component(1).expect("has month") } /// Get the day of the month for this date pub fn day(&self) -> u32 { self.component(2).expect("has day") } /// Borrow this date as a string reference pub fn as_str(&self) -> &str { self.0.as_ref() } /// Get a specific component of the date by numerical offset fn component(&self, index: usize) -> Option { self.0 .split('-') .nth(index) .map(|cmp| cmp.parse().expect("numerical date components")) } } impl AsRef for Date { fn as_ref(&self) -> &str { self.as_str() } } impl Display for Date { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.as_str()) } } impl FromStr for Date { type Err = Error; /// Create a `Date` from the given RFC 3339 date string fn from_str(rfc3339_date: &str) -> Result { let date = CivilDate::from_str(rfc3339_date) .map_err(|_| Error::new(ErrorKind::Parse, format!("invalid date: {rfc3339_date}")))?; if !(YEAR_MIN..=YEAR_MAX).contains(&(date.year() as u32)) { fail!( ErrorKind::Parse, "year must be between {} and {}: {}", YEAR_MIN, YEAR_MAX, rfc3339_date ); } Ok(Date(rfc3339_date.into())) } } impl<'de> Deserialize<'de> for Date { fn deserialize>(deserializer: D) -> Result { use de::Error; String::deserialize(deserializer)? .parse() .map_err(D::Error::custom) } } #[cfg(test)] mod tests { use super::Date; use std::str::FromStr; #[test] fn from_str_test() { // Valid dates assert!(Date::from_str("2000-01-01").is_ok()); assert!(Date::from_str("2017-01-01").is_ok()); assert!(Date::from_str("2099-12-31").is_ok()); // Invalid dates assert!(Date::from_str("derp").is_err()); assert!(Date::from_str("1999-12-31").is_err()); assert!(Date::from_str("02017-01-01").is_err()); assert!(Date::from_str("2017-00-01").is_err()); assert!(Date::from_str("2017-01-00").is_err()); assert!(Date::from_str("2017-13-01").is_err()); assert!(Date::from_str("2017-01-32").is_err()); assert!(Date::from_str("2017-01-").is_err()); assert!(Date::from_str("2017-01-01-01").is_err()); assert!(Date::from_str("2017-02-29").is_err()); assert!(Date::from_str("2017-04-31").is_err()); assert!(Date::from_str("2017-02-30").is_err()); // Leap year February 29th assert!(Date::from_str("2000-02-29").is_ok()); assert!(Date::from_str("2004-02-29").is_ok()); assert!(Date::from_str("2100-02-29").is_err()); } #[test] fn date_components_test() { let date = Date::from_str("2000-01-02").unwrap(); assert_eq!(date.year(), 2000); assert_eq!(date.month(), 1); assert_eq!(date.day(), 2); } } rustsec-0.33.0/src/advisory/id.rs000064400000000000000000000215251046102023000147750ustar 00000000000000//! Advisory identifiers use super::date::{YEAR_MAX, YEAR_MIN}; use crate::error::{Error, ErrorKind}; use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as DeError}; use std::{ fmt::{self, Display}, str::FromStr, }; /// An identifier for an individual advisory #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] pub struct Id { /// An autodetected identifier kind kind: IdKind, /// Year this vulnerability was published year: Option, /// The actual string representing the identifier string: String, } impl Id { /// Placeholder advisory name: shouldn't be used until an ID is assigned pub const PLACEHOLDER: &'static str = "RUSTSEC-0000-0000"; /// Get a string reference to this advisory ID pub fn as_str(&self) -> &str { self.string.as_ref() } /// Get the advisory kind for this advisory pub fn kind(&self) -> IdKind { self.kind } /// Is this advisory ID the `RUSTSEC-0000-0000` placeholder ID? pub fn is_placeholder(&self) -> bool { self.string == Self::PLACEHOLDER } /// Is this advisory ID a RUSTSEC advisory? pub fn is_rustsec(&self) -> bool { self.kind == IdKind::RustSec } /// Is this advisory ID a CVE? pub fn is_cve(&self) -> bool { self.kind == IdKind::Cve } /// Is this advisory ID a GHSA? pub fn is_ghsa(&self) -> bool { self.kind == IdKind::Ghsa } /// Is this advisory ID a TALOS advisory? pub fn is_talos(&self) -> bool { self.kind == IdKind::Talos } /// Is this an unknown kind of advisory ID? pub fn is_other(&self) -> bool { self.kind == IdKind::Other } /// Get the year this vulnerability was published (if known) pub fn year(&self) -> Option { self.year } /// Get the numerical part of this advisory (if available). /// /// This corresponds to the numbers on the right side of the ID. pub fn numerical_part(&self) -> Option { if self.is_placeholder() { return None; } self.string .split('-') .next_back() .and_then(|s| str::parse(s).ok()) } /// Get a URL to a web page with more information on this advisory // TODO(tarcieri): look up GHSA URLs via the GraphQL API? // pub fn url(&self) -> Option { match self.kind { IdKind::RustSec => { if self.is_placeholder() { None } else { Some(format!("https://rustsec.org/advisories/{}", &self.string)) } } IdKind::Cve => Some(format!( "https://cve.mitre.org/cgi-bin/cvename.cgi?name={}", &self.string )), IdKind::Ghsa => Some(format!("https://github.com/advisories/{}", &self.string)), IdKind::Talos => Some(format!( "https://www.talosintelligence.com/reports/{}", &self.string )), _ => None, } } } impl AsRef for Id { fn as_ref(&self) -> &str { self.as_str() } } impl Default for Id { fn default() -> Id { Id { kind: IdKind::RustSec, year: None, string: Id::PLACEHOLDER.into(), } } } impl Display for Id { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.as_str()) } } impl FromStr for Id { type Err = Error; /// Create an `Id` from the given string fn from_str(advisory_id: &str) -> Result { if advisory_id == Id::PLACEHOLDER { return Ok(Id::default()); } let kind = IdKind::detect(advisory_id); // Ensure known advisory types are well-formed let year = match kind { IdKind::RustSec | IdKind::Cve | IdKind::Talos => Some(parse_year(advisory_id)?), _ => None, }; Ok(Self { kind, year, string: advisory_id.into(), }) } } impl Serialize for Id { fn serialize(&self, serializer: S) -> Result { serializer.serialize_str(&self.string) } } impl<'de> Deserialize<'de> for Id { fn deserialize>(deserializer: D) -> Result { Self::from_str(&String::deserialize(deserializer)?).map_err(D::Error::custom) } } /// Known kinds of advisory IDs #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] #[non_exhaustive] pub enum IdKind { /// Our advisory namespace RustSec, /// Common Vulnerabilities and Exposures Cve, /// GitHub Security Advisory Ghsa, /// Cisco Talos identifiers Talos, /// Other types of advisory identifiers we don't know about Other, } impl IdKind { /// Detect the identifier kind for the given string pub fn detect(string: &str) -> Self { if string.starts_with("RUSTSEC-") { IdKind::RustSec } else if string.starts_with("CVE-") { IdKind::Cve } else if string.starts_with("TALOS-") { IdKind::Talos } else if string.starts_with("GHSA-") { IdKind::Ghsa } else { IdKind::Other } } } /// Parse the year from an advisory identifier fn parse_year(advisory_id: &str) -> Result { let mut parts = advisory_id.split('-'); parts.next().unwrap(); let year = match parts.next().unwrap().parse::() { Ok(n) => match n { YEAR_MIN..=YEAR_MAX => n, _ => fail!( ErrorKind::Parse, "out-of-range year in advisory ID: {}", advisory_id ), }, _ => fail!( ErrorKind::Parse, "malformed year in advisory ID: {}", advisory_id ), }; if let Some(num) = parts.next() { if num.parse::().is_err() { fail!(ErrorKind::Parse, "malformed advisory ID: {}", advisory_id); } } else { fail!(ErrorKind::Parse, "incomplete advisory ID: {}", advisory_id); } if parts.next().is_some() { fail!(ErrorKind::Parse, "malformed advisory ID: {}", advisory_id); } Ok(year) } #[cfg(test)] mod tests { use super::{Id, IdKind}; const EXAMPLE_RUSTSEC_ID: &str = "RUSTSEC-2018-0001"; const EXAMPLE_CVE_ID: &str = "CVE-2017-1000168"; const EXAMPLE_GHSA_ID: &str = "GHSA-4mmc-49vf-jmcp"; const EXAMPLE_TALOS_ID: &str = "TALOS-2017-0468"; const EXAMPLE_UNKNOWN_ID: &str = "Anonymous-42"; #[test] fn rustsec_id_test() { let rustsec_id = EXAMPLE_RUSTSEC_ID.parse::().unwrap(); assert!(rustsec_id.is_rustsec()); assert_eq!(rustsec_id.year().unwrap(), 2018); assert_eq!( rustsec_id.url().unwrap(), "https://rustsec.org/advisories/RUSTSEC-2018-0001" ); assert_eq!(rustsec_id.numerical_part().unwrap(), 1); } // The RUSTSEC-0000-0000 ID is a placeholder we need to treat as valid #[test] fn rustsec_0000_0000_test() { let rustsec_id = Id::PLACEHOLDER.parse::().unwrap(); assert!(rustsec_id.is_rustsec()); assert!(rustsec_id.year().is_none()); assert!(rustsec_id.url().is_none()); assert!(rustsec_id.numerical_part().is_none()); } #[test] fn cve_id_test() { let cve_id = EXAMPLE_CVE_ID.parse::().unwrap(); assert!(cve_id.is_cve()); assert_eq!(cve_id.year().unwrap(), 2017); assert_eq!( cve_id.url().unwrap(), "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-1000168" ); assert_eq!(cve_id.numerical_part().unwrap(), 1000168); } #[test] fn ghsa_id_test() { let ghsa_id = EXAMPLE_GHSA_ID.parse::().unwrap(); assert!(ghsa_id.is_ghsa()); assert!(ghsa_id.year().is_none()); assert_eq!( ghsa_id.url().unwrap(), "https://github.com/advisories/GHSA-4mmc-49vf-jmcp" ); assert!(ghsa_id.numerical_part().is_none()); } #[test] fn talos_id_test() { let talos_id = EXAMPLE_TALOS_ID.parse::().unwrap(); assert_eq!(talos_id.kind(), IdKind::Talos); assert_eq!(talos_id.year().unwrap(), 2017); assert_eq!( talos_id.url().unwrap(), "https://www.talosintelligence.com/reports/TALOS-2017-0468" ); assert_eq!(talos_id.numerical_part().unwrap(), 468); } #[test] fn other_id_test() { let other_id = EXAMPLE_UNKNOWN_ID.parse::().unwrap(); assert!(other_id.is_other()); assert!(other_id.year().is_none()); assert!(other_id.url().is_none()); assert_eq!(other_id.numerical_part().unwrap(), 42); } } rustsec-0.33.0/src/advisory/informational.rs000064400000000000000000000100551046102023000172370ustar 00000000000000//! Informational advisories: ones which don't represent an immediate security //! threat, but something users of a crate should be warned of/aware of use crate::{error::Error, warning}; use serde::{Deserialize, Serialize, de, ser}; use std::{fmt, str::FromStr}; /// Categories of informational vulnerabilities #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] #[non_exhaustive] pub enum Informational { /// Security notices for a crate which are published on /// but don't represent a vulnerability in a crate itself. Notice, /// Crate is unmaintained / abandoned Unmaintained, /// Crate is not [sound], i.e., unsound. /// /// A crate is unsound if, using its public API from safe code, it is possible to cause [Undefined Behavior]. /// /// [sound]: https://rust-lang.github.io/unsafe-code-guidelines/glossary.html#soundness-of-code--of-a-library /// [Undefined Behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html Unsound, /// Other types of informational advisories: left open-ended to add /// more of them in the future. Other(String), } impl Informational { /// Get a `str` representing an [`Informational`] category pub fn as_str(&self) -> &str { match self { Self::Notice => "notice", Self::Unmaintained => "unmaintained", Self::Unsound => "unsound", Self::Other(other) => other, } } /// Is this informational advisory a `notice`? pub fn is_notice(&self) -> bool { *self == Self::Notice } /// Is this informational advisory for an `unmaintained` crate? pub fn is_unmaintained(&self) -> bool { *self == Self::Unmaintained } /// Is this informational advisory for an `unsound` crate? pub fn is_unsound(&self) -> bool { *self == Self::Unsound } /// Is this informational advisory of an unknown kind? pub fn is_other(&self) -> bool { matches!(self, Self::Other(_)) } /// Get a warning kind for this informational type (if applicable) pub fn warning_kind(&self) -> Option { match self { Self::Notice => Some(warning::WarningKind::Notice), Self::Unmaintained => Some(warning::WarningKind::Unmaintained), Self::Unsound => Some(warning::WarningKind::Unsound), Self::Other(_) => None, } } } impl fmt::Display for Informational { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.as_str()) } } impl FromStr for Informational { type Err = Error; fn from_str(s: &str) -> Result { Ok(match s { "notice" => Self::Notice, "unmaintained" => Self::Unmaintained, "unsound" => Self::Unsound, other => Self::Other(other.to_owned()), }) } } impl<'de> Deserialize<'de> for Informational { fn deserialize>(deserializer: D) -> Result { use de::Error; let string = String::deserialize(deserializer)?; string.parse().map_err(D::Error::custom) } } impl Serialize for Informational { fn serialize(&self, serializer: S) -> Result { self.to_string().serialize(serializer) } } #[cfg(test)] mod tests { use super::Informational; #[test] fn parse_notice() { let notice = "notice".parse::().unwrap(); assert_eq!(Informational::Notice, notice); assert_eq!("notice", notice.as_str()); } #[test] fn parse_unmaintained() { let unmaintained = "unmaintained".parse::().unwrap(); assert_eq!(Informational::Unmaintained, unmaintained); assert_eq!("unmaintained", unmaintained.as_str()); } #[test] fn parse_other() { let other = "foobar".parse::().unwrap(); assert_eq!(Informational::Other("foobar".to_owned()), other); assert_eq!("foobar", other.as_str()); } } rustsec-0.33.0/src/advisory/keyword.rs000064400000000000000000000017671046102023000160730ustar 00000000000000//! Advisory keywords use crate::error::Error; use serde::{Deserialize, Deserializer, Serialize, de::Error as DeError}; use std::str::FromStr; /// Keywords on advisories, similar to Cargo keywords #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize)] pub struct Keyword(String); impl Keyword { /// Borrow this keyword as a string slice pub fn as_str(&self) -> &str { self.0.as_ref() } } impl AsRef for Keyword { fn as_ref(&self) -> &str { self.as_str() } } impl<'de> Deserialize<'de> for Keyword { fn deserialize>(deserializer: D) -> Result { Self::from_str(&String::deserialize(deserializer)?) .map_err(|e| D::Error::custom(format!("{e}"))) } } impl FromStr for Keyword { type Err = Error; /// Create a new keyword fn from_str(keyword: &str) -> Result { // TODO: validate keywords according to Cargo-like rules Ok(Keyword(keyword.into())) } } rustsec-0.33.0/src/advisory/license.rs000064400000000000000000000056251046102023000160260ustar 00000000000000use crate::Error; use serde::{Deserialize, Serialize, Serializer}; use std::fmt::{Display, Formatter}; use std::str::FromStr; #[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize)] #[serde(from = "String")] /// Type representing licenses used for advisory content #[non_exhaustive] pub enum License { /// Creative Commons Zero v1.0 Universal /// SPDX identifier: CC0-1.0 #[default] CcZero10, /// Creative Commons Attribution 4.0 International /// SPDX identifier: CC-BY-4.0 /// /// Note: For GitHub Security Advisories database, /// providing a link is [documented](https://docs.github.com/en/site-policy/github-terms/github-terms-for-additional-products-and-features#advisory-database) /// as fulfilling the attribution obligation for the CC-BY 4.0 license used. /// /// For advisories imported from a GitHub Security Advisory, we follow this by putting the /// original URL in the `url` filed of the RustSec advisory, as it assures the link will be /// visible to downstream users. CcBy40, /// Other SPDX requirement Other(String), } impl From for License { fn from(s: String) -> Self { match s.as_str() { "CC0-1.0" => License::CcZero10, "CC-BY-4.0" => License::CcBy40, _ => License::Other(s), } } } impl Serialize for License { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_str(self.spdx()) } } impl Display for License { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.spdx()) } } impl License { /// Get license as an `&str` containing the SPDX identifier pub fn spdx(&self) -> &str { match &self { License::CcBy40 => "CC-BY-4.0", License::CcZero10 => "CC0-1.0", License::Other(l) => l, } } } impl FromStr for License { type Err = Error; // Parse standard SPDX identifiers fn from_str(s: &str) -> Result { Ok(match s { "CC0-1.0" => License::CcZero10, "CC-BY-4.0" => License::CcBy40, l => License::Other(l.to_string()), }) } } #[cfg(test)] mod tests { use crate::advisory::License; #[test] fn serialize_licenses() { assert_eq!( serde_json::to_string(&License::CcBy40).unwrap(), "\"CC-BY-4.0\"".to_string() ); assert_eq!( serde_json::to_string(&License::Other("MPL-2.0".to_string())).unwrap(), "\"MPL-2.0\"".to_string() ); } #[test] fn deserialize_licenses() { let l: License = serde_json::from_str("\"CC-BY-4.0\"").unwrap(); assert_eq!(l, License::CcBy40); let l: License = serde_json::from_str("\"MPL-2.0\"").unwrap(); assert_eq!(l, License::Other("MPL-2.0".to_string())); } } rustsec-0.33.0/src/advisory/linter.rs000064400000000000000000000337171046102023000157040ustar 00000000000000//! Advisory linter: ensure advisories are well-formed according to the //! currently valid set of fields. //! //! This is run in CI at the time advisories are submitted. use super::{Advisory, Category, parts}; use crate::advisory::license::License; use crate::fs; use std::str::FromStr; use std::{fmt, path::Path}; /// Lint information about a particular advisory #[derive(Debug)] pub struct Linter { /// Advisory being linted advisory: Advisory, /// Errors detected during linting errors: Vec, } impl Linter { /// Lint the advisory TOML file located at the given path pub fn lint_file>(path: P) -> Result { let path = path.as_ref(); match path.extension().and_then(|ext| ext.to_str()) { Some("md") => (), other => fail!( crate::ErrorKind::Parse, "invalid advisory file extension: {}", other.unwrap_or("(missing)") ), } let advisory_data = fs::read_to_string(path).map_err(|e| { crate::Error::with_source( crate::ErrorKind::Io, format!("couldn't open {}", path.display()), e, ) })?; Self::lint_string(&advisory_data, Some(Advisory::is_draft(path))) } /// Lint the given advisory data pub fn lint_string(s: &str, draft: Option) -> Result { // Ensure the advisory parses according to the normal parser first let advisory = s.parse::()?; // Get advisory "front matter" (TOML formatted) let advisory_parts = parts::Parts::parse(s)?; let front_matter = advisory_parts .front_matter .parse::() .map_err(crate::Error::from_toml)?; let mut linter = Self { advisory, errors: vec![], }; linter.lint_advisory(&front_matter, draft); Ok(linter) } /// Get the parsed advisory pub fn advisory(&self) -> &Advisory { &self.advisory } /// Get the errors that occurred during linting pub fn errors(&self) -> &[Error] { self.errors.as_slice() } /// Lint the provided TOML value as the toplevel table of an advisory fn lint_advisory(&mut self, advisory: &toml::Table, draft: Option) { for (key, value) in advisory { match key.as_str() { "advisory" => self.lint_metadata(value, draft), "versions" => self.lint_versions(value), "affected" => self.lint_affected(value), _ => self.errors.push(Error { kind: ErrorKind::key(key), section: None, message: None, }), } } } /// Lint the `[advisory]` metadata section fn lint_metadata(&mut self, metadata: &toml::Value, draft: Option) { let mut year = None; if let Some(table) = metadata.as_table() { for (key, value) in table { match key.as_str() { "id" => { if let Some(draft) = draft && self.advisory.metadata.id.is_placeholder() != draft { self.errors.push(Error { kind: ErrorKind::value("id", value.to_string()), section: Some("advisory"), message: Some("advisory ID draft status does not match file name"), }); } if self.advisory.metadata.id.is_other() { self.errors.push(Error { kind: ErrorKind::value("id", value.to_string()), section: Some("advisory"), message: Some("unknown advisory ID type"), }); } else if let Some(y1) = self.advisory.metadata.id.year() { // Exclude CVE IDs, since the year from CVE ID may not match the report date if !self.advisory.metadata.id.is_cve() { if let Some(y2) = year { if y1 != y2 { self.errors.push(Error { kind: ErrorKind::value("id", value.to_string()), section: Some("advisory"), message: Some( "year in advisory ID does not match date", ), }); } } else { year = Some(y1); } } } } "categories" => { for category in &self.advisory.metadata.categories { if let Category::Other(other) = category { self.errors.push(Error { kind: ErrorKind::value("category", other.to_string()), section: Some("advisory"), message: Some("unknown category"), }); } } } "collection" => self.errors.push(Error { kind: ErrorKind::Malformed, section: Some("advisory"), message: Some("collection shouldn't be explicit; inferred by location"), }), "informational" => { let informational = self .advisory .metadata .informational .as_ref() .expect("parsed informational"); if informational.is_other() { self.errors.push(Error { kind: ErrorKind::value("informational", informational.as_str()), section: Some("advisory"), message: Some("unknown informational advisory type"), }); } } "url" => { if let Some(url) = value.as_str() && !url.starts_with("https://") { self.errors.push(Error { kind: ErrorKind::value("url", value.to_string()), section: Some("advisory"), message: Some("URL must start with https://"), }); } } "date" => { let y1 = self.advisory.metadata.date.year(); if let Some(y2) = year { if y1 != y2 { self.errors.push(Error { kind: ErrorKind::value("date", value.to_string()), section: Some("advisory"), message: Some("year in advisory ID does not match date"), }); } } else { year = Some(y1); } } "yanked" => { if self.advisory.metadata.withdrawn.is_none() { self.errors.push(Error { kind: ErrorKind::Malformed, section: Some("metadata"), message: Some( "Field `yanked` is deprecated, use `withdrawn` field instead", ), }); } } "license" => { if let Some(l) = value.as_str() { // We don't want to accept any license, only explicitly accepted ones let unknown_license = matches!(License::from_str(l).unwrap(), License::Other(_)); if unknown_license { self.errors.push(Error { kind: ErrorKind::value("license", l.to_string()), section: Some("advisory"), message: Some("Unknown license"), }); } } } "aliases" | "cvss" | "keywords" | "package" | "references" | "related" | "title" | "withdrawn" | "description" | "expect-deleted" => (), _ => self.errors.push(Error { kind: ErrorKind::key(key), section: Some("advisory"), message: None, }), } } } else { self.errors.push(Error { kind: ErrorKind::Malformed, section: Some("advisory"), message: Some("expected table"), }); } } /// Lint the `[versions]` section of an advisory fn lint_versions(&mut self, versions: &toml::Value) { if let Some(table) = versions.as_table() { for (key, _) in table { match key.as_str() { "patched" | "unaffected" => (), _ => self.errors.push(Error { kind: ErrorKind::key(key), section: Some("versions"), message: None, }), } } } } /// Lint the `[affected]` section of an advisory fn lint_affected(&mut self, affected: &toml::Value) { if let Some(table) = affected.as_table() { for (key, _) in table { match key.as_str() { "functions" => { for function in self.advisory.affected.as_ref().unwrap().functions.keys() { // Rust identifiers do not allow '-' character but crate names do, // thus "crate-name" would be addressed as "crate_name" in function path let crate_name = self.advisory.metadata.package.as_str().replace('-', "_"); if function.segments()[0].as_str() != crate_name { self.errors.push(Error { kind: ErrorKind::value("functions", function.to_string()), section: Some("affected"), message: Some("function path must start with crate name"), }); } } } "arch" | "os" => (), _ => self.errors.push(Error { kind: ErrorKind::key(key), section: Some("affected"), message: None, }), } } } } } /// Lint errors #[derive(Clone, Debug, Eq, PartialEq)] pub struct Error { /// Kind of error kind: ErrorKind, /// Section of the advisory where the error occurred section: Option<&'static str>, /// Message about why it's invalid message: Option<&'static str>, } impl Error { /// Get the kind of error pub fn kind(&self) -> &ErrorKind { &self.kind } /// Get the section of the advisory where the error occurred pub fn section(&self) -> Option<&str> { self.section.as_ref().map(AsRef::as_ref) } /// Get an optional message about the lint failure pub fn message(&self) -> Option<&str> { self.message.as_ref().map(AsRef::as_ref) } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", &self.kind)?; if let Some(section) = &self.section { write!(f, " in [{section}]")?; } else { write!(f, " in toplevel")?; } if let Some(msg) = &self.message { write!(f, ": {msg}")? } Ok(()) } } /// Lint errors #[derive(Clone, Debug, Eq, PartialEq)] #[non_exhaustive] pub enum ErrorKind { /// Advisory is structurally malformed Malformed, /// Unknown key InvalidKey { /// Name of the key name: String, }, /// Unknown value InvalidValue { /// Name of the key name: String, /// Invalid value value: String, }, } impl ErrorKind { /// Invalid key pub fn key(name: &str) -> Self { ErrorKind::InvalidKey { name: name.to_owned(), } } /// Invalid value pub fn value(name: &str, value: impl Into) -> Self { ErrorKind::InvalidValue { name: name.to_owned(), value: value.into(), } } } impl fmt::Display for ErrorKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ErrorKind::Malformed => write!(f, "malformed content"), ErrorKind::InvalidKey { name } => write!(f, "invalid key `{name}`"), ErrorKind::InvalidValue { name, value } => { write!(f, "invalid value `{value}` for key `{name}`") } } } } rustsec-0.33.0/src/advisory/metadata.rs000064400000000000000000000063261046102023000161630ustar 00000000000000//! Advisory information (i.e. the `[advisory]` section) use super::{ category::Category, date::Date, id::Id, informational::Informational, keyword::Keyword, }; use crate::advisory::license::License; use crate::{SourceId, collection::Collection, package}; use cvss::Cvss; use serde::{Deserialize, Serialize}; use url::Url; /// The `[advisory]` section of a RustSec security advisory #[non_exhaustive] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct Metadata { /// Security advisory ID (e.g. RUSTSEC-YYYY-NNNN) pub id: Id, /// Name of affected crate pub package: package::Name, /// One-liner description of a vulnerability #[serde(default)] pub title: String, /// Extended description of a vulnerability #[serde(default)] pub description: String, /// Date the underlying issue was reported pub date: Date, /// Advisory IDs in other databases which point to the same advisory #[serde(default)] pub aliases: Vec, /// Advisory IDs which are related to this advisory. /// (use `aliases` for the same vulnerability syndicated to other databases) #[serde(default)] pub related: Vec, /// Collection this advisory belongs to. This isn't intended to be /// explicitly specified in the advisory, but rather is auto-populated /// based on the location pub collection: Option, /// RustSec vulnerability categories: one of a fixed list of vulnerability /// categorizations accepted by the project. #[serde(default)] pub categories: Vec, /// Freeform keywords which succinctly describe this vulnerability (e.g. "ssl", "rce", "xss") #[serde(default)] pub keywords: Vec, /// CVSS v3.1 Base Metrics vector string containing severity information. /// /// Example: /// /// ```text /// CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N /// ``` pub cvss: Option, /// Informational advisories can be used to warn users about issues /// affecting a particular crate without failing the build. pub informational: Option, /// Additional reference URLs with more information related to this advisory #[serde(default)] pub references: Vec, /// Source URL where the vulnerable package is located/published. /// /// Defaults to crates.io, i.e. `registry+https://github.com/rust-lang/crates.io-index` pub source: Option, /// URL with an announcement (e.g. blog post, PR, disclosure issue, CVE) pub url: Option, /// Was this advisory (i.e. itself, regardless of the crate) withdrawn? /// If yes, when? /// /// This can be used to soft-delete advisories which were filed in error. #[serde(default)] pub withdrawn: Option, /// License under which the advisory content is available #[serde(default)] pub license: License, /// Whether the crate is expected to be deleted from the registry /// /// Linting normally checks that a crate is available on crates.io, but this might not be the /// case, for example if a malicious crate has been completely removed. #[serde(rename = "expect-deleted", default)] pub expect_deleted: bool, } rustsec-0.33.0/src/advisory/parts.rs000064400000000000000000000037261046102023000155350ustar 00000000000000//! Support for parsing advisories from a Markdown file //! (a.k.a. "V3 advisory format") use crate::error::{Error, ErrorKind}; /// Parts of a parsed advisory #[derive(Copy, Clone, Debug)] pub struct Parts<'a> { /// TOML front matter pub front_matter: &'a str, /// Markdown without front matter pub markdown: &'a str, /// Advisory title pub title: &'a str, /// Advisory description (markdown) pub description: &'a str, } impl<'a> Parts<'a> { /// Parse a Markdown advisory into its component parts pub fn parse(advisory_data: &'a str) -> Result { if !advisory_data.starts_with("```toml") { let context = if advisory_data.len() > 20 { &advisory_data[..20] } else { advisory_data }; fail!( ErrorKind::Parse, "unexpected start of advisory: \"{}\"", context ) } let toml_end = advisory_data.find("\n```").ok_or_else(|| { Error::new( ErrorKind::Parse, "couldn't find end of TOML front matter in advisory", ) })?; let front_matter = advisory_data[7..toml_end].trim_start().trim_end(); let markdown = advisory_data[(toml_end + 4)..].trim_start(); if !markdown.starts_with("# ") { fail!( ErrorKind::Parse, "Expected # header after TOML front matter" ); } let next_newline = markdown.find('\n').ok_or_else(|| { Error::new( ErrorKind::Parse, "no Markdown body (i.e. description) found", ) })?; let title = markdown[2..next_newline].trim_end(); let description = markdown[(next_newline + 1)..].trim_start().trim_end(); Ok(Self { front_matter, markdown, title, description, }) } } rustsec-0.33.0/src/advisory/versions.rs000064400000000000000000000043521046102023000162500ustar 00000000000000//! The `[versions]` subsection of an advisory. use crate::{Error, osv}; use semver::{Version, VersionReq}; use serde::{Deserialize, Serialize}; /// The `[versions]` subsection of an advisory: future home to information /// about which versions are patched and/or unaffected. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(try_from = "RawVersions")] pub struct Versions { /// Versions which are patched and not vulnerable (expressed as semantic version requirements) patched: Vec, /// Versions which were never affected in the first place #[serde(default)] unaffected: Vec, } impl Versions { /// Is the given version of a package vulnerable? pub fn is_vulnerable(&self, version: &Version) -> bool { for range in osv::ranges_for_advisory(self).iter() { if range.affects(version) { return true; } } false } /// Creates a new `[versions]` entry. /// Checks consistency of the passed version requirements. pub fn new(patched: Vec, unaffected: Vec) -> Result { RawVersions { patched, unaffected, } .try_into() } /// Versions which are patched and not vulnerable (expressed as semantic version requirements) pub fn patched(&self) -> &[VersionReq] { self.patched.as_slice() } /// Versions which were never affected in the first place pub fn unaffected(&self) -> &[VersionReq] { self.unaffected.as_slice() } } impl TryFrom for Versions { type Error = Error; fn try_from(raw: RawVersions) -> Result { validate_ranges(&raw)?; Ok(Versions { patched: raw.patched, unaffected: raw.unaffected, }) } } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] /// Raw deserialized data that didn't pass validation yet pub(crate) struct RawVersions { pub patched: Vec, #[serde(default)] pub unaffected: Vec, } fn validate_ranges(versions: &RawVersions) -> Result<(), Error> { let _ = osv::ranges_for_unvalidated_advisory(versions)?; Ok(()) } rustsec-0.33.0/src/advisory.rs000064400000000000000000000075351046102023000144060ustar 00000000000000//! Security advisories in the RustSec database pub mod affected; mod category; mod date; mod id; mod informational; mod keyword; mod license; pub mod linter; mod metadata; mod parts; pub(crate) mod versions; pub use self::{ affected::Affected, category::Category, date::Date, id::{Id, IdKind}, informational::Informational, keyword::Keyword, license::License, linter::Linter, metadata::Metadata, parts::Parts, versions::Versions, }; pub use cvss::Severity; use crate::{ error::{Error, ErrorKind}, fs, }; use serde::{Deserialize, Serialize}; use std::{ffi::OsStr, path::Path, str::FromStr}; /// RustSec Security Advisories #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct Advisory { /// The `[advisory]` section of a RustSec advisory #[serde(rename = "advisory")] pub metadata: Metadata, /// The (optional) `[affected]` section of a RustSec advisory pub affected: Option, /// Versions related to this advisory which are patched or unaffected. pub versions: Versions, } impl Advisory { /// Load an advisory from a `RUSTSEC-20XX-NNNN.md` file pub fn load_file(path: impl AsRef) -> Result { let path = path.as_ref(); let advisory_data = fs::read_to_string(path).map_err(|e| { Error::with_source( ErrorKind::Io, format!("couldn't open {}", path.display()), e, ) })?; advisory_data.parse().map_err(|e| { Error::with_source( ErrorKind::Parse, format!("error parsing {}", path.display()), e, ) }) } /// Get advisory ID pub fn id(&self) -> &Id { &self.metadata.id } /// Get advisory title pub fn title(&self) -> &str { self.metadata.title.as_ref() } /// Get advisory description pub fn description(&self) -> &str { self.metadata.description.as_ref() } /// Get the date the underlying issue was reported on pub fn date(&self) -> &Date { &self.metadata.date } /// Get the severity of this advisory if it has a CVSS v3 associated pub fn severity(&self) -> Option { self.metadata.cvss.as_ref().map(|cvss| cvss.severity()) } /// Whether the advisory has been withdrawn, i.e. soft-deleted pub fn withdrawn(&self) -> bool { self.metadata.withdrawn.is_some() } /// Whether the given `path` represents a draft advisory pub fn is_draft(path: &Path) -> bool { matches!( path.file_name().and_then(OsStr::to_str), Some(name) if name.starts_with("RUSTSEC-0000-0000."), ) } } impl FromStr for Advisory { type Err = Error; fn from_str(advisory_data: &str) -> Result { let parts = Parts::parse(advisory_data)?; // V4 advisories omit the leading `[advisory]` TOML table let front_matter = if parts.front_matter.starts_with("[advisory]") { parts.front_matter.to_owned() } else { String::from("[advisory]\n") + parts.front_matter }; let mut advisory: Self = toml::from_str(&front_matter).map_err(Error::from_toml)?; if !advisory.metadata.title.is_empty() { fail!( ErrorKind::Parse, "invalid `title` attribute in advisory TOML" ); } if !advisory.metadata.description.is_empty() { fail!( ErrorKind::Parse, "invalid `description` attribute in advisory TOML" ); } #[allow(clippy::assigning_clones)] { advisory.metadata.title = parts.title.to_owned(); advisory.metadata.description = parts.description.to_owned(); } Ok(advisory) } } rustsec-0.33.0/src/binary_scanning/binary_deps.rs000064400000000000000000000157041046102023000202060ustar 00000000000000//! Extracts the list of dependencies from a binary file use std::str::FromStr; use crate::{Error, ErrorKind}; use auditable_serde::VersionInfo; use cargo_lock::{Dependency, Lockfile, Package}; use crate::binary_scanning::BinaryFormat; /// The default file size limit is 8MB const DEFAULT_FILE_SIZE_LIMIT: usize = 8 * 1024 * 1024; /// Dependencies recovered from scanning a compiled Rust executable pub enum BinaryReport { /// Full dependency list embedded by `cargo auditable` Complete(Lockfile), /// Partially recovered dependencies from panic messages Incomplete(Lockfile), /// No data found whatsoever, probably not a Rust executable None, } /// Load the dependency tree from a compiled Rust executable. /// /// Recovers the precise dependency list if the binary is built with [`cargo auditable`](https://crates.io/crates/cargo-auditable). /// Failing that, recovers as many crates as possible from panic messages (using [quitters]). /// /// If `audit_data_size_limit` is set to `None`, the limit defaults to 8MB. pub fn load_deps_from_binary( file_contents: &[u8], audit_data_size_limit: Option, ) -> crate::Result<(BinaryFormat, BinaryReport)> { let format = detect_format(file_contents); let file_size_limit = match audit_data_size_limit { Some(size) => size, None => DEFAULT_FILE_SIZE_LIMIT, }; let version_info = auditable_info::audit_info_from_slice(file_contents, file_size_limit); use auditable_info::Error::*; // otherwise rustfmt makes the matches multiline and unreadable match version_info { Ok(json_struct) => Ok(( format, BinaryReport::Complete(lockfile_from_version_info_json(&json_struct)?), )), Err(e) => match e { NoAuditData => { if let Some(deps) = deps_from_panic_messages(file_contents) { Ok((format, BinaryReport::Incomplete(deps))) } else { Ok((format, BinaryReport::None)) } } // The error handling boilerplate is in here instead of the `rustsec` crate because as of this writing // the public APIs of the crates involved are still somewhat unstable, // and this way we don't expose the error types in any public APIs Io(_) => Err(Error::with_source( ErrorKind::Io, "could not extract dependencies from binary".to_string(), e, )), // Everything else is just Parse, but we enumerate them explicitly in case variant list changes InputLimitExceeded | OutputLimitExceeded | BinaryParsing(_) | Decompression(_) | Json(_) | Utf8(_) => Err(Error::with_source( ErrorKind::Parse, "could not extract dependencies from binary".to_string(), e, )), }, } } fn detect_format(data: &[u8]) -> BinaryFormat { match binfarce::detect_format(data) { binfarce::Format::Unknown => { // binfarce doesn't detect WASM if data.starts_with(b"\0asm") { BinaryFormat::Wasm } else { BinaryFormat::Unknown } } known_format => known_format.into(), } } fn deps_from_panic_messages(data: &[u8]) -> Option { let deps = quitters::versions(data); if !deps.is_empty() { let packages: Vec = deps.into_iter().map(to_package).collect(); Some(Lockfile { version: cargo_lock::ResolveVersion::V2, packages, root: None, metadata: Default::default(), patch: Default::default(), }) } else { None } } // matches https://docs.rs/cargo-lock/8.0.2/src/cargo_lock/package/source.rs.html#19 // to signal crates.io to the `cargo-lock` crate const CRATES_IO_INDEX: &str = "registry+https://github.com/rust-lang/crates.io-index"; fn to_package(quitter: (&str, cargo_lock::Version)) -> Package { Package { // The `quitters` crate already ensures the name is valid, so we can just `.unwrap()` here name: cargo_lock::Name::from_str(quitter.0).unwrap(), version: quitter.1, // we can't know the exact registry, but by default `cargo audit` will // only scan crates from crates.io, so assume they're from there source: Some(cargo_lock::package::SourceId::from_url(CRATES_IO_INDEX).unwrap()), checksum: None, dependencies: Vec::new(), replace: None, } } /// Recover a [`Lockfile`] from a [`VersionInfo`] struct. fn lockfile_from_version_info_json(input: &VersionInfo) -> Result { let mut root_package: Option = None; let mut packages: Vec = Vec::new(); for pkg in input.packages.iter() { let lock_pkg = Package { name: cargo_lock::package::Name::from_str(&pkg.name)?, version: pkg.version.clone(), checksum: None, dependencies: { let result: Result, _> = pkg .dependencies .iter() .map(|i| { let package = input .packages .get(*i) .ok_or(cargo_lock::Error::Parse(format!( "There is no dependency with index {i} in the input JSON" )))?; Result::<_, cargo_lock::Error>::Ok(Dependency { name: cargo_lock::package::Name::from_str(package.name.as_str())?, version: package.version.clone(), source: source_from_json(&package.source), }) }) .collect(); result? }, replace: None, source: source_from_json(&pkg.source), }; if pkg.root { if root_package.is_some() { return Err(cargo_lock::Error::Parse( "More than one root package specified in JSON!".to_string(), )); } root_package = Some(lock_pkg.clone()); } packages.push(lock_pkg); } Ok(Lockfile { version: cargo_lock::ResolveVersion::V2, packages, root: root_package, metadata: std::collections::BTreeMap::new(), patch: cargo_lock::Patch { unused: Vec::new() }, }) } fn source_from_json(source: &auditable_serde::Source) -> Option { match source { auditable_serde::Source::CratesIo => Some( cargo_lock::package::SourceId::from_url( "registry+https://github.com/rust-lang/crates.io-index", ) .unwrap(), ), _ => None, // we don't store enough info about other sources to reconstruct the URL } } rustsec-0.33.0/src/binary_scanning/binary_format.rs000064400000000000000000000021331046102023000205330ustar 00000000000000//! A shim around `binfarce::Format` so that `binfarce` crate could be an optional dependency #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[non_exhaustive] /// Formats of compiled executables that can be scanned pub enum BinaryFormat { /// Executable and Linkable Format, 32-bit. Used on Unix systems. Elf32, /// Executable and Linkable Format, 64-bit. Used on Unix systems. Elf64, /// Mach object file format. Used on Apple systems. Macho, /// Portable Executable (PE) format. Used on Windows. PE, /// WebAssembly Wasm, /// The format is not known Unknown, } #[cfg(feature = "binary-scanning")] impl From for BinaryFormat { fn from(value: binfarce::Format) -> Self { match value { binfarce::Format::Elf32 { byte_order: _ } => BinaryFormat::Elf32, binfarce::Format::Elf64 { byte_order: _ } => BinaryFormat::Elf64, binfarce::Format::Macho => BinaryFormat::Macho, binfarce::Format::PE => BinaryFormat::PE, binfarce::Format::Unknown => BinaryFormat::Unknown, } } } rustsec-0.33.0/src/binary_scanning/binary_type_filter.rs000064400000000000000000000065631046102023000216040ustar 00000000000000use std::collections::BTreeSet; use std::str::FromStr; use crate::platforms::{OS, platform::PlatformReq}; use once_cell::sync::OnceCell; use crate::Report; use crate::binary_scanning::BinaryFormat; /// Filters a [Report] to remove advisories not applicable to the given binary type. /// /// For example, Windows-only advisories should not be reported for ELF files. pub fn filter_report_by_binary_type(binary_type: &BinaryFormat, report: &mut Report) { // Filter vulnerabilities let vulns = &mut report.vulnerabilities; assert_eq!( vulns.list.len(), vulns.count, "Internal logic error: Incorrect number of vulnerabilities in the report!" ); vulns .list .retain(|vuln| advisory_applicable_to_binary(binary_type, &vuln.affected)); vulns.count = vulns.list.len(); vulns.found = !vulns.list.is_empty(); // Filter warnings let warns = &mut report.warnings; warns.iter_mut().for_each(|(_kind, warnings)| { warnings.retain(|w| advisory_applicable_to_binary(binary_type, &w.affected)) }); } fn advisory_applicable_to_binary( binary_type: &BinaryFormat, affected: &Option, ) -> bool { if let Some(affected) = affected { if affected.os.is_empty() { true // all platforms are affected if the "os" list is empty } else { at_least_one_os_runs_binary(binary_type, &affected.os) } } else { true // all platforms are affected if "affected" section is not specified in the TOML } } fn at_least_one_os_runs_binary(binary_type: &BinaryFormat, os_list: &[OS]) -> bool { use BinaryFormat::*; match binary_type { PE => os_list.contains(&OS::Windows), Macho => os_list.iter().any(|os| apple_OSs().contains(os)), // O(n*log(n)) Elf32 | Elf64 => { // For now we'll assume it's affected if the list contains something other than Windows or Apple OSs os_list .iter() .any(|os| os != &OS::Windows && !apple_OSs().contains(os)) // TODO: this could be improved if we somehow keep track of which OS uses elf and which doesn't. // Sadly `rustc --print-cfg` doesn't expose this information. // Perhaps we can make `platforms` expose the `family` which can be `windows` or `unix` or `unknown`? // That way we can capture all the unix-likes as using ELF and discard everything else } Wasm => { // WASM doesn't have an OS, so OS-specific vulns should not be an issue. // There isn't really a way to indicate WASM-specific vulns, // because in Rustc's terms WASM is not an OS. // Assume vulns with `Unknown` and `None` OS still apply, // just to have some last-ditch way to indicate WASM is affected // other than including all the platforms ever. os_list .iter() .any(|os| os == &OS::Unknown || os == &OS::None) } Unknown => true, // might be possible for detection based on panic messages? } } #[allow(non_snake_case)] fn apple_OSs() -> &'static BTreeSet { static INSTANCE: OnceCell> = OnceCell::new(); INSTANCE.get_or_init(|| { let req = PlatformReq::from_str("*apple*").unwrap(); req.matching_platforms().map(|p| p.target_os).collect() }) } rustsec-0.33.0/src/binary_scanning.rs000064400000000000000000000011241046102023000156760ustar 00000000000000//! Recover dependency list from compiled Rust binaries //! //! Most functions and types in this module only appear //! when the `binary_scanning` feature is enabled. // enabled unconditionally due to being able to name the type being useful // and this type alone not pulling in any additional dependencies mod binary_format; pub use binary_format::*; #[cfg(feature = "binary-scanning")] mod binary_deps; #[cfg(feature = "binary-scanning")] mod binary_type_filter; #[cfg(feature = "binary-scanning")] pub use binary_deps::*; #[cfg(feature = "binary-scanning")] pub use binary_type_filter::*; rustsec-0.33.0/src/cached_index.rs000064400000000000000000000246541046102023000151450ustar 00000000000000//! An efficient way to check whether a given package has been yanked use std::{ collections::{BTreeSet, HashMap}, time::Duration, }; use crate::{ error::{Error, ErrorKind}, package::{self, Package}, }; pub use tame_index::external::reqwest::ClientBuilder; use tame_index::utils::flock::{FileLock, LockOptions}; enum Index { SparseCached(tame_index::index::SparseIndex), SparseRemote(tame_index::index::AsyncRemoteSparseIndex), } impl Index { #[inline] fn krate( &self, name: &package::Name, lock: &FileLock, ) -> Result, Error> { let name = name.as_str().try_into().map_err(Error::from_tame)?; let res = match self { Self::SparseCached(si) => si.cached_krate(name, lock), Self::SparseRemote(rsi) => rsi.cached_krate(name, lock), } .map_err(Error::from_tame)?; Ok(res) } } /// Provides an efficient way to check if the given package has been yanked. /// /// Operations on crates.io index are rather slow. /// Instead of peforming an index lookup for every version of every crate, /// this implementation looks up each crate only once and caches the result in memory. /// /// Please note that this struct will hold a global Cargo package lock while it exists. /// Cargo operations that download crates (e.g. `cargo update` or even `cargo build`) /// will not be possible while this lock is held. pub struct CachedIndex { index: Index, /// The inner hash map is logically HashMap /// but we don't parse semver because crates.io registry contains invalid semver: /// cache: HashMap>, Error>>, /// The lock we hold on the Cargo cache directory lock: FileLock, } impl CachedIndex { /// Open the local crates.io index /// /// If this opens a git index, it will perform a fetch to get the latest index /// information. /// /// If this is a sparse index, it will be downloaded later on demand. /// /// ## Locking /// /// This function will wait for up to `lock_timeout` for the filesystem lock on the repository. /// It will fail with [`rustsec::Error::LockTimeout`](Error) if the lock is still held /// after that time. /// /// If `lock_timeout` is set to `std::time::Duration::from_secs(0)`, it will not wait at all, /// and instead return an error immediately if it fails to aquire the lock. pub fn fetch(lock_timeout: Duration) -> Result { Self::fetch_inner(None, lock_timeout).map_err(Error::from_tame) } fn fetch_inner( client: Option, lock_timeout: Duration, ) -> Result { let si = tame_index::index::SparseIndex::new(tame_index::IndexLocation::new( tame_index::IndexUrl::crates_io(None, None, None)?, ))?; let lock = acquire_cargo_package_lock(lock_timeout)?; let client_builder = client.unwrap_or_default(); // note: this would need to change if rustsec ever adds the capability // to query other indices that _might_ not support HTTP/2, but // hopefully that would never need to happen let client = client_builder.build().map_err(tame_index::Error::from)?; let index = Index::SparseRemote(tame_index::index::AsyncRemoteSparseIndex::new(si, client)); Ok(CachedIndex { index, cache: Default::default(), lock, }) } /// Open the local crates.io index /// /// If this opens a git index, it allows reading of index entries from the repository. /// /// If this is a sparse index, it only allows reading of index entries that are already cached locally. /// /// ## Locking /// /// This function will wait for up to `lock_timeout` for the filesystem lock on the repository. /// It will fail with [`rustsec::Error::LockTimeout`](Error) if the lock is still held /// after that time. /// /// If `lock_timeout` is set to `std::time::Duration::from_secs(0)`, it will not wait at all, /// and instead return an error immediately if it fails to aquire the lock. pub fn open(lock_timeout: Duration) -> Result { Self::open_inner(lock_timeout).map_err(Error::from_tame) } fn open_inner(lock_timeout: Duration) -> Result { let si = tame_index::index::SparseIndex::new(tame_index::IndexLocation::new( tame_index::IndexUrl::crates_io(None, None, None)?, ))?; let lock = acquire_cargo_package_lock(lock_timeout)?; let index = Index::SparseCached(si); Ok(CachedIndex { index, cache: Default::default(), lock, }) } /// Populates the cache entries for all of the specified crates. fn populate_cache(&mut self, mut packages: BTreeSet<&package::Name>) -> Result<(), Error> { // only look up info on packages that aren't yet cached packages.retain(|pkg| !self.cache.contains_key(pkg)); match &self.index { Index::SparseCached(_) => { for pkg in packages { self.insert(pkg.to_owned(), self.index.krate(pkg, &self.lock)); } } Index::SparseRemote(rsi) => { // Ensure we have a runtime let rt = tame_index::external::tokio::runtime::Runtime::new().map_err(|err| { Error::with_source( ErrorKind::Registry, "unable to start a tokio runtime".to_owned(), err, ) })?; let _rt = rt.enter(); /// This is the timeout per individual crate. If a crate fails to be /// requested for a retriable reason then it will be retried until /// this time limit is reached const REQUEST_TIMEOUT: Option = Some(Duration::from_secs(10)); let results = rsi .krates_blocking( packages .into_iter() .map(|p| p.as_str().to_owned()) .collect(), true, REQUEST_TIMEOUT, &self.lock, ) .map_err(|err| { Error::with_source( ErrorKind::Registry, "unable to acquire tokio runtime".to_owned(), err, ) })?; for (name, res) in results { self.insert( name.parse().expect("this was a package name before"), res.map_err(Error::from_tame), ); } } } Ok(()) } #[inline] fn insert( &mut self, package: package::Name, krate_res: Result, Error>, ) { let krate_res = krate_res.map(|ik| { ik.map(|ik| { ik.versions .into_iter() .map(|v| (v.version.to_string(), v.is_yanked())) .collect() }) }); self.cache.insert(package, krate_res); } /// Is the given package yanked? fn is_yanked(&mut self, package: &Package) -> Result { if !self.cache.contains_key(&package.name) { self.insert( package.name.to_owned(), self.index.krate(&package.name, &self.lock), ); } match &self.cache[&package.name] { Ok(Some(ik)) => match ik.get(&package.version.to_string()) { Some(is_yanked) => Ok(*is_yanked), None => Err(Error::new( ErrorKind::NotFound, format!( "No such version in crates.io index: {} {}", &package.name, &package.version ), )), }, Ok(None) => Err(Error::new( ErrorKind::NotFound, format!("No such crate in crates.io index: {}", &package.name), )), Err(err) => Err(err.clone()), } } /// Iterate over the provided packages, returning a vector of the /// packages which have been yanked. /// /// This function should be called with many packages at once rather than one by one; /// that way it can download the status of a large number of packages at once from the sparse index /// very quickly, orders of magnitude faster than requesting packages one by one. pub fn find_yanked<'a, I>(&mut self, packages: I) -> Vec> where I: IntoIterator, { let mut yanked = Vec::new(); let dedup_packages: BTreeSet<&Package> = packages.into_iter().collect(); let package_names: BTreeSet<&package::Name> = dedup_packages.iter().map(|p| &p.name).collect(); if let Err(e) = self.populate_cache(package_names) { yanked.push(Err(Error::with_source( ErrorKind::Registry, "Failed to download crates.io index. \ Data may be missing or stale when checking for yanked packages." .to_owned(), e, ))); } for package in dedup_packages { match self.is_yanked(package) { Ok(false) => {} // not yanked, nothing to report Ok(true) => yanked.push(Ok(package)), Err(error) => yanked.push(Err(error)), } } yanked } } // We cannot expose these publicly because that would leak the `tame_index` SemVer into the public API fn acquire_cargo_package_lock(lock_timeout: Duration) -> Result { let lock_opts = LockOptions::cargo_package_lock(None)?.exclusive(false); acquire_lock(lock_opts, lock_timeout) } fn acquire_lock( lock_opts: LockOptions<'_>, lock_timeout: Duration, ) -> Result { if lock_timeout == Duration::from_secs(0) { lock_opts.try_lock() } else { lock_opts.lock(|_| Some(lock_timeout)) } } rustsec-0.33.0/src/collection.rs000064400000000000000000000045071046102023000146750ustar 00000000000000//! Package collections use crate::error::{Error, ErrorKind}; use serde::{Deserialize, Serialize, de, ser}; use std::{fmt, str::FromStr}; /// Collections of packages (`crates` vs `rust`). /// /// Advisories are either filed against crates published to /// or packages provided by the Rust language itself (e.g. `std`, `rustdoc`) #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] pub enum Collection { /// Crates published through crates.io Crates, /// Rust core vulnerabilities Rust, } impl Collection { /// Get all collections as a slice pub fn all() -> &'static [Self] { &[Collection::Crates, Collection::Rust] } /// Get a `str` representing the kind of package pub fn as_str(&self) -> &str { match self { Collection::Crates => "crates", Collection::Rust => "rust", } } } impl fmt::Display for Collection { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.as_str()) } } impl FromStr for Collection { type Err = Error; fn from_str(s: &str) -> Result { Ok(match s { "crates" => Collection::Crates, "rust" => Collection::Rust, other => fail!(ErrorKind::Parse, "invalid package type: {}", other), }) } } impl<'de> Deserialize<'de> for Collection { fn deserialize>(deserializer: D) -> Result { use de::Error; let string = String::deserialize(deserializer)?; string.parse().map_err(D::Error::custom) } } impl Serialize for Collection { fn serialize(&self, serializer: S) -> Result { self.to_string().serialize(serializer) } } #[cfg(test)] mod tests { use super::Collection; #[test] fn parse_crate() { let crate_kind = "crates".parse::().unwrap(); assert_eq!(Collection::Crates, crate_kind); assert_eq!("crates", crate_kind.as_str()); } #[test] fn parse_rust() { let rust_kind = "rust".parse::().unwrap(); assert_eq!(Collection::Rust, rust_kind); assert_eq!("rust", rust_kind.as_str()); } #[test] fn parse_other() { assert!("foobar".parse::().is_err()); } } rustsec-0.33.0/src/database/entries.rs000064400000000000000000000116011046102023000157500ustar 00000000000000//! Entries in the advisory database use super::Iter; use crate::{ Map, advisory::{self, Advisory}, collection::Collection, error::{Error, ErrorKind}, map, }; use std::{ ffi::{OsStr, OsString}, path::Path, }; /// "Slots" identify the location in the entries table where a particular /// advisory is located. #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] pub(crate) struct Slot(usize); /// Entries in the advisory database #[derive(Debug, Default)] pub(crate) struct Entries { /// Index of advisory IDs to their slots index: Map, /// Advisory collection advisories: Vec, } impl Entries { /// Create a new database entries collection pub fn new() -> Self { Self::default() } /// Load an advisory from a file and insert it into the database entry table // TODO(tarcieri): factor more of this into `advisory.rs`? pub fn load_file(&mut self, path: &Path) -> Result, Error> { let mut advisory = Advisory::load_file(path)?; // TODO(tarcieri): deprecate and remove legacy TOML-based advisory format let expected_filename = match path.extension().and_then(|ext| ext.to_str()) { Some("md") => OsString::from(format!("{}.md", advisory.metadata.id)), _ => fail!( ErrorKind::Repo, "unexpected file extension: {}", path.display() ), }; // Ensure advisory has the correct filename if path.file_name().unwrap() != expected_filename && !Advisory::is_draft(path) { fail!( ErrorKind::Repo, "expected {} to be named {:?}", path.display(), expected_filename ); } // Ensure advisory is in a directory named after its package let package_dir = path.parent().ok_or_else(|| { Error::new( ErrorKind::Repo, format!("advisory has no parent dir: {}", path.display()), ) })?; if package_dir.file_name().unwrap() != OsStr::new(advisory.metadata.package.as_str()) { fail!( ErrorKind::Repo, "expected {} to be in {} directory (instead of \"{:?}\")", advisory.metadata.id, advisory.metadata.package, package_dir ); } // Get the collection this advisory is part of let collection_dir = package_dir .parent() .ok_or_else(|| { Error::new( ErrorKind::Repo, format!("advisory has no collection: {}", path.display()), ) })? .file_name() .unwrap(); let collection = if collection_dir == OsStr::new(Collection::Crates.as_str()) { Collection::Crates } else if collection_dir == OsStr::new(Collection::Rust.as_str()) { Collection::Rust } else { fail!( ErrorKind::Repo, "invalid package collection: {:?}", collection_dir ); }; match advisory.metadata.collection { Some(c) => { if c != collection { fail!( ErrorKind::Parse, "collection mismatch for {}", &advisory.metadata.id ); } } None => advisory.metadata.collection = Some(collection), } // Ensure placeholder advisories load and parse correctly, but // don't actually insert them into the advisory database if advisory.metadata.id.is_placeholder() { return Ok(None); } let id = advisory.metadata.id.clone(); let slot = Slot(self.advisories.len()); self.advisories.push(advisory); match self.index.entry(id) { map::Entry::Vacant(entry) => { entry.insert(slot); } map::Entry::Occupied(entry) => { fail!(ErrorKind::Parse, "duplicate advisory ID: {}", entry.key()) } } Ok(Some(slot)) } /// Find an advisory by its `advisory::Id` pub fn find_by_id(&self, id: &advisory::Id) -> Option<&Advisory> { self.index.get(id).and_then(|slot| self.get(*slot)) } /// Get an advisory from the database by its [`Slot`] pub fn get(&self, slot: Slot) -> Option<&Advisory> { self.advisories.get(slot.0) } /// Iterate over all of the entries in the database pub fn iter(&self) -> Iter<'_> { self.advisories.iter() } } impl IntoIterator for Entries { type Item = Advisory; type IntoIter = std::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { self.advisories.into_iter() } } rustsec-0.33.0/src/database/index.rs000064400000000000000000000016001046102023000154040ustar 00000000000000//! Database indexes pub use crate::set::Iter; use super::entries::Slot; use crate::{Map, Set, map, package}; /// Database index which maps package names to a set of advisory IDs #[derive(Debug, Default)] pub(crate) struct Index(Map>); impl Index { /// Create a new index pub fn new() -> Self { Self::default() } /// Insert an entry into the index pub fn insert(&mut self, key: &package::Name, slot: Slot) -> bool { let values = match self.0.entry(key.clone()) { map::Entry::Vacant(entry) => entry.insert(Set::new()), map::Entry::Occupied(entry) => entry.into_mut(), }; values.insert(slot) } /// Get an iterator over advisory IDs for a given package name pub fn get(&self, key: &package::Name) -> Option> { self.0.get(key).map(|set| set.iter()) } } rustsec-0.33.0/src/database/query.rs000064400000000000000000000162201046102023000154460ustar 00000000000000//! Queries against the RustSec database //! use crate::{ SourceId, advisory::{Advisory, Severity}, collection::Collection, package::{self, Package}, }; use platforms::target::{Arch, OS}; use semver::Version; /// Queries against the RustSec database #[derive(Clone, Debug)] pub struct Query { /// Collection to query against pub(super) collection: Option, /// Package name to search for pub(super) package_name: Option, /// Package version to search for package_version: Option, /// Source of the package advisories should be matched against package_source: Option, /// Severity threshold (i.e. minimum severity) severity: Option, /// Target architecture target_arch: Vec, /// Target operating system target_os: Vec, /// Year associated with the advisory ID year: Option, /// Query for withdrawn advisories /// (i.e. advisories which were soft-deleted from the database, /// as opposed to yanked crates) withdrawn: Option, /// Query for informational advisories informational: Option, } impl Query { /// Create a new query. /// /// This creates a "wildcard" query with no constraints. Use the various /// builder methods of this type to restrict which advisories match. /// /// Note that this differs from [`Query::default()`], which scopes the /// query to crates (i.e. [`Query::crate_scope`]). /// /// When in doubt, use [`Query::default()`]. pub fn new() -> Self { Self { collection: None, package_name: None, package_version: None, package_source: None, severity: None, target_arch: Default::default(), target_os: Default::default(), year: None, withdrawn: None, informational: None, } } /// Create a new query which uses the default scope rules for crates: /// /// - Only `Collection::Crates` /// - Ignore withdrawn advisories /// - Ignore informational advisories pub fn crate_scope() -> Self { Self::new() .collection(Collection::Crates) .withdrawn(false) .informational(false) } /// Set collection to query against pub fn collection(mut self, collection: Collection) -> Self { self.collection = Some(collection); self } /// Provide a package and use all of its attributes as part of the query #[allow(clippy::assigning_clones)] pub fn package(mut self, package: &Package) -> Self { self.package_name = Some(package.name.clone()); self.package_version = Some(package.version.clone()); self.package_source = package.source.clone(); self } /// Set package name to search for. pub fn package_name(mut self, name: package::Name) -> Self { self.package_name = Some(name); self } /// Set package version to search for pub fn package_version(mut self, version: Version) -> Self { self.package_version = Some(version); self } /// Set package source (e.g. registry) where this package is located pub fn package_source(mut self, source: SourceId) -> Self { self.package_source = Some(source); self } /// Set minimum severity threshold according to the CVSS /// Qualitative Severity Rating Scale. /// /// Vulnerabilities without associated CVSS information will always /// match regardless of what this is set to. pub fn severity(mut self, severity: Severity) -> Self { self.severity = Some(severity); self } /// Set target architectures pub fn target_arch(mut self, arch: Vec) -> Self { self.target_arch = arch; self } /// Set target operating systems pub fn target_os(mut self, os: Vec) -> Self { self.target_os = os; self } /// Query for vulnerabilities occurring in a specific year. pub fn year(mut self, year: u32) -> Self { self.year = Some(year); self } /// Query for withdrawn advisories. /// /// By default they will be omitted from query results. pub fn withdrawn(mut self, setting: bool) -> Self { self.withdrawn = Some(setting); self } /// Query for informational advisories. By default they will be omitted /// from query results. pub fn informational(mut self, setting: bool) -> Self { self.informational = Some(setting); self } /// Does this query match a given advisory? pub fn matches(&self, advisory: &Advisory) -> bool { if let Some(collection) = self.collection && Some(collection) != advisory.metadata.collection { return false; } if let Some(package_name) = &self.package_name && package_name != &advisory.metadata.package { return false; } if let Some(package_version) = &self.package_version && !advisory.versions.is_vulnerable(package_version) { return false; } if let Some(package_source) = &self.package_source { let advisory_source = advisory .metadata .source .as_ref() .cloned() .unwrap_or_default(); // TODO(tarcieri): better source comparison? if advisory_source.kind() != package_source.kind() || advisory_source.url() != package_source.url() { return false; } } if let Some(severity_threshold) = self.severity && let Some(advisory_severity) = advisory.severity() && advisory_severity < severity_threshold { return false; } if let Some(affected) = &advisory.affected { if !affected.arch.is_empty() && !self.target_arch.is_empty() && !self .target_arch .iter() .any(|target_arch| affected.arch.contains(target_arch)) { return false; } if !affected.os.is_empty() && !self.target_os.is_empty() && !self .target_os .iter() .any(|target_os| affected.os.contains(target_os)) { return false; } } if let Some(query_year) = self.year && let Some(advisory_year) = advisory.metadata.id.year() && query_year != advisory_year { return false; } if let Some(withdrawn) = self.withdrawn && withdrawn != advisory.metadata.withdrawn.is_some() { return false; } if let Some(informational) = self.informational && informational != advisory.metadata.informational.is_some() { return false; } true } } impl Default for Query { fn default() -> Query { Query::crate_scope() } } rustsec-0.33.0/src/database.rs000064400000000000000000000137671046102023000143160ustar 00000000000000//! Database containing `RustSec` security advisories mod entries; mod index; mod query; pub use self::query::Query; use self::{entries::Entries, index::Index}; use crate::{ Lockfile, advisory::{self, Advisory}, collection::Collection, error::Error, fs, vulnerability::Vulnerability, }; use std::path::Path; #[cfg(feature = "git")] use crate::repository::git; /// Iterator over entries in the database pub type Iter<'a> = std::slice::Iter<'a, Advisory>; /// Database of RustSec security advisories, indexed both by ID and collection #[derive(Debug)] pub struct Database { /// All advisories in the database advisories: Entries, /// Index of Rust core vulnerabilities rust_index: Index, /// Index of third party crates crate_index: Index, /// Information about the last git commit to the database #[cfg(feature = "git")] latest_commit: Option, } impl Database { /// Open [`Database`] located at the given local path pub fn open(path: &Path) -> Result { let mut advisory_paths = vec![]; for collection in Collection::all() { let collection_path = path.join(collection.as_str()); let collection_entry = match fs::read_dir(&collection_path) { Ok(entries) => entries, // The `Rust` collection is currently not useful to end users Err(_) if collection == &Collection::Rust => continue, Err(err) => return Err(err.into()), }; for dir_entry in collection_entry { let dir_entry = dir_entry?; if !dir_entry.file_type()?.is_dir() { continue; } for advisory_entry in fs::read_dir(dir_entry.path())? { let advisory_path = advisory_entry?.path(); let file_name = advisory_path.file_name().and_then(|f| f.to_str()); // skip dotfiles like .DS_Store if file_name.is_some_and(|f| f.starts_with('.')) { continue; } advisory_paths.push(advisory_path); } } } let mut advisories = Entries::new(); let mut rust_index = Index::new(); let mut crate_index = Index::new(); for path in &advisory_paths { if let Some(slot) = advisories.load_file(path)? { let advisory = advisories.get(slot).unwrap(); match advisory.metadata.collection.unwrap() { Collection::Crates => { crate_index.insert(&advisory.metadata.package, slot); } Collection::Rust => { rust_index.insert(&advisory.metadata.package, slot); } } } } Ok(Self { advisories, crate_index, rust_index, #[cfg(feature = "git")] latest_commit: None, }) } /// Load [`Database`] from the given [`git::Repository`] #[cfg(feature = "git")] pub fn load_from_repo(repo: &git::Repository) -> Result { let mut db = Self::open(repo.path())?; db.latest_commit = Some(repo.latest_commit()?); Ok(db) } /// Fetch the default advisory database from GitHub #[cfg(feature = "git")] pub fn fetch() -> Result { git::Repository::fetch_default_repo().and_then(|repo| Self::load_from_repo(&repo)) } /// Look up an advisory by an advisory ID (e.g. "RUSTSEC-YYYY-XXXX") pub fn get(&self, id: &advisory::Id) -> Option<&Advisory> { self.advisories.find_by_id(id) } /// Query the database according to the given query object pub fn query(&self, query: &Query) -> Vec<&Advisory> { // Use indexes if we know a package name and collection if let Some(name) = &query.package_name && let Some(collection) = query.collection { return match collection { Collection::Crates => self.crate_index.get(name), Collection::Rust => self.rust_index.get(name), } .map(|slots| { slots .map(|slot| self.advisories.get(*slot).unwrap()) .filter(|advisory| query.matches(advisory)) .collect() }) .unwrap_or_else(Vec::new); } self.iter() .filter(|advisory| query.matches(advisory)) .collect() } /// Find vulnerabilities in the provided `Lockfile` which match a given query. pub fn query_vulnerabilities(&self, lockfile: &Lockfile, query: &Query) -> Vec { let mut vulns = vec![]; for package in &lockfile.packages { if package .source .as_ref() .is_none_or(|source| !source.is_default_registry()) { continue; } let advisories = self.query(&query.clone().package(package)); vulns.extend( advisories .iter() .map(|advisory| Vulnerability::new(advisory, package)), ); } vulns } /// Scan for vulnerabilities in the provided `Lockfile`. pub fn vulnerabilities(&self, lockfile: &Lockfile) -> Vec { self.query_vulnerabilities(lockfile, &Query::crate_scope()) } /// Iterate over all of the advisories in the database pub fn iter(&self) -> Iter<'_> { self.advisories.iter() } /// Get information about the latest commit to the repo #[cfg(feature = "git")] pub fn latest_commit(&self) -> Option<&git::Commit> { self.latest_commit.as_ref() } } impl IntoIterator for Database { type Item = Advisory; type IntoIter = std::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { self.advisories.into_iter() } } rustsec-0.33.0/src/error.rs000064400000000000000000000137051046102023000136730ustar 00000000000000//! Error types used by this crate use std::{ fmt::{self, Display}, io, str::Utf8Error, sync::Arc, }; use thiserror::Error; /// Create and return an error with a formatted message macro_rules! fail { ($kind:path, $msg:expr) => { return Err(crate::error::Error::new($kind, $msg)) }; ($kind:path, $fmt:expr, $($arg:tt)+) => { fail!($kind, &format!($fmt, $($arg)+)) }; } /// Result alias with the `rustsec` crate's `Error` type. pub type Result = std::result::Result; /// Error type #[derive(Clone, Debug)] pub struct Error { /// Kind of error kind: ErrorKind, /// Message providing a more specific explanation than `self.kind`. /// /// This may be a complete error by itself, or it may provide context for `self.source`. msg: String, /// Cause of this error. /// /// The specific type of this error should not be considered part of the stable interface of /// this crate. source: Option>, } impl Error { /// Creates a new [`Error`](struct@Error) with the given description. /// /// Do not use this for wrapping [`std::error::Error`]; use [`Error::with_source()`] instead. pub fn new(kind: ErrorKind, description: impl Display) -> Self { // TODO: In a semver-breaking release, deprecate accepting anything but a `String`, // or maybe `AsRef`. This will discourage putting error types in the `description` // position, which makes it impossible to retrieve their `.source()` info. It will also // avoid an unnecessary clone in the common case where `S` is already a `String`. Self { kind, msg: description.to_string(), source: None, } } pub(crate) fn from_source( kind: ErrorKind, source: impl std::error::Error + Send + Sync + 'static, ) -> Self { Self::with_source(kind, source.to_string(), source) } /// Creates a new [`Error`](struct@Error) whose [`std::error::Error::source()`] is `source`. /// /// `msg` should describe the operation which failed so as to give context for how `source` /// is a meaningful error. For example, if `source` is a [`std::io::Error`] from trying to /// read a file, then `msg` should include the path of the file and why the file is relevant. pub fn with_source( kind: ErrorKind, msg: String, source: E, ) -> Self { Self { kind, msg, source: Some(Arc::new(source)), } } /// Obtain the inner `ErrorKind` for this error pub fn kind(&self) -> ErrorKind { self.kind } } impl Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}: {}", self.kind, self.msg) } } impl std::error::Error for Error { /// The lower-level source of this error, if any. /// /// The specific type of the returned error should not be considered part of the stable /// interface of this crate; prefer to use this only for displaying error information. fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match &self.source { Some(boxed_error) => Some(&**boxed_error), None => None, } } } /// Custom error type for this library #[derive(Copy, Clone, Debug, Error, Eq, PartialEq)] #[non_exhaustive] pub enum ErrorKind { /// Invalid argument or parameter #[error("bad parameter")] BadParam, /// An error occurred performing an I/O operation (e.g. network, file) #[error("I/O operation failed")] Io, /// Not found #[error("not found")] NotFound, /// Unable to acquire filesystem lock #[error("unable to acquire filesystem lock")] LockTimeout, /// Couldn't parse response data #[error("parse error")] Parse, /// Registry-related error #[error("registry")] Registry, /// Git operation failed #[error("git operation failed")] Repo, /// Errors related to versions #[error("bad version")] Version, } impl From for Error { fn from(other: Utf8Error) -> Self { Self::from_source(ErrorKind::Parse, other) } } impl From for Error { fn from(other: cargo_lock::Error) -> Self { Self::from_source(ErrorKind::Io, other) } } impl From for Error { fn from(other: fmt::Error) -> Self { Self::from_source(ErrorKind::Io, other) } } impl From for Error { fn from(other: io::Error) -> Self { Self::from_source(ErrorKind::Io, other) } } impl Error { /// Converts from [`tame_index::Error`] to our `Error`. /// /// This is a separate function instead of a `From` impl /// because a trait impl would leak into the public API, /// and we need to keep it private because `tame_index` semver /// will be bumped frequently and we don't want to bump `rustsec` semver /// every time it changes. #[cfg(feature = "git")] pub(crate) fn from_tame(err: tame_index::Error) -> Self { // Separate lock timeouts into their own LockTimeout variant. use tame_index::utils::flock::LockError; match err { tame_index::Error::Lock(lock_err) => match &lock_err.source { LockError::TimedOut | LockError::Contested => { Self::from_source(ErrorKind::LockTimeout, lock_err) } _ => Self::from_source(ErrorKind::Io, lock_err), }, other => Self::from_source(ErrorKind::Registry, other), } } /// Converts from [`toml::de::Error`] to our `Error`. /// /// This is used so rarely that there is no need to `impl From`, /// and this way we can avoid leaking it into the public API. pub(crate) fn from_toml(other: toml::de::Error) -> Self { Self::with_source(ErrorKind::Parse, other.to_string(), other) } } rustsec-0.33.0/src/fixer.rs000064400000000000000000000053361046102023000136600ustar 00000000000000//! Automatically attempt to fix vulnerable dependencies //! //! This module is **experimental**, and its behavior may change in the future. use crate::vulnerability::Vulnerability; use cargo_lock::{Lockfile, Package}; use std::path::{Path, PathBuf}; use std::process::Command; /// Auto-fixer for vulnerable dependencies #[cfg_attr(docsrs, doc(cfg(feature = "fix")))] pub struct Fixer { lockfile: Lockfile, manifest_path: Option, path_to_cargo: Option, } impl Fixer { /// Create a new [`Fixer`] for the given `Cargo.lock` file /// /// `path_to_cargo` defaults to `cargo`, resolved in your `$PATH`. /// /// If the path to `Cargo.toml` is not specified, the `cargo update` command /// will be run in the directory with the `Cargo.lock` file. /// Leaving it blank will fix the entire workspace. pub fn new( cargo_lock: Lockfile, cargo_toml: Option, path_to_cargo: Option, ) -> Self { Self { lockfile: cargo_lock, manifest_path: cargo_toml, path_to_cargo, } } /// Returns a command that calls `cargo update` with the right arguments /// to attempt to fix this vulnerability. /// /// Note that the success of the command does not mean /// the vulnerability was actually fixed! /// It may remain if no semver-compatible fix was available. pub fn get_fix_command(&self, vulnerability: &Vulnerability, dry_run: bool) -> Command { let cargo_path: &Path = self.path_to_cargo.as_deref().unwrap_or(Path::new("cargo")); let pkg_name = &vulnerability.package.name; let mut command = Command::new(cargo_path); command.arg("update"); if let Some(path) = self.manifest_path.as_ref() { command.arg("--manifest-path").arg(path); } if dry_run { command.arg("--dry-run"); } // there can be more than one version of a given package in the lockfile, so we need to iterate over all of them for pkg in self.lockfile.packages.iter().filter(|pkg| { &pkg.name == pkg_name && vulnerability.versions.is_vulnerable(&pkg.version) }) { let pkgid = pkgid(pkg); command.arg(&pkgid); } command } } /// Returns a Cargo unique identifier for a package. /// See `cargo help pkgid` for more info. /// /// We need to pass these to `cargo update` because otherwise /// the package specification will be ambiguous, and it will refuse to do anything. fn pkgid(pkg: &Package) -> String { match pkg.source.as_ref() { Some(source) => format!("{}#{}@{}", source, pkg.name, pkg.version), None => format!("{}@{}", pkg.name, pkg.version), } } rustsec-0.33.0/src/lib.rs000064400000000000000000000027011046102023000133020ustar 00000000000000#![doc = include_str!("../README.md")] #![doc(html_logo_url = "https://raw.githubusercontent.com/RustSec/logos/main/rustsec-logo-lg.png")] #![cfg_attr(docsrs, feature(doc_cfg))] #![forbid(unsafe_code)] #![warn(missing_docs, rust_2018_idioms, unused_qualifications)] #[macro_use] mod error; pub mod advisory; mod collection; pub mod database; mod fixer; pub mod osv; pub mod report; pub mod repository; mod vulnerability; mod warning; pub mod binary_scanning; #[cfg(feature = "git")] #[cfg_attr(docsrs, doc(cfg(feature = "git")))] mod cached_index; #[cfg(feature = "git")] #[cfg_attr(docsrs, doc(cfg(feature = "git")))] pub mod registry { //! Support for interacting with the local crates.io registry index pub use super::cached_index::CachedIndex; } pub use cargo_lock::{self, Lockfile, SourceId, package}; use fs_err as fs; pub use platforms; pub use semver::{self, Version, VersionReq}; pub use crate::{ advisory::Advisory, collection::Collection, database::Database, error::{Error, ErrorKind, Result}, report::Report, vulnerability::Vulnerability, warning::{Warning, WarningKind}, }; pub use crate::fixer::Fixer; #[cfg(feature = "git")] pub use crate::repository::git::Repository; // Use BTreeMap and BTreeSet as our map and set types use std::collections::{BTreeMap as Map, BTreeSet as Set, btree_map as map, btree_set as set}; /// Current version of the RustSec crate pub const VERSION: &str = env!("CARGO_PKG_VERSION"); rustsec-0.33.0/src/osv/advisory.rs000064400000000000000000000274641046102023000152200ustar 00000000000000//! OSV advisories. //! //! It implements the parts of the [OSV schema](https://ossf.github.io/osv-schema) required for //! RustSec. use super::ranges_for_advisory; use crate::advisory::Versions; use crate::{ Advisory, advisory::{Affected, Category, Id, Informational, affected::FunctionPath}, repository::git::{GitModificationTimes, GitPath}, }; use cvss::Cvss; use serde::{Deserialize, Deserializer, Serialize}; use std::str::FromStr; use url::Url; const ECOSYSTEM: &str = "crates.io"; /// Security advisory in the format defined by #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(docsrs, doc(cfg(feature = "osv-export")))] pub struct OsvAdvisory { #[serde(skip_serializing_if = "Option::is_none")] schema_version: Option, id: Id, modified: String, // maybe add an rfc3339 newtype? published: String, // maybe add an rfc3339 newtype? #[serde(skip_serializing_if = "Option::is_none")] withdrawn: Option, // maybe add an rfc3339 newtype? #[serde(default)] aliases: Vec, #[serde(default)] related: Vec, summary: String, details: String, #[serde(default)] severity: Vec, #[serde(default)] affected: Vec, #[serde(default)] references: Vec, #[serde(default)] database_specific: MainOsvDatabaseSpecific, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct OsvPackage { /// Set to a constant identifying crates.io pub(crate) ecosystem: String, /// Crate name pub(crate) name: String, /// https://github.com/package-url/purl-spec derived from the other two #[serde(default)] purl: Option, } impl From<&cargo_lock::Name> for OsvPackage { fn from(package: &cargo_lock::Name) -> Self { OsvPackage { ecosystem: ECOSYSTEM.to_string(), name: package.to_string(), purl: Some("pkg:cargo/".to_string() + package.as_str()), } } } #[derive(Debug, Clone, Serialize, Deserialize)] #[allow(non_camel_case_types)] #[serde(tag = "type", content = "score")] pub enum OsvSeverity { CVSS_V3(cvss::v3::Base), CVSS_V4(cvss::v4::Vector), } impl TryFrom for OsvSeverity { type Error = &'static str; fn try_from(cvss: Cvss) -> Result { match cvss { Cvss::CvssV30(base) => Ok(OsvSeverity::CVSS_V3(base)), Cvss::CvssV31(base) => Ok(OsvSeverity::CVSS_V3(base)), Cvss::CvssV40(vector) => Ok(OsvSeverity::CVSS_V4(vector)), _ => unreachable!(), } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct OsvAffected { pub(crate) package: OsvPackage, ecosystem_specific: Option, database_specific: OsvDatabaseSpecific, ranges: Option>, // FIXME deserialize with deserialize_semver_compat versions: Option>, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct OsvJsonRange { // 'type' is a reserved keyword in Rust #[serde(rename = "type")] kind: String, events: Vec, // 'repo' field is not used because we don't track or export git commit data } impl OsvJsonRange { /// Generates the timeline of the bug being introduced and fixed for the /// [`affected[].ranges[].events`](https://github.com/ossf/osv-schema/blob/main/schema.md#affectedrangesevents-fields) field. fn new(versions: &Versions) -> Self { let ranges = ranges_for_advisory(versions); assert!(!ranges.is_empty()); // zero ranges means nothing is affected, so why even have an advisory? let mut timeline = Vec::new(); for range in ranges { match range.introduced { Some(ver) => timeline.push(OsvTimelineEvent::Introduced(ver)), None => timeline.push(OsvTimelineEvent::Introduced( semver::Version::parse("0.0.0-0").unwrap(), )), } #[allow(clippy::single_match)] match range.fixed { Some(ver) => timeline.push(OsvTimelineEvent::Fixed(ver)), None => (), // "everything after 'introduced' is affected" is implicit in OSV } } Self { kind: "SEMVER".to_string(), events: timeline, } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub enum OsvTimelineEvent { #[serde(rename = "introduced")] #[serde(deserialize_with = "deserialize_semver_compat")] Introduced(semver::Version), #[serde(rename = "fixed")] #[serde(deserialize_with = "deserialize_semver_compat")] Fixed(semver::Version), #[serde(rename = "last_affected")] #[serde(deserialize_with = "deserialize_semver_compat")] LastAffected(semver::Version), } fn deserialize_semver_compat<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { let mut ver = String::deserialize(deserializer)?; match ver.matches('.').count() { 0 => ver.push_str(".0.0"), 1 => ver.push_str(".0"), _ => (), } semver::Version::from_str(&ver).map_err(serde::de::Error::custom) } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct OsvReference { // 'type' is a reserved keyword in Rust #[serde(rename = "type")] pub kind: OsvReferenceKind, pub url: Url, } impl From for OsvReference { fn from(url: Url) -> Self { OsvReference { kind: guess_url_kind(&url), url, } } } #[allow(clippy::upper_case_acronyms)] #[derive(Debug, Clone, Serialize, Deserialize)] pub enum OsvReferenceKind { ADVISORY, #[allow(dead_code)] ARTICLE, REPORT, #[allow(dead_code)] FIX, PACKAGE, WEB, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct OsvEcosystemSpecific { affects: Option, affected_functions: Option>, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct OsvEcosystemSpecificAffected { arch: Vec, os: Vec, /// We include function names only in order to allow changing /// the way versions are specified without an API break functions: Vec, } impl From for OsvEcosystemSpecificAffected { fn from(a: Affected) -> Self { OsvEcosystemSpecificAffected { arch: a.arch, os: a.os, functions: a.functions.into_keys().collect(), } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct OsvDatabaseSpecific { #[serde(default)] categories: Vec, cvss: Option, informational: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct MainOsvDatabaseSpecific { #[serde(default)] license: Option, } impl OsvAdvisory { /// Advisory ID pub fn id(&self) -> &Id { &self.id } /// Publication date pub fn published(&self) -> &str { &self.published } /// Converts a single RustSec advisory to OSV format. /// `path` is the path to the advisory file. It must be relative to the git repository root. pub fn from_rustsec( advisory: Advisory, mod_times: &GitModificationTimes, path: GitPath<'_>, ) -> Self { let metadata = advisory.metadata; // Assemble the URLs to put into 'references' field let mut reference_urls: Vec = Vec::new(); // link to the package on crates.io let package_url = "https://crates.io/crates/".to_owned() + metadata.package.as_str(); reference_urls.push(Url::parse(&package_url).unwrap()); // link to human-readable RustSec advisory let advisory_url = format!( "https://rustsec.org/advisories/{}.html", metadata.id.as_str() ); reference_urls.push(Url::parse(&advisory_url).unwrap()); // primary URL for the issue specified in the advisory if let Some(url) = metadata.url { reference_urls.push(url); } // other references reference_urls.extend(metadata.references); OsvAdvisory { schema_version: None, id: metadata.id, modified: mod_times .for_path(path) .format(&time::format_description::well_known::Rfc3339) .expect("well-known format to heap never fails"), published: rustsec_date_to_rfc3339(&metadata.date), affected: vec![OsvAffected { package: (&metadata.package).into(), ranges: Some(vec![OsvJsonRange::new(&advisory.versions)]), versions: Some(vec![]), ecosystem_specific: Some(OsvEcosystemSpecific { affects: Some(advisory.affected.unwrap_or_default().into()), affected_functions: None, }), database_specific: OsvDatabaseSpecific { categories: metadata.categories, cvss: metadata.cvss.clone(), informational: metadata.informational, }, }], withdrawn: metadata.withdrawn.map(|d| rustsec_date_to_rfc3339(&d)), aliases: metadata.aliases, related: metadata.related, summary: metadata.title, severity: match metadata.cvss { Some(cvss) => match cvss.try_into() { Ok(sev) => vec![sev], Err(_) => vec![], }, None => vec![], }, details: metadata.description, references: osv_references(reference_urls), database_specific: MainOsvDatabaseSpecific { license: Some(metadata.license.spdx().to_string()), }, } } /// Try to extract RustSec alias id from OSV advisory metadata pub fn rustsec_refs_imported(&self) -> Vec { let mut refs: Vec = self .references .iter() .filter(|r| { r.url .as_str() .starts_with("https://rustsec.org/advisories/") }) .map(|r| Id::from_str(&r.url.as_str()[31..48]).expect("Invalid rustsec url")) .collect(); refs.sort(); refs.dedup(); refs } /// Get crates in crates.io ecosystem referenced in this advisory pub fn crates(&self) -> Vec { let mut res: Vec = self .affected .iter() .filter_map(|a| { if a.package.ecosystem == ECOSYSTEM { Some(a.package.name.clone()) } else { None } }) .collect(); res.sort(); res.dedup(); res } /// Get aliases ids pub fn aliases(&self) -> &[Id] { self.aliases.as_slice() } /// Is this advisory withdrawn? pub fn withdrawn(&self) -> bool { self.withdrawn.is_some() } } fn osv_references(references: Vec) -> Vec { references.into_iter().map(|u| u.into()).collect() } fn guess_url_kind(url: &Url) -> OsvReferenceKind { let str = url.as_str(); if (str.contains("://github.com/") || str.contains("://gitlab.")) && str.contains("/issues/") { OsvReferenceKind::REPORT // the check for "/advisories/" matches both RustSec and GHSA URLs } else if str.contains("/advisories/") || str.contains("://cve.mitre.org/") { OsvReferenceKind::ADVISORY } else if str.contains("://crates.io/crates/") { OsvReferenceKind::PACKAGE } else { OsvReferenceKind::WEB } } fn rustsec_date_to_rfc3339(d: &crate::advisory::Date) -> String { format!("{}-{:02}-{:02}T12:00:00Z", d.year(), d.month(), d.day()) } rustsec-0.33.0/src/osv/range.rs000064400000000000000000000013211046102023000144340ustar 00000000000000use semver::Version; /// A range of affected versions. /// /// If any of the bounds is unspecified, that means ALL versions /// in that direction are affected. /// /// This format is defined by #[derive(Debug, Clone)] pub struct OsvRange { /// Inclusive pub introduced: Option, /// Exclusive pub fixed: Option, } impl OsvRange { /// Returns true if the given version is affected pub fn affects(&self, v: &Version) -> bool { (match &self.introduced { None => true, Some(start_v) => v >= start_v, }) && (match &self.fixed { None => true, Some(end_v) => v < end_v, }) } } rustsec-0.33.0/src/osv/ranges_for_advisory.rs000064400000000000000000000210321046102023000174060ustar 00000000000000//! Ranges for advisories. use super::{ range::OsvRange, unaffected_range::{Bound, UnaffectedRange}, }; use crate::{ Error, advisory::{Versions, versions::RawVersions}, }; use semver::{Prerelease, Version, VersionReq}; /// Returns OSV ranges for all affected versions in the given advisory. /// /// OSV ranges are `[start, end)` intervals, and anything included in them is affected. /// Panics if the ranges are malformed or range specification syntax is not supported, /// since that has been validated on deserialization. pub fn ranges_for_advisory(versions: &Versions) -> Vec { unaffected_to_osv_ranges(versions.unaffected(), versions.patched()).unwrap() } /// Returns OSV ranges for all affected versions in the given advisory. /// /// OSV ranges are `[start, end)` intervals, and anything included in them is affected. /// Errors if the ranges are malformed or range specification syntax is not supported. pub(crate) fn ranges_for_unvalidated_advisory( versions: &RawVersions, ) -> Result, Error> { unaffected_to_osv_ranges(&versions.unaffected, &versions.patched) } /// Converts a list of unaffected ranges to a range of affected OSV ranges. /// /// Since OSV ranges are a negation of the UNaffected ranges that RustSec stores, /// the entire list has to be passed at once, both patched and unaffected ranges. fn unaffected_to_osv_ranges( unaffected_req: &[VersionReq], patched_req: &[VersionReq], ) -> Result, Error> { // Consolidate ranges for all versions that aren't affected let mut unaffected: Vec = Vec::new(); for req in unaffected_req { unaffected.push(req.try_into()?); } for req in patched_req { unaffected.push(req.try_into()?); } // Edge case: no unaffected ranges specified. That means that ALL versions are affected. if unaffected.is_empty() { return Ok(vec![OsvRange { introduced: None, fixed: None, }]); } // Verify that the incoming ranges do not overlap. This is required for the correctness of the algorithm. // The current impl has quadratic complexity, but since we have like 4 ranges at most, this doesn't matter. // We can optimize this later if it starts showing up on profiles. for (idx, a) in unaffected[..unaffected.len() - 1].iter().enumerate() { for b in unaffected[idx + 1..].iter() { if a.overlaps(b) { fail!( crate::ErrorKind::BadParam, format!("Overlapping version ranges: {} and {}", a, b) ); } } } // Now that we know that unaffected ranges don't overlap, we can simply order them by any of the bounds // and that will result in all ranges being ordered let mut unaffected = unaffected.to_vec(); use std::cmp::Ordering; unaffected.sort_unstable_by(|a, b| { match (a.start().version(), b.start().version()) { (None, _) => Ordering::Less, (_, None) => Ordering::Greater, (Some(v1), Some(v2)) => { assert!(v1 != v2); // should be already ruled out by overlap checks, but verify just in case v1.cmp(v2) } } }); let mut result = Vec::new(); // Handle the start bound of the first element, since it's not handled by the main loop match &unaffected.first().unwrap().start() { Bound::Unbounded => {} // Nothing to do Bound::Exclusive(v) => result.push(OsvRange { introduced: None, fixed: Some(increment(v)), }), Bound::Inclusive(v) => result.push(OsvRange { introduced: None, fixed: Some(v.clone()), }), } // Iterate over pairs of UnaffectedRange and turn the space between each pair into an OsvRange for r in unaffected.windows(2) { let start = match &r[0].end() { // ranges are ordered, so Unbounded can only appear in the first or last element, which are handled outside the loop Bound::Unbounded => unreachable!(), Bound::Exclusive(v) => v.clone(), Bound::Inclusive(v) => increment(v), }; let end = match &r[1].start() { Bound::Unbounded => unreachable!(), Bound::Exclusive(v) => increment(v), Bound::Inclusive(v) => v.clone(), }; result.push(OsvRange { introduced: Some(start), fixed: Some(end), }); } // Handle the end bound of the last element, since it's not handled by the main loop match &unaffected.last().unwrap().end() { Bound::Unbounded => {} // Nothing to do Bound::Exclusive(v) => result.push(OsvRange { introduced: Some(v.clone()), fixed: None, }), Bound::Inclusive(v) => result.push(OsvRange { introduced: Some(increment(v)), fixed: None, }), } Ok(result) } /// Returns the lowest possible version greater than the input according to /// [the SemVer 2.0 precedence rules](https://semver.org/#spec-item-11). /// This is not the intuitive "increment": this function returns a pre-release version! /// E.g. "1.2.3" is transformed to "1.2.4-0". fn increment(v: &Version) -> Version { let mut v = v.clone(); v.build = Default::default(); // Clear any build metadata, it's not used to determine precedence if v.pre.is_empty() { // Not a pre-release. // Increment the last version and add "0" as pre-release specifier. // E.g. "1.2.3" is transformed to "1.2.4-0". // This seems to be the lowest possible version that's above 1.2.3 according to semver 2.0 spec v.patch += 1; v.pre = Prerelease::new("0").unwrap(); } else { // It's a pre-release. // We take advantage of the fact that a version with more pre-release components // has a higher precedence than one with fewer components, // but *only* when all the preceding fields are equal, // as per https://semver.org/#spec-item-11 // Therefore appending a new component with the lowest possible value // creates the next version according to semver precedence rules. // This is confirmed by the unit tests below. let incremented = v.pre.to_string() + ".0"; v.pre = Prerelease::new(&incremented).unwrap(); } v } #[cfg(test)] mod tests { use super::increment; use semver::Version; #[test] fn increment_simple() { let input = Version::parse("1.2.3").unwrap(); let incremented = increment(&input); assert!(incremented > input); let expected = Version::parse("1.2.4-0").unwrap(); assert_eq!(expected, incremented); } #[test] fn increment_prerelease_numeric() { let input = Version::parse("1.2.3-9").unwrap(); let incremented = increment(&input); assert!(incremented > input); let intuitively_next = Version::parse("1.2.3-10").unwrap(); assert!(incremented < intuitively_next); let lots_of_zeroes = Version::parse("1.2.3-9.0.0.0").unwrap(); assert!(incremented < lots_of_zeroes); let expected = Version::parse("1.2.3-9.0").unwrap(); assert_eq!(expected, incremented); } #[test] fn increment_prerelease_numeric_multipart() { let input = Version::parse("1.2.3-4.5.6").unwrap(); let incremented = increment(&input); assert!(incremented > input); let intuitively_next = Version::parse("1.2.3-4.5.7").unwrap(); assert!(incremented < intuitively_next); let expected = Version::parse("1.2.3-4.5.6.0").unwrap(); assert_eq!(expected, incremented); } #[test] fn increment_prerelease_alphanumeric() { let input = Version::parse("1.2.3-alpha1").unwrap(); let incremented = increment(&input); assert!(incremented > input); let intuitively_next = Version::parse("1.2.3-alpha2").unwrap(); assert!(incremented < intuitively_next); let lexicographically_next = Version::parse("1.2.3-alpha1-").unwrap(); assert!(incremented < lexicographically_next); let expected = Version::parse("1.2.3-alpha1.0").unwrap(); assert_eq!(expected, incremented); } #[test] fn increment_prerelease_textual_multipart() { let input = Version::parse("1.2.3-alpha.1.foo").unwrap(); let incremented = increment(&input); assert!(incremented > input); let expected = Version::parse("1.2.3-alpha.1.foo.0").unwrap(); assert_eq!(expected, incremented); } } rustsec-0.33.0/src/osv/unaffected_range.rs000064400000000000000000000441201046102023000166240ustar 00000000000000//! This is an intermediate representation used for converting from //! Cargo-style version selectors (`>=`, `^`, `<`, etc) to OSV ranges. //! It is an implementation detail and is not exported outside OSV module. use crate::{Error, ErrorKind::BadParam}; use semver::{Comparator, Op, Prerelease, Version}; use std::fmt::Display; #[derive(Clone, PartialEq, Eq, Hash, Debug)] pub(crate) enum Bound { Unbounded, Exclusive(Version), Inclusive(Version), } impl Bound { /// Returns just the version, ignoring whether the bound is inclusive or exclusive pub fn version(&self) -> Option<&Version> { match &self { Bound::Unbounded => None, Bound::Exclusive(v) => Some(v), Bound::Inclusive(v) => Some(v), } } /// The handling of `Bound::Unbounded` in this function assumes that /// the first bound is start of a range, and the other bound is the end of a range. /// **Make sure** this is the way you call it. /// This is also why we don't define PartialOrd. fn less_or_equal(&self, other: &Bound) -> bool { // It's defined on Bound and not UnaffectedRange // so that it could be used on bounds from different ranges. let start = self; let end = other; // This appears to be a false positive in Clippy: // https://github.com/rust-lang/rust-clippy/issues/7383 #[allow(clippy::if_same_then_else)] if start == &Bound::Unbounded || end == &Bound::Unbounded { true } else if start.version().unwrap() < end.version().unwrap() { true } else { match (&start, &end) { (Bound::Inclusive(v_start), Bound::Inclusive(v_end)) => v_start == v_end, (_, _) => false, } } } } /// A range of unaffected versions, used by either `patched` /// or `unaffected` fields in the security advisory. /// Bounds may be inclusive or exclusive. /// `start` is guaranteed to be less than or equal to `end`. /// If `start == end`, both bounds must be inclusive. #[derive(Clone, PartialEq, Eq, Hash, Debug)] pub(crate) struct UnaffectedRange { start: Bound, end: Bound, } impl UnaffectedRange { pub fn new(start: Bound, end: Bound) -> Result { if start.less_or_equal(&end) { Ok(UnaffectedRange { start, end }) } else { Err(Error::new( BadParam, "Invalid range: start must be <= end; if equal, both bounds must be inclusive", )) } } pub fn start(&self) -> &Bound { &self.start } pub fn end(&self) -> &Bound { &self.end } pub fn overlaps(&self, other: &UnaffectedRange) -> bool { // range check for well-formed ranges is `(Start1 <= End2) && (Start2 <= End1)` self.start.less_or_equal(&other.end) && other.start.less_or_equal(&self.end) } } impl Display for UnaffectedRange { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match &self.start { Bound::Unbounded => f.write_str("[0"), Bound::Exclusive(v) => f.write_fmt(format_args!("({v}")), Bound::Inclusive(v) => f.write_fmt(format_args!("[{v}")), }?; f.write_str(", ")?; match &self.end { Bound::Unbounded => f.write_str("∞)"), Bound::Exclusive(v) => f.write_fmt(format_args!("{v})")), Bound::Inclusive(v) => f.write_fmt(format_args!("{v}]")), } } } /// To keep the algorithm simple, we impose several constraints: /// /// 1. There is at most one upper and at most one lower bound in each range. /// Stuff like `>= 1.0, >= 2.0` is nonsense and is not supported. /// 2. If the requirement is "1.0" or "^1.0" that defines both the lower and upper bound, /// it is the only one in its range. /// /// If any of those constraints are unmet, an error will be returned. impl TryFrom<&semver::VersionReq> for UnaffectedRange { type Error = Error; fn try_from(input: &semver::VersionReq) -> Result { if input.comparators.len() > 2 { fail!( BadParam, format!("Too many comparators in version specification: {}", input) ); } // If one of the bounds is not specified, it's unbounded, // e.g. ["> 0.5"] means the lower bound is 0.5 and there is no upper bound let mut start = Bound::Unbounded; let mut end = Bound::Unbounded; for comparator in &input.comparators { match comparator.op { // Full list of operators supported by Cargo can be found here: // https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html // One of the Cargo developers has confirmed that the list is complete: // https://internals.rust-lang.org/t/changing-cargo-semver-compatibility-for-pre-releases/14820/14 // However, `semver` crate recognizes more operators than Cargo supports Op::Greater => { if start != Bound::Unbounded { fail!( BadParam, format!("More than one lower bound in the same range: {}", input) ); } start = Bound::Exclusive(comp_to_ver(comparator)); } Op::GreaterEq => { if start != Bound::Unbounded { fail!( BadParam, format!("More than one lower bound in the same range: {}", input) ); } start = Bound::Inclusive(comp_to_ver(comparator)); } Op::Less => { if end != Bound::Unbounded { fail!( BadParam, format!("More than one upper bound in the same range: {}", input) ); } end = Bound::Exclusive(comp_to_ver(comparator)); } Op::LessEq => { if end != Bound::Unbounded { fail!( BadParam, format!("More than one upper bound in the same range: {}", input) ); } end = Bound::Inclusive(comp_to_ver(comparator)); } Op::Exact => { if input.comparators.len() != 1 { fail!( BadParam, "Selectors that define an exact version (e.g. '=1.0') must be alone in their range" ); } start = Bound::Inclusive(comp_to_ver(comparator)); end = Bound::Inclusive(comp_to_ver(comparator)); } Op::Caret => { if input.comparators.len() != 1 { fail!( BadParam, "Selectors that define both the upper and lower bound (e.g. '^1.0') must be alone in their range" ); } let start_version = comp_to_ver(comparator); let mut end_version = if start_version.major == 0 { match (comparator.minor, comparator.patch) { // ^0.0.x (Some(0), Some(patch)) => Version::new(0, 0, patch + 1), // ^0.x and ^0.x.x (Some(minor), _) => Version::new(0, minor + 1, 0), // ^0 (None, None) => Version::new(1, 0, 0), (None, Some(_)) => unreachable!( "Comparator specifies patch version but not minor version" ), } } else { Version::new(&start_version.major + 1, 0, 0) }; // -0 is the lowest possible prerelease. // If we didn't append it, e.g. ^1.0.0 would match 2.0.0-alpha1 end_version.pre = Prerelease::new("0").unwrap(); start = Bound::Inclusive(start_version); end = Bound::Exclusive(end_version); } Op::Tilde => { if input.comparators.len() != 1 { fail!( BadParam, "Selectors that define both the upper and lower bound (e.g. '~1.0') must be alone in their range" ); } let start_version = comp_to_ver(comparator); let major = comparator.major; let mut end_version = match (comparator.minor, comparator.patch) { (None, None) => Version::new(major + 1, 0, 0), (Some(minor), _) => Version::new(major, minor + 1, 0), (None, Some(_)) => { unreachable!("Comparator specifies patch version but not minor version") } }; // -0 is the lowest possible prerelease. // If we didn't append it, e.g. ~1.2 would match 1.3.0-alpha1 end_version.pre = Prerelease::new("0").unwrap(); start = Bound::Inclusive(start_version); end = Bound::Exclusive(end_version); } _ => { // the struct is non-exhaustive, we have to do this fail!( BadParam, "Unsupported operator in version specification: '{}'", comparator ); } } } UnaffectedRange::new(start, end) } } /// Strips comparison operators from a Comparator and turns it into a Version. /// Would have been better implemented by `into` but these are foreign types fn comp_to_ver(c: &Comparator) -> Version { Version { major: c.major, minor: c.minor.unwrap_or(0), patch: c.patch.unwrap_or(0), pre: c.pre.clone(), build: Default::default(), } } #[cfg(test)] mod tests { use super::*; use semver::VersionReq; #[test] fn both_unbounded() { let range1 = UnaffectedRange { start: Bound::Unbounded, end: Bound::Unbounded, }; let range2 = UnaffectedRange { start: Bound::Unbounded, end: Bound::Unbounded, }; assert!(range1.overlaps(&range2)); assert!(range2.overlaps(&range1)); } #[test] fn barely_not_overlapping() { let range1 = UnaffectedRange { start: Bound::Inclusive(Version::parse("1.2.3").unwrap()), end: Bound::Unbounded, }; let range2 = UnaffectedRange { start: Bound::Unbounded, end: Bound::Exclusive(Version::parse("1.2.3").unwrap()), }; assert!(!range1.overlaps(&range2)); assert!(!range2.overlaps(&range1)); } #[test] fn barely_overlapping() { let range1 = UnaffectedRange { start: Bound::Inclusive(Version::parse("1.2.3").unwrap()), end: Bound::Unbounded, }; let range2 = UnaffectedRange { start: Bound::Unbounded, end: Bound::Inclusive(Version::parse("1.2.3").unwrap()), }; assert!(range1.overlaps(&range2)); assert!(range2.overlaps(&range1)); } #[test] fn clearly_not_overlapping() { let range1 = UnaffectedRange { start: Bound::Inclusive(Version::parse("0.1.0").unwrap()), end: Bound::Inclusive(Version::parse("0.3.0").unwrap()), }; let range2 = UnaffectedRange { start: Bound::Inclusive(Version::parse("1.1.0").unwrap()), end: Bound::Inclusive(Version::parse("1.3.0").unwrap()), }; assert!(!range1.overlaps(&range2)); assert!(!range2.overlaps(&range1)); } #[test] fn clearly_overlapping() { let range1 = UnaffectedRange { start: Bound::Inclusive(Version::parse("0.1.0").unwrap()), end: Bound::Inclusive(Version::parse("1.1.0").unwrap()), }; let range2 = UnaffectedRange { start: Bound::Inclusive(Version::parse("0.2.0").unwrap()), end: Bound::Inclusive(Version::parse("1.3.0").unwrap()), }; assert!(range1.overlaps(&range2)); assert!(range2.overlaps(&range1)); } #[test] fn exact_requirement_10() { let input = VersionReq::parse("=1.0").unwrap(); let expected = UnaffectedRange { start: Bound::Inclusive(Version::parse("1.0.0").unwrap()), end: Bound::Inclusive(Version::parse("1.0.0").unwrap()), }; let result: UnaffectedRange = (&input).try_into().unwrap(); assert_eq!(expected, result); } // Test data for caret requirements is taken from the Cargo spec // https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#caret-requirements // but adjusted to correctly handle pre-releases under semver precedence rules: // https://semver.org/#spec-item-11 #[test] fn caret_requirement_123() { let input = VersionReq::parse("^1.2.3").unwrap(); let expected = UnaffectedRange { start: Bound::Inclusive(Version::parse("1.2.3").unwrap()), end: Bound::Exclusive(Version::parse("2.0.0-0").unwrap()), }; let result: UnaffectedRange = (&input).try_into().unwrap(); assert_eq!(expected, result); } #[test] fn caret_requirement_12() { let input = VersionReq::parse("^1.2").unwrap(); let expected = UnaffectedRange { start: Bound::Inclusive(Version::parse("1.2.0").unwrap()), end: Bound::Exclusive(Version::parse("2.0.0-0").unwrap()), }; let result: UnaffectedRange = (&input).try_into().unwrap(); assert_eq!(expected, result); } #[test] fn caret_requirement_1() { let input = VersionReq::parse("^1").unwrap(); let expected = UnaffectedRange { start: Bound::Inclusive(Version::parse("1.0.0").unwrap()), end: Bound::Exclusive(Version::parse("2.0.0-0").unwrap()), }; let result: UnaffectedRange = (&input).try_into().unwrap(); assert_eq!(expected, result); } #[test] fn caret_requirement_023() { let input = VersionReq::parse("^0.2.3").unwrap(); let expected = UnaffectedRange { start: Bound::Inclusive(Version::parse("0.2.3").unwrap()), end: Bound::Exclusive(Version::parse("0.3.0-0").unwrap()), }; let result: UnaffectedRange = (&input).try_into().unwrap(); assert_eq!(expected, result); } #[test] fn caret_requirement_02() { let input = VersionReq::parse("^0.2").unwrap(); let expected = UnaffectedRange { start: Bound::Inclusive(Version::parse("0.2.0").unwrap()), end: Bound::Exclusive(Version::parse("0.3.0-0").unwrap()), }; let result: UnaffectedRange = (&input).try_into().unwrap(); assert_eq!(expected, result); } #[test] fn caret_requirement_003() { let input = VersionReq::parse("^0.0.3").unwrap(); let expected = UnaffectedRange { start: Bound::Inclusive(Version::parse("0.0.3").unwrap()), end: Bound::Exclusive(Version::parse("0.0.4-0").unwrap()), }; let result: UnaffectedRange = (&input).try_into().unwrap(); assert_eq!(expected, result); } #[test] fn caret_requirement_00() { let input = VersionReq::parse("^0.0").unwrap(); let expected = UnaffectedRange { start: Bound::Inclusive(Version::parse("0.0.0").unwrap()), end: Bound::Exclusive(Version::parse("0.1.0-0").unwrap()), }; let result: UnaffectedRange = (&input).try_into().unwrap(); assert_eq!(expected, result); } #[test] fn caret_requirement_0() { let input = VersionReq::parse("^0").unwrap(); let expected = UnaffectedRange { start: Bound::Inclusive(Version::parse("0.0.0").unwrap()), end: Bound::Exclusive(Version::parse("1.0.0-0").unwrap()), }; let result: UnaffectedRange = (&input).try_into().unwrap(); assert_eq!(expected, result); } // Test data for tilde requirements is taken from the Cargo spec // https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#tilde-requirements // but adjusted to correctly handle pre-releases under semver precedence rules: // https://semver.org/#spec-item-11 #[test] fn tilde_requirement_123() { let input = VersionReq::parse("~1.2.3").unwrap(); let expected = UnaffectedRange { start: Bound::Inclusive(Version::parse("1.2.3").unwrap()), end: Bound::Exclusive(Version::parse("1.3.0-0").unwrap()), }; let result: UnaffectedRange = (&input).try_into().unwrap(); assert_eq!(expected, result); } #[test] fn tilde_requirement_12() { let input = VersionReq::parse("~1.2").unwrap(); let expected = UnaffectedRange { start: Bound::Inclusive(Version::parse("1.2.0").unwrap()), end: Bound::Exclusive(Version::parse("1.3.0-0").unwrap()), }; let result: UnaffectedRange = (&input).try_into().unwrap(); assert_eq!(expected, result); } #[test] fn tilde_requirement_1() { let input = VersionReq::parse("~1").unwrap(); let expected = UnaffectedRange { start: Bound::Inclusive(Version::parse("1.0.0").unwrap()), end: Bound::Exclusive(Version::parse("2.0.0-0").unwrap()), }; let result: UnaffectedRange = (&input).try_into().unwrap(); assert_eq!(expected, result); } } rustsec-0.33.0/src/osv.rs000064400000000000000000000014461046102023000133500ustar 00000000000000//! Provides support for exporting to the interchange format defined by //! //! //! We also use OSV-style ranges for version matching in RustSec crate //! because it allows handling pre-releases correctly, //! which `semver` crate does not allow doing directly. //! //! See #[cfg(feature = "osv-export")] mod advisory; #[cfg(feature = "osv-export")] pub use advisory::OsvAdvisory; // The rest are enabled unconditionally because the OSV range format // is used for determining whether a given version is affected or not mod range; mod ranges_for_advisory; mod unaffected_range; pub use range::OsvRange; pub use ranges_for_advisory::ranges_for_advisory; pub(crate) use ranges_for_advisory::ranges_for_unvalidated_advisory; rustsec-0.33.0/src/report.rs000064400000000000000000000145371046102023000140610ustar 00000000000000//! Vulnerability report generator //! //! These types map directly to the JSON report generated by `cargo-audit`, //! but also provide the core reporting functionality used in general. use crate::{ Lockfile, Map, advisory, database::{Database, Query}, map, platforms::target::{Arch, OS}, vulnerability::Vulnerability, warning::{self, Warning}, }; use serde::{Deserialize, Serialize}; /// Vulnerability report for a given lockfile #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Report { /// Information about the advisory database #[cfg(feature = "git")] #[cfg_attr(docsrs, doc(cfg(feature = "git")))] pub database: DatabaseInfo, /// Information about the audited lockfile pub lockfile: LockfileInfo, /// Settings used when generating report pub settings: Settings, /// Vulnerabilities detected in project pub vulnerabilities: VulnerabilityInfo, /// Warnings about dependencies (from e.g. informational advisories) pub warnings: WarningInfo, } impl Report { /// Generate a report for the given advisory database and lockfile pub fn generate(db: &Database, lockfile: &Lockfile, settings: &Settings) -> Self { let vulnerabilities = db .query_vulnerabilities(lockfile, &settings.query()) .into_iter() .filter(|vuln| !settings.ignore.contains(&vuln.advisory.id)) .collect(); let warnings = find_warnings(db, lockfile, settings); Self { #[cfg(feature = "git")] database: DatabaseInfo::new(db), lockfile: LockfileInfo::new(lockfile), settings: settings.clone(), vulnerabilities: VulnerabilityInfo::new(vulnerabilities), warnings, } } } /// Options to use when generating the report #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct Settings { /// CPU architecture pub target_arch: Vec, /// Operating system pub target_os: Vec, /// Severity threshold to alert at pub severity: Option, /// List of advisory IDs to ignore pub ignore: Vec, /// Types of informational advisories to generate warnings for pub informational_warnings: Vec, } impl Settings { /// Get a query which corresponds to the configured report settings. /// Note that queries can't filter ignored advisories, so this happens in /// a separate pass pub fn query(&self) -> Query { let mut query = Query::crate_scope() .target_arch(self.target_arch.clone()) .target_os(self.target_os.clone()); if let Some(severity) = self.severity { query = query.severity(severity); } query } } /// Information about the advisory database #[cfg(feature = "git")] #[cfg_attr(docsrs, doc(cfg(feature = "git")))] #[derive(Clone, Debug, Deserialize, Serialize)] pub struct DatabaseInfo { /// Number of advisories in the database #[serde(rename = "advisory-count")] pub advisory_count: usize, /// Git commit hash for the last commit to the database #[serde(rename = "last-commit")] pub last_commit: Option, /// Date when the advisory database was last committed to #[serde(rename = "last-updated", with = "time::serde::rfc3339::option")] pub last_updated: Option, } #[cfg(feature = "git")] impl DatabaseInfo { /// Create database information from the advisory db pub fn new(db: &Database) -> Self { Self { advisory_count: db.iter().count(), last_commit: db.latest_commit().map(|c| c.commit_id.to_hex()), last_updated: db.latest_commit().map(|c| c.timestamp), } } } /// Information about `Cargo.lock` #[derive(Clone, Debug, Deserialize, Serialize)] pub struct LockfileInfo { /// Number of dependencies in the lock file #[serde(rename = "dependency-count")] dependency_count: usize, } impl LockfileInfo { /// Create lockfile information from the given lockfile pub fn new(lockfile: &Lockfile) -> Self { Self { dependency_count: lockfile.packages.len(), } } } /// Information about detected vulnerabilities #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct VulnerabilityInfo { /// Were any vulnerabilities found? pub found: bool, /// Number of vulnerabilities found pub count: usize, /// List of detected vulnerabilities pub list: Vec, } impl VulnerabilityInfo { /// Create new vulnerability info pub fn new(list: Vec) -> Self { Self { found: !list.is_empty(), count: list.len(), list, } } } /// Information about warnings pub type WarningInfo = Map>; /// Find warnings from the given advisory [`Database`] and [`Lockfile`] pub fn find_warnings(db: &Database, lockfile: &Lockfile, settings: &Settings) -> WarningInfo { let query = settings.query().informational(true); let mut warnings = WarningInfo::default(); // TODO(tarcieri): abstract `Cargo.lock` query logic between vulnerabilities/warnings for advisory_vuln in db.query_vulnerabilities(lockfile, &query) { let advisory = &advisory_vuln.advisory; if settings.ignore.contains(&advisory.id) { continue; } if settings .informational_warnings .iter() .any(|info| Some(info) == advisory.informational.as_ref()) { let warning_kind = match advisory .informational .as_ref() .expect("informational advisory") .warning_kind() { Some(kind) => kind, None => continue, }; let warning = Warning::new( warning_kind, &advisory_vuln.package, Some(advisory.clone()), advisory_vuln.affected.clone(), Some(advisory_vuln.versions.clone()), ); match warnings.entry(warning.kind) { map::Entry::Occupied(entry) => (*entry.into_mut()).push(warning), map::Entry::Vacant(entry) => { entry.insert(vec![warning]); } } } } warnings } rustsec-0.33.0/src/repository/git/commit.rs000064400000000000000000000144171046102023000170350ustar 00000000000000//! Commits to the advisory DB git repository use crate::{ error::{Error, ErrorKind}, repository::{ git::{CommitHash, Repository}, signature::Signature, }, }; use std::time::{Duration, SystemTime}; /// Number of days after which the repo will be considered stale /// (90 days) const STALE_AFTER: Duration = Duration::from_secs(90 * 86400); /// Information about a commit to the Git repository #[cfg_attr(docsrs, doc(cfg(feature = "git")))] #[derive(Debug)] pub struct Commit { /// ID (i.e. SHA-1 hash) of the latest commit pub commit_id: CommitHash, /// Information about the author of a commit pub author: String, /// Summary message for the commit pub summary: String, /// Commit time in number of seconds since the UNIX epoch pub timestamp: time::OffsetDateTime, /// Signature on the commit (mandatory for Repository::fetch) // TODO: actually verify signatures pub signature: Option, /// Signed data to verify along with this commit signed_data: Option>, } impl Commit { /// Get information about HEAD pub(crate) fn from_repo_head(repo: &Repository) -> Result { let commit = repo.repo.head_commit().map_err(|err| { Error::with_source( ErrorKind::Repo, "unable to locate head commit".to_owned(), err, ) })?; // Since we are pulling multiple pieces from the commit it's better to do this once let cref = commit.decode().map_err(|err| { Error::with_source( ErrorKind::Repo, "unable to decode commit information".to_owned(), err, ) })?; let commit_id = commit.id; let time = cref.time().map_err(|err| { Error::with_source( ErrorKind::Repo, "unable to parse commit time".to_owned(), err, ) })?; let timestamp = crate::repository::git::gix_time_to_time(time); let author = { let sig = cref.author().map_err(|err| { Error::with_source( ErrorKind::Repo, "unable to parse commit author".to_owned(), err, ) })?; format!("{} <{}>", sig.name, sig.email) }; let summary = cref.message_summary().to_string(); if summary.is_empty() { return Err(Error::new( ErrorKind::Repo, format!("no commit summary for {}", commit_id), )); } let commit_id = CommitHash::from_gix(commit_id); let (signature, signed_data) = if let Some(sig) = cref.extra_headers().pgp_signature() { // Note this is inefficient as gix doesn't yet support signature extraction natively. // TODO: convert this to native methods once https://github.com/Byron/gitoxide/pull/973 ships in a stable release. let signed_data = { let mut commit_without_signature = cref.clone(); let pos = commit_without_signature .extra_headers .iter() .position(|eh| eh.0 == "gpgsig") .unwrap(); commit_without_signature.extra_headers.remove(pos); let mut signed_data = Vec::new(); use gix::objs::WriteTo; commit_without_signature.write_to(&mut signed_data)?; signed_data }; (Some(Signature::from_bytes(sig)?), Some(signed_data)) } else { (None, None) }; Ok(Self { commit_id, author, summary, timestamp, signature, signed_data, }) } /// Is the commit timestamp "fresh" as in the database has been updated /// recently? (i.e. 90 days, per the `STALE_AFTER` constant) pub fn is_fresh(&self) -> bool { self.timestamp > SystemTime::now().checked_sub(STALE_AFTER).unwrap() } /// Get the raw bytes to be verified when verifying a commit signature pub fn raw_signed_bytes(&self) -> Option<&[u8]> { self.signed_data.as_ref().map(|bytes| bytes.as_ref()) } /// Reset the repository's state to match this commit pub(crate) fn reset(&self, repo: &Repository) -> Result<(), Error> { let repo = &repo.repo; let workdir = repo .workdir() .ok_or_else(|| Error::new(ErrorKind::Repo, "unable to checkout, repository is bare"))?; let root_tree = repo .find_object(self.commit_id.to_gix()) .map_err(|err| { Error::with_source(ErrorKind::Repo, "unable to locate commit".to_owned(), err) })? .peel_to_tree() .map_err(|err| { Error::with_source(ErrorKind::Repo, "unable to peel to tree".to_owned(), err) })? .id; let all_validations_for_max_safety = gix::worktree::validate::path::component::Options::default(); let index = gix::index::State::from_tree(&root_tree, &repo.objects, all_validations_for_max_safety) .map_err(|err| { Error::with_source( ErrorKind::Repo, format!("failed to create index from tree '{}'", root_tree), err, ) })?; let mut index = gix::index::File::from_state(index, repo.index_path()); let opts = gix::worktree::state::checkout::Options { destination_is_initially_empty: false, overwrite_existing: true, ..Default::default() }; gix::worktree::state::checkout( &mut index, workdir, repo.objects.clone(), &gix::progress::Discard, &gix::progress::Discard, &gix::interrupt::IS_INTERRUPTED, opts, ) .map_err(|err| Error::with_source(ErrorKind::Repo, "failed to checkout".to_owned(), err))?; index.write(Default::default()).map_err(|err| { Error::with_source(ErrorKind::Repo, "failed to write index".to_owned(), err) })?; Ok(()) } } rustsec-0.33.0/src/repository/git/commit_hash.rs000064400000000000000000000025741046102023000200410ustar 00000000000000use std::fmt::Display; /// ID (i.e. SHA-1 hash) of a git commit /// /// This is a wrapper around [gix::ObjectId] to prevent gix semver changes /// also breaking semver for `rustsec` crate. #[cfg_attr(docsrs, doc(cfg(feature = "git")))] #[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Clone, Copy)] pub struct CommitHash { hash: gix::ObjectId, } impl CommitHash { // Conversions to/from `gix` are only pub(crate) // to avoid leaking `gix` types and semver into the external API pub(crate) fn to_gix(self) -> gix::ObjectId { self.hash } pub(crate) fn from_gix(hash: gix::ObjectId) -> Self { CommitHash { hash } } /// Interpret this object id as raw byte slice. #[inline] pub fn as_bytes(&self) -> &[u8] { self.hash.as_bytes() } /// Display the hash as a hexadecimal string. #[inline] pub fn to_hex(&self) -> String { self.hash.to_hex().to_string() } /// Returns `true` if this hash is equal to an empty blob. #[inline] pub fn is_empty_blob(&self) -> bool { self.hash.is_empty_blob() } /// Returns `true` if this hash is equal to an empty tree. #[inline] pub fn is_empty_tree(&self) -> bool { self.hash.is_empty_tree() } } impl Display for CommitHash { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.hash.fmt(f) } } rustsec-0.33.0/src/repository/git/gitpath.rs000064400000000000000000000025221046102023000171770ustar 00000000000000use super::Repository; use crate::{Error, ErrorKind}; use std::path::Path; /// A path *relative to the root of the git repository* that is guaranteed to be tracked by Git. /// /// This type is immutable. #[cfg_attr(docsrs, doc(cfg(feature = "osv-export")))] #[derive(Clone, Copy)] pub struct GitPath<'a> { repo: &'a Repository, path: &'a Path, } impl<'a> GitPath<'a> { /// Creates a new `GitPath`, validating that this file is tracked in Git pub fn new(repo: &'a Repository, path: &'a Path) -> Result { // Validate that the path is relative for better feedback to API users if path.has_root() { fail!( ErrorKind::BadParam, "{} is not a relative path", path.display() ); } if !repo.has_relative_path(path) { Err(Error::new( ErrorKind::Repo, format!("HEAD commit does not contain path '{}'", path.display()), )) } else { Ok(GitPath { repo, path }) } } /// A path *relative to the root of the git repository* that is guaranteed to be tracked by Git pub fn path(&self) -> &'a Path { self.path } /// The git repository the path is tracked by pub fn repository(&self) -> &'a Repository { self.repo } } rustsec-0.33.0/src/repository/git/modification_time.rs000064400000000000000000000170751046102023000212330ustar 00000000000000use crate::advisory::Date; use crate::error::{Error, ErrorKind}; use gix::date::Time; use gix::traverse::commit::simple::CommitTimeOrder; use std::{ cmp::{max, min}, collections::HashMap, path::PathBuf, }; use time::OffsetDateTime; use super::GitPath; /// Tracks the time of latest modification of files in git. #[cfg_attr(docsrs, doc(cfg(feature = "osv-export")))] pub struct GitModificationTimes { mtimes: HashMap, ctimes: HashMap, } impl GitModificationTimes { /// Performance: collects all modification times on creation /// and caches them. This is more efficient for looking up lots of files, /// but wasteful if you just need to look up a couple files. pub fn new(repo: &super::Repository) -> Result { use gix::{bstr::ByteVec, diff::tree::recorder::Change, prelude::Find}; // Sadly I had to hand-roll this; there is no good off-the-shelf impl. // libgit2 has had a feature request for this for over a decade: // https://github.com/libgit2/libgit2/issues/495 // as does git2-rs: https://github.com/rust-lang/git2-rs/issues/588 // To make sure this works I've verified it against a naive shell script using `git log` // as well as `git whatchanged` let mut mtimes: HashMap = HashMap::new(); let mut ctimes: HashMap = HashMap::new(); let repo = &repo.repo; let walk = repo .rev_walk(Some(repo.head_id().map_err(|err| { Error::with_source(ErrorKind::Repo, "unable to find head id".to_owned(), err) })?)) .sorting(gix::revision::walk::Sorting::ByCommitTime( CommitTimeOrder::NewestFirst, )) .all() .map_err(|err| { Error::with_source(ErrorKind::Repo, "unable to walk commits".to_owned(), err) })?; let db = &repo.objects; let mut buf = Vec::new(); let mut buf2 = Vec::new(); for info in walk { let info = info.map_err(|err| { Error::with_source( ErrorKind::Repo, "failed to retrieve commit info".to_owned(), err, ) })?; let parent_commit_id = match info.parent_ids.len() { 1 => Some(info.parent_ids[0]), // Diff with the previous commit 0 => None, // We've found the initial commit, diff with empty repo _ => continue, // Ignore merge commits (2+ parents) because that's what 'git whatchanged' does. }; buf.clear(); buf2.clear(); let (main_tree_id, file_mod_time) = { let commit = db .try_find(&info.id, &mut buf) .map_err(|err| { Error::new( ErrorKind::Repo, format!("failed to find commit '{}': {err}", info.id), ) })? .ok_or_else(|| { Error::new(ErrorKind::Repo, format!("commit '{}' not present", info.id)) })? .decode() .map_err(|err| { Error::with_source( ErrorKind::Repo, format!("unable to decode commit '{}'", info.id), err, ) })? .into_commit() .expect("id is actually a commit"); ( commit.tree(), commit.time().map_err(|err| { Error::with_source( ErrorKind::Repo, format!("unable to parse commit time for '{}'", info.id), err, ) })?, ) }; let current_tree = db .try_find(&main_tree_id, &mut buf) .map_err(|err| { Error::new( ErrorKind::Repo, format!("failed to find tree for commit '{}': {err}", info.id), ) })? .expect("main tree present") .try_into_tree_iter() .expect("id to be a tree"); let previous_tree = parent_commit_id .and_then(|id| db.try_find(&id, &mut buf2).ok().flatten()) .and_then(|c| c.decode().ok()) .and_then(gix::objs::ObjectRef::into_commit) .map(|c| c.tree()) .and_then(|tree| db.try_find(&tree, &mut buf2).ok().flatten()) .and_then(|tree| tree.try_into_tree_iter()) .unwrap_or_default(); let mut recorder = gix::diff::tree::Recorder::default(); gix::diff::tree( previous_tree, current_tree, &mut gix::diff::tree::State::default(), db, &mut recorder, ) .map_err(|err| { Error::with_source( ErrorKind::Repo, format!( "failed to diff commit {} to its parent {:?}", info.id, parent_commit_id ), err, ) })?; for diff in recorder.records { // AFAIK files should never be deleted from an advisory db, // though unsure how moves/renames are handled by the recorder let file_path = match diff { Change::Addition { path, .. } | Change::Modification { path, .. } | Change::Deletion { path, .. } => { Vec::from(path).into_path_buf().expect("non utf-8 path") } }; mtimes .entry(file_path.clone()) .and_modify(|t| *t = max(*t, file_mod_time)) .or_insert(file_mod_time); ctimes .entry(file_path) .and_modify(|t| *t = min(*t, file_mod_time)) .or_insert(file_mod_time); } } Ok(Self { mtimes, ctimes }) } /// Looks up the Git modification time for a given file path. /// The path must be relative to the root of the repository. pub fn for_path(&self, path: GitPath<'_>) -> OffsetDateTime { crate::repository::git::gix_time_to_time(*self.mtimes.get(path.path()).unwrap()) .to_offset(time::UtcOffset::UTC) } /// Looks up the Git creation time for a given file path. /// The path must be relative to the root of the repository. pub fn mdate_for_path(&self, path: GitPath<'_>) -> Date { Self::gix_time_to_date(self.mtimes.get(path.path()).unwrap()) } /// Looks up the Git creation time for a given file path. /// The path must be relative to the root of the repository. pub fn cdate_for_path(&self, path: GitPath<'_>) -> Date { Self::gix_time_to_date(self.ctimes.get(path.path()).unwrap()) } fn gix_time_to_date(timestamp: &Time) -> Date { let odt = crate::repository::git::gix_time_to_time(*timestamp); let date = odt.date(); format!( "{:0>4}-{:0>2}-{:0>2}", date.year(), u8::from(date.month()), date.day() ) .parse() .unwrap() } } rustsec-0.33.0/src/repository/git/repository.rs000064400000000000000000000473471046102023000177740ustar 00000000000000//! Git repositories use std::{ fmt::Write, fs, path::{Path, PathBuf}, str, time::Duration, }; use gix::{bstr::ByteSlice, protocol::handshake::Ref}; use tame_index::utils::flock::LockOptions; use super::{Commit, DEFAULT_URL}; use crate::error::{Error, ErrorKind}; /// Directory under `~/.cargo` where the advisory-db repo will be kept const ADVISORY_DB_DIRECTORY: &str = "advisory-db"; /// Refspec used to fetch updates from remote advisory databases const REF_SPEC: &str = "+HEAD:refs/remotes/origin/HEAD"; /// The direction of the remote const DIR: gix::remote::Direction = gix::remote::Direction::Fetch; const DEFAULT_LOCK_TIMEOUT: Duration = Duration::from_secs(5 * 60); /// Git repository for a Rust advisory DB. #[cfg_attr(docsrs, doc(cfg(feature = "git")))] pub struct Repository { /// Repository object pub(super) repo: gix::Repository, } impl Repository { /// Location of the default `advisory-db` repository for crates.io pub fn default_path() -> PathBuf { home::cargo_home() .unwrap_or_else(|err| { panic!("Error locating Cargo home directory: {err}"); }) .join(ADVISORY_DB_DIRECTORY) } /// Fetch the default repository. /// /// ## Locking /// This function will wait for up to 5 minutes for the filesystem lock on the repository. /// It will fail with [`rustsec::Error::LockTimeout`](Error) if the lock is still held /// after that time. Use [Repository::fetch] if you need to configure locking behavior. pub fn fetch_default_repo() -> Result { Self::fetch( DEFAULT_URL, Repository::default_path(), true, DEFAULT_LOCK_TIMEOUT, ) } /// Create a new [`Repository`] with the given URL and path, and fetch its contents. /// /// ## Locking /// /// This function will wait for up to `lock_timeout` for the filesystem lock on the repository. /// It will fail with [`rustsec::Error::LockTimeout`](Error) if the lock is still held /// after that time. /// /// If `lock_timeout` is set to `std::time::Duration::from_secs(0)`, it will not wait at all, /// and instead return an error immediately if it fails to aquire the lock. pub fn fetch>( url: &str, into_path: P, ensure_fresh: bool, lock_timeout: Duration, ) -> Result { if !url.starts_with("https://") { fail!( ErrorKind::BadParam, "expected {} to start with https://", url ); } let path = into_path.into(); if let Some(parent) = path.parent() { if !parent.is_dir() { fs::create_dir_all(parent)?; } } else { fail!(ErrorKind::BadParam, "invalid directory: {}", path.display()) } // Avoid libgit2 errors in the case the directory exists but is // otherwise empty. // // See: https://github.com/RustSec/cargo-audit/issues/32 if path.is_dir() && fs::read_dir(&path)?.next().is_none() { fs::remove_dir(&path)?; } // Lock the directory to avoid several checkouts running at the same time trampling on each other. // We do not use Git locks because they have undesirable properties - they leave stale locks on SIGKILL or power loss // with no way to recover. They don't even write the PID to the lockfile. let lock_path = tame_index::Path::from_path(&path) .ok_or_else(|| { Error::new( ErrorKind::BadParam, "Path to the advisory DB directory is not valid UTF-8!", ) })? .with_extension(".lock"); let lock_opts = LockOptions::new(&lock_path).exclusive(false); let _lock = if lock_timeout == Duration::from_secs(0) { lock_opts.try_lock() } else { lock_opts.lock(|_| Some(lock_timeout)) } .map_err(Error::from_tame)?; let open_or_clone_repo = || -> Result<_, Error> { let mut mapping = gix::sec::trust::Mapping::default(); let open_with_complete_config = gix::open::Options::default().permissions(gix::open::Permissions { config: gix::open::permissions::Config { // Be sure to get all configuration, some of which is only known by the git binary. // That way we are sure to see all the systems credential helpers git_binary: true, ..Default::default() }, ..Default::default() }); mapping.reduced = open_with_complete_config.clone(); mapping.full = open_with_complete_config.clone(); // Attempt to open the repository, if it fails for any reason, // attempt to perform a fresh clone instead let repo = gix::ThreadSafeRepository::discover_opts( &path, gix::discover::upwards::Options::default().apply_environment(), mapping, ) .ok() .map(|repo| repo.to_thread_local()) .filter(|repo| { repo.find_remote("origin").is_ok_and(|remote| { remote .url(DIR) .is_some_and(|remote_url| remote_url.to_bstring() == url) }) }) .or_else(|| gix::open_opts(&path, open_with_complete_config).ok()); let res = if let Some(repo) = repo { (repo, None) } else { let mut progress = gix::progress::Discard; let should_interrupt = &gix::interrupt::IS_INTERRUPTED; let (mut prep_checkout, out) = gix::prepare_clone(url, path) .map_err(|err| { Error::with_source( ErrorKind::Repo, "failed to prepare clone".to_owned(), err, ) })? .with_remote_name("origin") .map_err(|err| { Error::with_source(ErrorKind::Repo, "invalid remote name".to_owned(), err) })? .configure_remote(|remote| Ok(remote.with_refspecs([REF_SPEC], DIR)?)) .fetch_then_checkout(&mut progress, should_interrupt) .map_err(|err| Error::with_source(ErrorKind::Repo, err.to_string(), err))?; let repo = prep_checkout .main_worktree(&mut progress, should_interrupt) .map_err(|err| { Error::with_source( ErrorKind::Repo, "failed to checkout fresh clone".to_owned(), err, ) })? .0; (repo, Some(out)) }; Ok(res) }; let (mut repo, fetch_outcome) = open_or_clone_repo()?; if let Some(fetch_outcome) = fetch_outcome { write_fetch_head(&repo, &fetch_outcome, &repo.find_remote("origin").unwrap())?; } else { // If we didn't open a fresh repo we need to peform a fetch ourselves, and // do the work of updating the HEAD to point at the latest remote HEAD, which // gix doesn't currently do. Self::perform_fetch(&mut repo)?; } repo.object_cache_size_if_unset(OBJECT_CACHE_SIZE); let repo = Self { repo }; let latest_commit = Commit::from_repo_head(&repo)?; latest_commit.reset(&repo)?; // Ensure that the upstream repository hasn't gone stale if ensure_fresh && !latest_commit.is_fresh() { fail!( ErrorKind::Repo, "repository is stale (last commit: {:?})", latest_commit.timestamp ); } Ok(repo) } /// Open a repository at the given path pub fn open>(into_path: P) -> Result { let path = into_path.into(); let mut repo = gix::open(&path).map_err(|err| { Error::with_source( ErrorKind::Repo, format!("failed to open repository at '{}'", path.display()), err, ) })?; repo.object_cache_size_if_unset(OBJECT_CACHE_SIZE); // TODO: Figure out how to detect if the worktree has modifications // as gix currently doesn't have a status/state summary like git2 has Ok(Self { repo }) } /// Get information about the latest commit to the repo pub fn latest_commit(&self) -> Result { Commit::from_repo_head(self) } /// Path to the local checkout of a git repository pub fn path(&self) -> &Path { // Safety: Would fail if this is a bare repo, which we aren't self.repo.workdir().unwrap() } /// Determines if the tree pointed to by `HEAD` contains the specified path pub fn has_relative_path(&self, path: &Path) -> bool { let lookup = || { self.repo .head_commit() .ok()? .tree() .ok()? .lookup_entry_by_path(path) .ok() .map(|_e| true) }; lookup().unwrap_or_default() } fn perform_fetch(repo: &mut gix::Repository) -> Result<(), Error> { let mut config = repo.config_snapshot_mut(); config .set_raw_value_by("committer", None, "name", "rustsec") .map_err(|err| { Error::with_source( ErrorKind::Repo, "failed to set `committer.name`".to_owned(), err, ) })?; // Note we _have_ to set the email as well, but luckily gix does not actually // validate if it's a proper email or not :) config .set_raw_value_by("committer", None, "email", "") .map_err(|err| { Error::with_source( ErrorKind::Repo, "failed to set `committer.email`".to_owned(), err, ) })?; let repo = config.commit_auto_rollback().map_err(|err| { Error::with_source(ErrorKind::Repo, "failed to set `committer`".to_owned(), err) })?; let mut remote = repo.find_remote("origin").map_err(|err| { Error::with_source( ErrorKind::Repo, "failed to find `origin` remote".to_owned(), err, ) })?; remote .replace_refspecs(Some(REF_SPEC), DIR) .expect("valid statically known refspec"); // Perform the actual fetch let outcome = remote .connect(DIR) .map_err(|err| { Error::with_source( ErrorKind::Repo, "failed to connect to remote".to_owned(), err, ) })? .prepare_fetch(&mut gix::progress::Discard, Default::default()) .map_err(|err| { Error::with_source(ErrorKind::Repo, "failed to prepare fetch".to_owned(), err) })? .receive(&mut gix::progress::Discard, &gix::interrupt::IS_INTERRUPTED) .map_err(|err| { Error::with_source(ErrorKind::Repo, "failed to fetch".to_owned(), err) })?; let remote_head_id = write_fetch_head(&repo, &outcome, &remote)?; use gix::refs::{Target, transaction as tx}; // In all (hopefully?) cases HEAD is a symbolic reference to // refs/heads/ which is a peeled commit id, if that's the case // we update it to the new commit id, otherwise we just set HEAD // directly use gix::head::Kind; let edit = match repo .head() .map_err(|err| { Error::with_source(ErrorKind::Repo, "unable to locate HEAD".to_owned(), err) })? .kind { Kind::Symbolic(sref) => { // Update our local HEAD to the remote HEAD if let Target::Symbolic(name) = sref.target { Some(tx::RefEdit { change: tx::Change::Update { log: tx::LogChange { mode: tx::RefLog::AndReference, force_create_reflog: false, message: "".into(), }, expected: tx::PreviousValue::MustExist, new: Target::Object(remote_head_id), }, name, deref: true, }) } else { None } } Kind::Unborn(_) | Kind::Detached { .. } => None, }; let edit = edit.unwrap_or_else(|| tx::RefEdit { change: tx::Change::Update { log: tx::LogChange { mode: tx::RefLog::AndReference, force_create_reflog: false, message: "".into(), }, expected: tx::PreviousValue::Any, new: Target::Object(remote_head_id), }, name: "HEAD".try_into().unwrap(), deref: true, }); repo.edit_reference(edit).map_err(|err| { Error::with_source( ErrorKind::Repo, "failed to set update reflog".to_owned(), err, ) })?; Ok(()) } } /// Writes the `FETCH_HEAD` for the specified fetch outcome to the specified git /// repository /// /// This function is narrowly focused on on writing a `FETCH_HEAD` that contains /// exactly two pieces of information, the id of the commit pointed to by the /// remote `HEAD`, and, if it exists, the same id with the remote branch whose /// `HEAD` is the same. This focus gives use two things: /// 1. `FETCH_HEAD` that can be parsed to the correct remote HEAD by /// [`gix`](https://github.com/Byron/gitoxide/commit/eb2b513bd939f6b59891d0a4cf5465b1c1e458b3) /// 1. A `FETCH_HEAD` that closely (or even exactly) matches that created by /// cargo via git or git2 when fetching only `+HEAD:refs/remotes/origin/HEAD` /// /// Calling this function for the fetch outcome of a clone will write `FETCH_HEAD` /// just as if a normal fetch had occurred, but note that AFAICT neither git nor /// git2 does this, ie. a fresh clone will not have a `FETCH_HEAD` present. I don't /// _think_ that has negative implications, but if it does...just don't call this /// function on the result of a clone :) /// /// Note that the remote provided should be the same remote used for the fetch /// operation. The reason this is not just grabbed from the repo is because /// repositories may not have the configured remote, or the remote was modified /// (eg. replacing refspecs) before the fetch operation fn write_fetch_head( repo: &gix::Repository, fetch: &gix::remote::fetch::Outcome, remote: &gix::Remote<'_>, ) -> Result { // Find the remote head commit let (head_target_branch, oid) = fetch .ref_map .mappings .iter() .find_map(|mapping| { let gix::remote::fetch::refmap::Source::Ref(rref) = &mapping.remote else { return None; }; let Ref::Symbolic { full_ref_name, target, object, .. } = rref else { return None; }; (full_ref_name == "HEAD").then_some((target, object)) }) .ok_or_else(|| Error::new(ErrorKind::Repo, "unable to find remote HEAD"))?; let remote_url = { let ru = remote .url(gix::remote::Direction::Fetch) .expect("can't fetch without a fetch url"); let s = ru.to_bstring(); let v = s.into(); String::from_utf8(v).expect("remote url was not utf-8 :-/") }; let fetch_head = { let mut hex_id = [0u8; 40]; let gix::ObjectId::Sha1(sha1) = oid else { return Err(Error::new( ErrorKind::Repo, "unsupported object id format in remote HEAD", )); }; let commit_id = encode_hex(sha1, &mut hex_id); let mut fetch_head = String::new(); let remote_name = remote .name() .and_then(|n| { let gix::remote::Name::Symbol(name) = n else { return None; }; Some(name.as_ref()) }) .unwrap_or("origin"); // We write the remote HEAD first, but _only_ if it was explicitly requested if remote .refspecs(gix::remote::Direction::Fetch) .iter() .any(|rspec| { let rspec = rspec.to_ref(); if !rspec.remote().is_some_and(|r| r.ends_with(b"HEAD")) { return false; } rspec.local().is_some_and(|l| { l.to_str().ok().and_then(|l| { l.strip_prefix("refs/remotes/") .and_then(|l| l.strip_suffix("/HEAD")) }) == Some(remote_name) }) }) { writeln!(&mut fetch_head, "{commit_id}\t\t{remote_url}").unwrap(); } // Attempt to get the branch name, but if it looks suspect just skip this, // it _should_ be fine, or at least, we've already written the only thing // that gix can currently parse if let Some(branch_name) = head_target_branch .to_str() .ok() .and_then(|s| s.strip_prefix("refs/heads/")) { writeln!( &mut fetch_head, "{commit_id}\t\tbranch '{branch_name}' of {remote_url}" ) .unwrap(); } fetch_head }; // We _could_ also emit other branches/tags like git does, however it's more // complicated than just our limited use case of writing remote HEAD // // 1. Remote branches are always emitted, however in gix those aren't part // of the ref mappings if they haven't been updated since the last fetch // 2. Conversely, tags are _not_ written by git unless they have been changed // added, but gix _does_ always place those in the fetch mappings if fetch_head.is_empty() { return Err(Error::new(ErrorKind::Repo, "unable to find remote HEAD")); } let fetch_head_path = repo.path().join("FETCH_HEAD"); fs::write(&fetch_head_path, fetch_head).map_err(|err| { Error::with_source( ErrorKind::Io, format!("failed to write {}", fetch_head_path.display()), err, ) })?; Ok(*oid) } /// Encodes a slice of bytes into a hexadecimal string to the specified buffer fn encode_hex<'out, const I: usize, const O: usize>( input: &[u8; I], output: &'out mut [u8; O], ) -> &'out str { assert_eq!(I * 2, O); const CHARS: &[u8] = b"0123456789abcdef"; for (i, &byte) in input.iter().enumerate() { let i = i * 2; output[i] = CHARS[(byte >> 4) as usize]; output[i + 1] = CHARS[(byte & 0xf) as usize]; } // We only emit ASCII hex characters, so this is guaranteed to be valid UTF-8 str::from_utf8(output).expect("hex encoding produced invalid UTF-8") } const OBJECT_CACHE_SIZE: usize = 4 * 1024 * 1024; rustsec-0.33.0/src/repository/git.rs000064400000000000000000000014551046102023000155430ustar 00000000000000//! Git repository handling for the RustSec advisory DB mod commit; mod commit_hash; #[cfg(feature = "osv-export")] mod gitpath; #[cfg(feature = "osv-export")] mod modification_time; mod repository; pub use self::{commit::Commit, commit_hash::CommitHash, repository::Repository}; #[cfg(feature = "osv-export")] pub use self::{gitpath::GitPath, modification_time::GitModificationTimes}; /// Location of the RustSec advisory database for crates.io pub const DEFAULT_URL: &str = "https://github.com/RustSec/advisory-db.git"; #[inline] pub(crate) fn gix_time_to_time(time: gix::date::Time) -> time::OffsetDateTime { time::OffsetDateTime::from_unix_timestamp(time.seconds) .expect("always valid unix time") .to_offset(time::UtcOffset::from_whole_seconds(time.offset).expect("valid offset")) } rustsec-0.33.0/src/repository/signature.rs000064400000000000000000000010161046102023000167520ustar 00000000000000//! Git commit signatures use crate::error::Error; /// Digital signatures (in OpenPGP format) on commits to the repository #[derive(Clone, Debug, Eq, PartialEq)] pub struct Signature(Vec); impl Signature { /// Parse a signature from a Git commit pub fn from_bytes(bytes: &[u8]) -> Result { // TODO: actually verify the signature is well-structured Ok(Signature(bytes.into())) } } impl AsRef<[u8]> for Signature { fn as_ref(&self) -> &[u8] { self.0.as_ref() } } rustsec-0.33.0/src/repository.rs000064400000000000000000000001561046102023000147550ustar 00000000000000//! Repository handling for the RustSec advisory DB pub mod signature; #[cfg(feature = "git")] pub mod git; rustsec-0.33.0/src/vulnerability.rs000064400000000000000000000034741046102023000154350ustar 00000000000000//! Vulnerabilities represent the intersection of the [`Advisory`] database //! and a particular `Cargo.lock` file. use crate::{ advisory::{self, Advisory, affected::FunctionPath}, package::Package, }; use serde::{Deserialize, Serialize}; /// A vulnerable package and the associated advisory #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct Vulnerability { /// Security advisory for which the package is vulnerable pub advisory: advisory::Metadata, /// Versions impacted by this vulnerability pub versions: advisory::Versions, /// More specific information about what this advisory affects (if available) pub affected: Option, /// Vulnerable package pub package: Package, } impl Vulnerability { /// Create `Vulnerability` about a given [`Advisory`] and [`Package`] pub fn new(advisory: &Advisory, package: &Package) -> Self { Self { advisory: advisory.metadata.clone(), versions: advisory.versions.clone(), affected: advisory.affected.clone(), package: package.clone(), } } /// Get the set of functions affected by this vulnerability (if available) pub fn affected_functions(&self) -> Option> { self.affected.as_ref().and_then(|affected| { if affected.functions.is_empty() { None } else { let mut result = vec![]; for (path, versions) in &affected.functions { if versions .iter() .any(|req| req.matches(&self.package.version.clone())) { result.push(path.clone()); } } Some(result) } }) } } rustsec-0.33.0/src/warning.rs000064400000000000000000000060111046102023000141770ustar 00000000000000//! Warnings sourced from the Advisory DB use crate::error::{Error, ErrorKind}; use crate::{advisory, package::Package}; use serde::{Deserialize, Serialize}; use std::{fmt, str::FromStr}; /// Warnings sourced from the Advisory DB #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Warning { /// Kind of warning pub kind: WarningKind, /// Name of the dependent package pub package: Package, /// Source advisory pub advisory: Option, /// More specific information about what the source advisory affects (if available) pub affected: Option, /// Versions impacted by this warning pub versions: Option, } impl Warning { /// Create `Warning` of the given kind pub fn new( kind: WarningKind, package: &Package, advisory: Option, affected: Option, versions: Option, ) -> Self { Self { kind, package: package.clone(), advisory, affected, versions, } } /// Is this a warning a `notice` about a crate? pub fn is_notice(&self) -> bool { self.kind == WarningKind::Notice } /// Is this a warning about an `unmaintained` crate? pub fn is_unmaintained(&self) -> bool { self.kind == WarningKind::Unmaintained } /// Is this a warning about an `unsound` crate? pub fn is_unsound(&self) -> bool { self.kind == WarningKind::Unsound } /// Is this a warning about a yanked crate? pub fn is_yanked(&self) -> bool { self.kind == WarningKind::Yanked } } /// Kinds of warnings #[derive(Copy, Clone, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Serialize, Ord)] #[non_exhaustive] pub enum WarningKind { /// Informational notices about packages #[serde(rename = "notice")] Notice, /// Unmaintained packages #[serde(rename = "unmaintained")] Unmaintained, /// Unsound packages #[serde(rename = "unsound")] Unsound, /// Yanked packages #[serde(rename = "yanked")] Yanked, } impl WarningKind { /// Get a `str` representing an warning [`WarningKind`] pub fn as_str(&self) -> &str { match self { Self::Notice => "notice", Self::Unmaintained => "unmaintained", Self::Unsound => "unsound", Self::Yanked => "yanked", } } } impl FromStr for WarningKind { type Err = Error; fn from_str(s: &str) -> Result { Ok(match s { "notice" => WarningKind::Notice, "unmaintained" => WarningKind::Unmaintained, "unsound" => WarningKind::Unsound, "yanked" => WarningKind::Yanked, other => fail!(ErrorKind::Parse, "invalid warning type: {}", other), }) } } impl fmt::Display for WarningKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.as_str()) } } rustsec-0.33.0/tests/advisories.rs000064400000000000000000000074271046102023000152710ustar 00000000000000//! Tests for parsing RustSec advisories #![warn(rust_2018_idioms, unused_qualifications)] use cvss::Cvss; use rustsec::advisory::{Category, License}; use std::path::Path; /// Load example advisory from the filesystem fn load_advisory(case: &str) -> rustsec::Advisory { rustsec::Advisory::load_file(Path::new(&format!( "./tests/support/example_advisory_{case}.md" ))) .unwrap() } /// Basic metadata #[test] fn parse_metadata() { for advisory in &[load_advisory("v3"), load_advisory("v4")] { assert_eq!(advisory.metadata.id.as_str(), "RUSTSEC-2001-2101"); assert_eq!(advisory.metadata.package.as_str(), "base"); assert_eq!(advisory.title(), "All your base are belong to us"); assert_eq!( advisory.description(), "You have no chance to survive. Make your time." ); assert_eq!(advisory.metadata.date.as_str(), "2001-02-03"); assert_eq!( advisory.metadata.url.as_ref().unwrap().to_string(), "https://www.youtube.com/watch?v=jQE66WA2s-A" ); for (i, category) in [Category::CodeExecution, Category::PrivilegeEscalation] .iter() .enumerate() { assert_eq!(*category, advisory.metadata.categories[i]); } for (i, kw) in ["how", "are", "you", "gentlemen"].iter().enumerate() { assert_eq!(*kw, advisory.metadata.keywords[i].as_str()); } assert_eq!(advisory.metadata.license, License::CcZero10); } // Test fields specific to advisories imported from GitHub let ghsa = load_advisory("v4_from_ghsa"); assert_eq!(ghsa.metadata.license, License::CcBy40); // Test advisory with unknown license let ghsa = load_advisory("v4_unknown_license"); assert_eq!(ghsa.metadata.license, License::Other("MPL-2.0".to_string())); } /// Parsing of impact metadata #[test] fn parse_affected() { let affected = load_advisory("v3").affected.unwrap(); assert_eq!(affected.arch[0], platforms::target::Arch::X86); assert_eq!(affected.os[0], platforms::target::OS::Windows); let example_function = "base::belongs::All".parse().unwrap(); let req = &affected.functions.get(&example_function).unwrap()[0]; assert!(req.matches(&"1.2.2".parse().unwrap())); assert!(!req.matches(&"1.2.3".parse().unwrap())); } /// Parsing of other aliased advisory IDs #[test] fn parse_aliases() { let alias = &load_advisory("v3").metadata.aliases[0]; assert!(alias.is_cve()); assert_eq!(alias.year().unwrap(), 2001); } /// Parsing of CVSS v3.1 severity vector strings #[test] fn parse_cvss_vector_string() { let advisory = load_advisory("v3"); assert_eq!( advisory.severity().unwrap(), rustsec::advisory::Severity::Critical ); let Cvss::CvssV31(cvss) = advisory.metadata.cvss.unwrap() else { panic!("expected CVSS v3.1"); }; assert_eq!(cvss.av.unwrap(), cvss::v3::base::AttackVector::Network); assert_eq!(cvss.ac.unwrap(), cvss::v3::base::AttackComplexity::Low); assert_eq!(cvss.pr.unwrap(), cvss::v3::base::PrivilegesRequired::None); assert_eq!(cvss.ui.unwrap(), cvss::v3::base::UserInteraction::None); assert_eq!(cvss.s.unwrap(), cvss::v3::base::Scope::Changed); assert_eq!(cvss.c.unwrap(), cvss::v3::base::Confidentiality::High); assert_eq!(cvss.i.unwrap(), cvss::v3::base::Integrity::High); assert_eq!(cvss.a.unwrap(), cvss::v3::base::Availability::High); assert_eq!(cvss.score().value(), 10.0); } /// Parsing of patched version reqs #[test] fn parse_patched_version_reqs() { let advisory = load_advisory("v3"); let req = &advisory.versions.patched()[0]; assert!(!req.matches(&"1.2.2".parse().unwrap())); assert!(req.matches(&"1.2.3".parse().unwrap())); assert!(req.matches(&"1.2.4".parse().unwrap())); } rustsec-0.33.0/tests/database.rs000064400000000000000000000031611046102023000146540ustar 00000000000000#![cfg(feature = "git")] use cargo_lock::Lockfile; use once_cell::sync::Lazy; use rustsec::{Database, database::Query, report::Settings, repository::git::Repository}; use std::{path::Path, sync::Mutex}; static DEFAULT_DATABASE: Lazy> = Lazy::new(|| { Mutex::new( Database::load_from_repo(&Repository::fetch_default_repo().unwrap()) .expect("Should be fetchable."), ) }); #[test] fn enumerate_vulnerabilities() { let lockfile_path = Path::new("./tests/support/cratesio_cargo.lock"); let lockfile = Lockfile::load(lockfile_path).expect("Should find the lock file in support folder."); let db = DEFAULT_DATABASE.lock().unwrap(); let vuln = db.vulnerabilities(&lockfile); assert_eq!(vuln.len(), 1); } #[test] fn query_vulnerabilities_with_crate_scope() { let lockfile_path = Path::new("./tests/support/cratesio_cargo.lock"); let lockfile = Lockfile::load(lockfile_path).expect("Should find the lock file in support folder."); let db = DEFAULT_DATABASE.lock().unwrap(); let vuln_all = db.query_vulnerabilities(&lockfile, &Query::crate_scope()); let vuln = db.vulnerabilities(&lockfile); assert_eq!(vuln_all, vuln); } #[test] fn query_warnings_local_crates() { let lockfile_path = Path::new("./tests/support/local-warnings.lock"); let lockfile = Lockfile::load(lockfile_path).expect("Should find the lock file in support folder."); let db = DEFAULT_DATABASE.lock().unwrap(); let warnings = db.query_vulnerabilities(&lockfile, &Settings::default().query().informational(true)); assert_eq!(warnings.len(), 0); } rustsec-0.33.0/tests/integration.rs000064400000000000000000000070521046102023000154360ustar 00000000000000//! Integration test against the live `advisory-db` repo on GitHub #![cfg(feature = "git")] #![warn(rust_2018_idioms, unused_qualifications)] use std::time::Duration; use rustsec::{ Collection, Database, Lockfile, VersionReq, advisory, database::Query, repository::git, }; use tempfile::tempdir; /// Happy path integration test (has online dependency on GitHub) #[test] fn happy_path() { let db = Database::load_from_repo(&git::Repository::fetch_default_repo().unwrap()).unwrap(); verify_rustsec_2017_0001(&db); verify_cve_2018_1000810(&db); } /// End-to-end integration test (has online dependency on GitHub) which looks /// for the `RUSTSEC-2017-0001` vulnerability (`sodiumoxide` crate). fn verify_rustsec_2017_0001(db: &Database) { let example_advisory_id = "RUSTSEC-2017-0001".parse::().unwrap(); let example_advisory = db.get(&example_advisory_id).unwrap(); let example_package = "sodiumoxide".parse().unwrap(); assert_eq!(example_advisory.metadata.id, example_advisory_id); assert_eq!(example_advisory.metadata.package, example_package); assert_eq!( example_advisory.versions.patched()[0], VersionReq::parse(">= 0.0.14").unwrap() ); assert_eq!(example_advisory.metadata.date.as_str(), "2017-01-26"); assert_eq!( example_advisory.metadata.url.as_ref().unwrap().to_string(), "https://github.com/dnaq/sodiumoxide/issues/154" ); assert_eq!( example_advisory.title(), "scalarmult() vulnerable to degenerate public keys" ); assert_eq!( &example_advisory.description()[0..30], "The `scalarmult()` function in" ); assert_eq!( example_advisory.metadata.collection.unwrap(), Collection::Crates ); let crate_advisories = db.query(&Query::new().package_name(example_package).year(2017)); assert_eq!(example_advisory, crate_advisories[0]); let lockfile = Lockfile::load("../Cargo.lock").unwrap(); let _vulns = db.vulnerabilities(&lockfile); } /// End-to-end integration test (has online dependency on GitHub) which looks /// for the `CVE-2018-1000810` vulnerability (`std::str::repeat`) fn verify_cve_2018_1000810(db: &Database) { let example_advisory_id = "CVE-2018-1000810".parse::().unwrap(); let example_advisory = db.get(&example_advisory_id).unwrap(); let example_package = "std".parse().unwrap(); assert_eq!(example_advisory.metadata.id, example_advisory_id); assert_eq!(example_advisory.metadata.package, example_package); assert_eq!( example_advisory.versions.patched()[0], VersionReq::parse(">= 1.29.1").unwrap() ); assert_eq!(example_advisory.metadata.date.as_str(), "2018-09-21"); assert_eq!( example_advisory.metadata.url.as_ref().unwrap().to_string(), "https://groups.google.com/forum/#!topic/rustlang-security-announcements/CmSuTm-SaU0" ); assert_eq!( example_advisory.title(), "Buffer overflow vulnerability in str::repeat()" ); assert_eq!( &example_advisory.description()[0..30], "The Rust team was recently not" ); assert_eq!( example_advisory.metadata.collection.unwrap(), Collection::Rust ); } /// Regression test for cloning into an existing directory #[test] fn clone_into_existing_directory() { // Make an empty temporary directory let tmp = tempdir().unwrap(); // Attempt to fetch into it git::Repository::fetch( git::DEFAULT_URL, tmp.path(), true, Duration::from_secs(5 * 60), ) .unwrap(); } rustsec-0.33.0/tests/linter.rs000064400000000000000000000110761046102023000144110ustar 00000000000000//! Linter tests #![warn(rust_2018_idioms, unused_qualifications)] use rustsec::advisory::Linter; /// Example RustSec Advisory const EXAMPLE_ADVISORY_PATH: &str = "./tests/support/example_advisory_v3.md"; /// Ensure example advisory passes lint #[test] fn valid_advisory() { let lint = Linter::lint_file(EXAMPLE_ADVISORY_PATH).unwrap(); assert_eq!(lint.errors(), &[]); } /// Example advisory used in the subsequent `#[test]` const INVALID_ADVISORY_MD: &str = r#"```toml [advisory] id = "LULZSEC-2001-2101" package = "base" collection = "crates" date = "2001-02-03" url = "ftp://www.youtube.com/watch?v=jQE66WA2s-A" categories = ["invalid-category"] keywords = ["how", "are", "you", "gentlemen"] aliases = ["CVE-2001-2101"] cvss = "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H" invalid-advisory-key = "invalid" [versions] patched = [">= 1.2.3"] [affected] arch = ["x86"] os = ["windows"] functions = { "notyourbase::belongs::All" = ["< 1.2.3"] } [invalid-section] ``` # All your base are belong to us You have no chance to survive. Make your time. "#; /// Advisory which fails lint for multiple msgs #[test] fn invalid_example() { let lint = Linter::lint_string(INVALID_ADVISORY_MD, Some(false)).unwrap(); // Do we get the expected number of errors? assert_eq!(lint.errors().len(), 7); // `invalid-category` let invalid_category = lint.errors()[0].to_string(); assert_eq!( invalid_category, "invalid value `invalid-category` for key `category` in [advisory]: unknown category" ); // explicit `collection` is disallowed let explicit_collection = lint.errors()[1].to_string(); assert_eq!( explicit_collection, "malformed content in [advisory]: collection shouldn\'t be explicit; inferred by location" ); // invalid advisory ID (LULZSEC) let invalid_advisory_id = lint.errors()[2].to_string(); assert_eq!( invalid_advisory_id, "invalid value `\"LULZSEC-2001-2101\"` for key `id` in [advisory]: unknown advisory ID type" ); // `invalid-advisory-key` let invalid_advisory_key = lint.errors()[3].to_string(); assert_eq!( invalid_advisory_key, "invalid key `invalid-advisory-key` in [advisory]" ); // invalid advisory URL (must start with https://) let invalid_advisory_url = lint.errors()[4].to_string(); assert_eq!( invalid_advisory_url, "invalid value `\"ftp://www.youtube.com/watch?v=jQE66WA2s-A\"` \ for key `url` in [advisory]: URL must start with https://" ); // function path that doesn't match crate name let invalid_function_path = lint.errors()[5].to_string(); assert_eq!( invalid_function_path, "invalid value `notyourbase::belongs::All` for key `functions` \ in [affected]: function path must start with crate name" ); // `invalid-section` let invalid_section = lint.errors()[6].to_string(); assert_eq!(invalid_section, "invalid key `invalid-section` in toplevel"); } /// A valid advisory with a real (non-placeholder) ID const VALID_ADVISORY_MD: &str = include_str!("support/example_advisory_v3.md"); /// The advisory ID's placeholder status must match the draft flag #[test] fn draft_id_mismatch() { let placeholder = VALID_ADVISORY_MD.replace("RUSTSEC-2001-2101", "RUSTSEC-0000-0000"); // Real ID flagged as a draft is an error let lint = Linter::lint_string(VALID_ADVISORY_MD, Some(true)).unwrap(); assert_eq!( lint.errors() .iter() .map(ToString::to_string) .collect::>(), vec![ "invalid value `\"RUSTSEC-2001-2101\"` for key `id` in [advisory]: \ advisory ID draft status does not match file name" ] ); // Placeholder ID flagged as non-draft is an error let lint = Linter::lint_string(&placeholder, Some(false)).unwrap(); assert_eq!( lint.errors() .iter() .map(ToString::to_string) .collect::>(), vec![ "invalid value `\"RUSTSEC-0000-0000\"` for key `id` in [advisory]: \ advisory ID draft status does not match file name" ] ); // Matching draft status (or no draft info) produces no error assert_eq!( Linter::lint_string(VALID_ADVISORY_MD, Some(false)) .unwrap() .errors(), &[] ); assert_eq!( Linter::lint_string(&placeholder, Some(true)) .unwrap() .errors(), &[] ); assert_eq!( Linter::lint_string(&placeholder, None).unwrap().errors(), &[] ); } rustsec-0.33.0/tests/query.rs000064400000000000000000000035611046102023000142610ustar 00000000000000//! Tests for parsing RustSec advisories #![warn(rust_2018_idioms, unused_qualifications)] use platforms::target::{Arch, OS}; use rustsec::{advisory::Severity, database::Query, package}; /// Load example advisory from the filesystem fn load_advisory() -> rustsec::Advisory { rustsec::Advisory::load_file("./tests/support/example_advisory_v3.md").unwrap() } #[test] fn matches_name() { let advisory = load_advisory(); let package_matches: package::Name = "base".parse().unwrap(); let query_matches = Query::new().package_name(package_matches); assert!(query_matches.matches(&advisory)); let package_nomatch: package::Name = "somethingelse".parse().unwrap(); let query_nomatch = Query::new().package_name(package_nomatch); assert!(!query_nomatch.matches(&advisory)); } #[test] fn matches_year() { let advisory = load_advisory(); let query_matches = Query::new().year(2001); assert!(query_matches.matches(&advisory)); let query_nomatch = Query::new().year(2525); assert!(!query_nomatch.matches(&advisory)); } #[test] fn matches_severity() { let advisory = load_advisory(); let query_matches = Query::new().severity(Severity::Critical); assert!(query_matches.matches(&advisory)); } #[test] fn matches_target_os() { let advisory = load_advisory(); let query_matches = Query::new().target_os(vec![OS::Windows, OS::Linux]); assert!(query_matches.matches(&advisory)); let query_normal = Query::new().target_os(vec![OS::MacOS, OS::FreeBSD]); assert!(!query_normal.matches(&advisory)); } #[test] fn matches_target_arch() { let advisory = load_advisory(); let query_matches = Query::new().target_arch(vec![Arch::X86, Arch::Arm]); assert!(query_matches.matches(&advisory)); let query_normal = Query::new().target_arch(vec![Arch::Mips, Arch::Mips64]); assert!(!query_normal.matches(&advisory)); } rustsec-0.33.0/tests/support/cratesio_cargo.lock000064400000000000000000000016461046102023000201220ustar 00000000000000# This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] name = "base64" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "base64_vuln" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "base64 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "byteorder" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum base64 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "124e5332dfc4e387b4ca058909aa175c0c3eccf03846b7c1a969b9ad067b8df2" "checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" rustsec-0.33.0/tests/support/example_advisory_v3.md000064400000000000000000000010541046102023000205620ustar 00000000000000```toml [advisory] id = "RUSTSEC-2001-2101" package = "base" date = "2001-02-03" url = "https://www.youtube.com/watch?v=jQE66WA2s-A" categories = ["code-execution", "privilege-escalation"] keywords = ["how", "are", "you", "gentlemen"] aliases = ["CVE-2001-2101"] cvss = "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H" [versions] patched = [">= 1.2.3"] unaffected = ["0.1.2"] [affected] arch = ["x86"] os = ["windows"] functions = { "base::belongs::All" = ["< 1.2.3"] } ``` # All your base are belong to us You have no chance to survive. Make your time. rustsec-0.33.0/tests/support/example_advisory_v4.md000064400000000000000000000010411046102023000205570ustar 00000000000000```toml id = "RUSTSEC-2001-2101" package = "base" date = "2001-02-03" url = "https://www.youtube.com/watch?v=jQE66WA2s-A" categories = ["code-execution", "privilege-escalation"] keywords = ["how", "are", "you", "gentlemen"] aliases = ["CVE-2001-2101"] cvss = "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H" [versions] patched = [">= 1.2.3"] unaffected = ["0.1.2"] [affected] arch = ["x86"] os = ["windows"] functions = { "base::belongs::All" = ["< 1.2.3"] } ``` # All your base are belong to us You have no chance to survive. Make your time. rustsec-0.33.0/tests/support/example_advisory_v4_from_ghsa.md000064400000000000000000000010751046102023000226130ustar 00000000000000```toml id = "RUSTSEC-2001-2101" package = "base" date = "2001-02-03" url = "https://github.com/advisories/GHSA-f8vr-r385-rh5r" categories = ["code-execution", "privilege-escalation"] keywords = ["how", "are", "you", "gentlemen"] aliases = ["CVE-2001-2101"] cvss = "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H" license = "CC-BY-4.0" [versions] patched = [">= 1.2.3"] unaffected = ["0.1.2"] [affected] arch = ["x86"] os = ["windows"] functions = { "base::belongs::All" = ["< 1.2.3"] } ``` # All your base are belong to us You have no chance to survive. Make your time. rustsec-0.33.0/tests/support/example_advisory_v4_unknown_license.md000064400000000000000000000010731046102023000240450ustar 00000000000000```toml id = "RUSTSEC-2001-2101" package = "base" date = "2001-02-03" url = "https://github.com/advisories/GHSA-f8vr-r385-rh5r" categories = ["code-execution", "privilege-escalation"] keywords = ["how", "are", "you", "gentlemen"] aliases = ["CVE-2001-2101"] cvss = "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H" license = "MPL-2.0" [versions] patched = [">= 1.2.3"] unaffected = ["0.1.2"] [affected] arch = ["x86"] os = ["windows"] functions = { "base::belongs::All" = ["< 1.2.3"] } ``` # All your base are belong to us You have no chance to survive. Make your time. rustsec-0.33.0/tests/support/local-warnings.lock000064400000000000000000000004421046102023000200470ustar 00000000000000# This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] name = "cargo-audit-name-collision" version = "0.1.0" dependencies = [ "mock", "tempdir", ] [[package]] name = "mock" version = "0.1.0" [[package]] name = "tempdir" version = "0.3.7" rustsec-0.33.0/tests/support/local_cargo.lock000064400000000000000000000000561046102023000173750ustar 00000000000000[[package]] name = "base64" version = "0.5.1"