diff --git a/CHANGELOG.md b/CHANGELOG.md index a6d1855..d60336c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ All notable changes to this project will be documented in this file. - Added 61 new detection rules across 46 providers: Axiom (API token + PAT), Trigger.dev (secret key + PAT), Dub.co, Svix webhook signing secret, Liveblocks, Inngest (signing key + event key), Seam, Courier, Cal.com, Arcjet, WarpStream, Mem0, Mintlify, Pirsch, Tinybird, Tolgee (project key + PAT), Ory (API key + session + OAuth2 tokens), Xendit, Xata, Crossmint (server + client keys), DeepL (Free + Pro), Flagsmith, E2B, Infisical, WooCommerce (consumer key + secret), Nightfall AI, Ramp (client ID + secret), Hex.pm (personal + workspace tokens), Convex deploy key, MiniMax, Mappedin (key + secret), Pollinations (secret + publishable), Fal.ai, Aikido, Hack Club, GuardSquare, Browser Use, Composio, Gamma, Hex.tech, Mastra, redirect.pizza, Upstash, and WorkOS. Also added new prefixed-token rules for Netlify (`nfp_`), Cloudflare (`cfut_`), and Supabase (`sb_publishable_`). Added live HTTP validation for 30 of these rules. - Added 32 new detection rules across 25 providers: Ghost CMS (admin + content keys), UpCloud (`ucat_`), Voiceflow (`VF.DM.`/`VF.WS.`), Robinhood Crypto (`rh-api-`), ClickUp (`pk_`), Unleash (client/admin + personal tokens), ConfigCat (standard + extended SDK keys), SaladCloud (`salad_cloud_`), Tigris (`tid_`/`tsec_`), Portainer (`ptr_`), Permit.io (`permit_key_`), Builder.io (`bpk-`), LiveKit (API key + secret), Close CRM (`api_`), Hetzner Cloud, Censys (API ID + secret), Wistia, PandaDoc, Pinata (key + secret), ZeroTier, Detectify, ChartMogul, Moralis, ButterCMS, and Loops. Includes HTTP validation for 19 of these rules. - Removed 17 direct dependencies from the root crate by dropping unused deps (`p256`, `ed25519-dalek`, `jsonwebtoken`, `gitlab`, `lazy_static`, `base32`, `pem`, `byteorder`, `reqwest-middleware`, `sha1`, `time`, `ring`, `num_cpus`, `strum_macros`), replacing `once_cell` with `std::sync::{LazyLock, OnceLock}`, and using `std::thread::available_parallelism()` in place of `num_cpus`. Salt generation now uses `rand` instead of `ring`, and all `strum_macros::Display` imports are consolidated under `strum::Display`. +- Migrated the workspace to Rust Edition 2024 (MSRV 1.94) and refactored nested `if let` chains in core/scanner hot paths (content-type detection, origin parsing, GCP/Harness/Azure DevOps access maps, GitHub/GitLab repo parsing, dependent-variable pairing) to use stable let-chains for flatter control flow. +- Tightened lint hygiene by converting stable `#[allow(...)]` attributes to `#[expect(...)]` across the workspace (e.g. `dead_code`, `clippy::too_many_arguments`, `clippy::large_enum_variant`) so the compiler surfaces stale suppressions as warnings. ## [v1.95.0] - Fixed scan performance regression: the rule profiler was unconditionally active even without `--rule-stats`, causing RwLock contention across scan threads. Scans are now ~15% faster than v1.94.0. diff --git a/Cargo.lock b/Cargo.lock index 67fe786..ea42061 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5150,7 +5150,6 @@ dependencies = [ "hex", "memchr", "memmap2", - "once_cell", "parking_lot 0.12.5", "pretty_assertions", "rustc-hash", @@ -5174,7 +5173,6 @@ dependencies = [ "ignore", "include_dir", "kingfisher-core", - "lazy_static", "liquid", "liquid-core", "percent-encoding", @@ -5228,7 +5226,6 @@ dependencies = [ "liquid-core", "mongodb", "mysql_async", - "once_cell", "p256", "parking_lot 0.12.5", "pem", diff --git a/Cargo.toml b/Cargo.toml index 9db5558..4cdcd32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ resolver = "2" [workspace.package] -edition = "2021" +edition = "2024" rust-version = "1.94" license = "Apache-2.0" authors = ["Mick Grove "] @@ -25,7 +25,6 @@ schemars = "0.8" regex = "1.12" bstr = { version = "1.12", features = ["serde"] } smallvec = { version = "1", features = ["const_generics", "const_new", "union"] } -once_cell = "1.21" parking_lot = "0.12" tracing = "0.1" vectorscan-rs = "0.0.6" diff --git a/crates/kingfisher-core/Cargo.toml b/crates/kingfisher-core/Cargo.toml index 307ea54..1bddc1a 100644 --- a/crates/kingfisher-core/Cargo.toml +++ b/crates/kingfisher-core/Cargo.toml @@ -26,7 +26,6 @@ hex.workspace = true # Memory management memmap2 = "0.9" -once_cell.workspace = true parking_lot.workspace = true # Collections diff --git a/crates/kingfisher-core/src/blob.rs b/crates/kingfisher-core/src/blob.rs index b51b457..0a62790 100644 --- a/crates/kingfisher-core/src/blob.rs +++ b/crates/kingfisher-core/src/blob.rs @@ -15,13 +15,12 @@ use std::{ path::Path, sync::{ atomic::{AtomicU64, Ordering}, - Arc, + Arc, OnceLock, }, }; use bstr::{BString, ByteSlice}; use gix::ObjectId; -use once_cell::sync::OnceCell; use parking_lot::Mutex; use rustc_hash::FxHashMap; use schemars::JsonSchema; @@ -114,7 +113,7 @@ impl<'a> BlobData<'a> { /// ``` pub struct Blob<'a> { /// Lazily computed content-based ID. - id: OnceCell, + id: OnceLock, /// The underlying data. data: BlobData<'a>, /// Temporary ID assigned at creation (for debugging/tracking). @@ -134,12 +133,12 @@ impl Blob<'_> { if file_size > LARGE_FILE_THRESHOLD { // Large files: one mmap, zero extra copies. let mmap = unsafe { memmap2::Mmap::map(&file)? }; - Ok(Blob { id: OnceCell::new(), data: BlobData::Mapped(mmap), temp_id }) + Ok(Blob { id: OnceLock::new(), data: BlobData::Mapped(mmap), temp_id }) } else { // Small files: read into memory. let mut bytes = Vec::with_capacity(file_size as usize); file.read_to_end(&mut bytes)?; - Ok(Blob { id: OnceCell::new(), data: BlobData::Owned(bytes), temp_id }) + Ok(Blob { id: OnceLock::new(), data: BlobData::Owned(bytes), temp_id }) } } @@ -147,14 +146,14 @@ impl Blob<'_> { #[inline] pub fn from_bytes(bytes: Vec) -> Self { let temp_id = NEXT_ID.fetch_add(1, Ordering::Relaxed); - Blob { id: OnceCell::new(), data: BlobData::Owned(bytes), temp_id } + Blob { id: OnceLock::new(), data: BlobData::Owned(bytes), temp_id } } /// Create a new `Blob` with a pre-computed ID and owned data. #[inline] pub fn new(id: BlobId, bytes: Vec) -> Self { let temp_id = NEXT_ID.fetch_add(1, Ordering::Relaxed); - let cell = OnceCell::new(); + let cell = OnceLock::new(); let _ = cell.set(id); Blob { id: cell, data: BlobData::Owned(bytes), temp_id } } @@ -204,7 +203,7 @@ impl<'a> Blob<'a> { #[inline] pub fn from_borrowed(bytes: &'a [u8]) -> Self { let temp_id = NEXT_ID.fetch_add(1, Ordering::Relaxed); - Blob { id: OnceCell::new(), data: BlobData::Borrowed(bytes), temp_id } + Blob { id: OnceLock::new(), data: BlobData::Borrowed(bytes), temp_id } } } @@ -317,8 +316,8 @@ impl JsonSchema for BlobId { "BlobId".into() } - fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { - let s = String::json_schema(gen); + fn json_schema(r#gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { + let s = String::json_schema(r#gen); let mut o = s.into_object(); o.string().pattern = Some("[0-9a-f]{40}".into()); let md = o.metadata(); diff --git a/crates/kingfisher-core/src/content_type.rs b/crates/kingfisher-core/src/content_type.rs index 7fe3b63..c4ca22d 100644 --- a/crates/kingfisher-core/src/content_type.rs +++ b/crates/kingfisher-core/src/content_type.rs @@ -1,10 +1,10 @@ -use once_cell::sync::Lazy; use std::path::Path; +use std::sync::LazyLock; use tokei::LanguageType; // Precompute all (shebang_prefix_bytes, language) pairs once. // Sort longest-first so more specific shebangs win. -static SHEBANG_PREFIXES: Lazy> = Lazy::new(|| { +static SHEBANG_PREFIXES: LazyLock> = LazyLock::new(|| { let mut v = Vec::new(); for &(lang, shebangs) in LanguageType::list() { for &sb in shebangs { @@ -113,10 +113,10 @@ impl ContentInspector { #[must_use] pub fn guess_language(&self, path: &Path, content: &[u8]) -> Option { // 1) Extension mapping (turbo, no I/O). - if let Some(ext) = path.extension().and_then(|e| e.to_str()) { - if let Some(lang) = LanguageType::from_file_extension(&ext.to_ascii_lowercase()) { - return Some(lang.name().to_string()); - } + if let Some(ext) = path.extension().and_then(|e| e.to_str()) + && let Some(lang) = LanguageType::from_file_extension(&ext.to_ascii_lowercase()) + { + return Some(lang.name().to_string()); } // 2) Well-known filenames with no/odd extensions (avoid from_path to keep this pure). diff --git a/crates/kingfisher-core/src/git_commit_metadata.rs b/crates/kingfisher-core/src/git_commit_metadata.rs index 4268d5d..e4cbee7 100644 --- a/crates/kingfisher-core/src/git_commit_metadata.rs +++ b/crates/kingfisher-core/src/git_commit_metadata.rs @@ -65,8 +65,8 @@ impl JsonSchema for TextTime { "Time".into() } - fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { - String::json_schema(gen) + fn json_schema(r#gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { + String::json_schema(r#gen) } } @@ -128,8 +128,8 @@ impl JsonSchema for HexObjectId { "ObjectId".into() } - fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { - let s = String::json_schema(gen); + fn json_schema(r#gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { + let s = String::json_schema(r#gen); let mut o = s.into_object(); o.string().pattern = Some("[0-9a-f]{40}".into()); let md = o.metadata(); diff --git a/crates/kingfisher-core/src/origin.rs b/crates/kingfisher-core/src/origin.rs index 278b9d7..af8a02f 100644 --- a/crates/kingfisher-core/src/origin.rs +++ b/crates/kingfisher-core/src/origin.rs @@ -8,11 +8,10 @@ use std::{ path::{Path, PathBuf}, - sync::Arc, + sync::{Arc, LazyLock}, }; use dashmap::DashMap; -use once_cell::sync::Lazy; use rustc_hash::FxHashSet; use schemars::JsonSchema; use serde::{ser::SerializeSeq, Deserialize, Serialize}; @@ -21,7 +20,7 @@ use smallvec::SmallVec; use crate::git_commit_metadata::CommitMetadata; // Cache for git remote URLs to avoid repeated lookups -static URL_CACHE: Lazy>> = Lazy::new(DashMap::default); +static URL_CACHE: LazyLock>> = LazyLock::new(DashMap::default); fn compute_url(repo_path: &Path) -> anyhow::Result { let repo = gix::open(repo_path)?; @@ -35,12 +34,10 @@ fn compute_url(repo_path: &Path) -> anyhow::Result { Ok(String::from_utf8_lossy(url_bytes.as_bytes()).into_owned()) } else if url_bytes.starts_with(b"git@") { let url_str = String::from_utf8_lossy(url_bytes.as_bytes()); - if let Some(stripped) = url_str.strip_prefix("git@") { - if let Some((domain, path)) = stripped.split_once(':') { - Ok(format!("https://{}/{}", domain, path)) - } else { - Err(anyhow::anyhow!("Invalid SSH URL format")) - } + if let Some(stripped) = url_str.strip_prefix("git@") + && let Some((domain, path)) = stripped.split_once(':') + { + Ok(format!("https://{}/{}", domain, path)) } else { Err(anyhow::anyhow!("Invalid SSH URL format")) } @@ -68,7 +65,7 @@ pub fn get_repo_url(repo_path: &Path) -> anyhow::Result> { /// The provenance of a scanned blob. #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case", tag = "kind")] -#[allow(clippy::large_enum_variant)] +#[expect(clippy::large_enum_variant)] pub enum Origin { /// Content from a file on disk. File(FileOrigin), @@ -219,8 +216,8 @@ impl JsonSchema for OriginSet { "OriginSet".into() } - fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { - let s = >::json_schema(gen); + fn json_schema(r#gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { + let s = >::json_schema(r#gen); let mut o = s.into_object(); o.array().min_items = Some(1); let md = o.metadata(); @@ -279,7 +276,7 @@ impl OriginSet { } /// Returns the number of origins in the set. - #[allow(clippy::len_without_is_empty)] + #[expect(clippy::len_without_is_empty)] #[inline] pub fn len(&self) -> usize { 1 + self.more_provenance.len() diff --git a/crates/kingfisher-rules/Cargo.toml b/crates/kingfisher-rules/Cargo.toml index 420247a..6c5f195 100644 --- a/crates/kingfisher-rules/Cargo.toml +++ b/crates/kingfisher-rules/Cargo.toml @@ -26,7 +26,6 @@ thiserror.workspace = true # Regex regex.workspace = true -lazy_static = "1.5" # Hashing xxhash-rust.workspace = true diff --git a/crates/kingfisher-rules/data/rules/arcjet.yml b/crates/kingfisher-rules/data/rules/arcjet.yml index 1709b66..3c52211 100644 --- a/crates/kingfisher-rules/data/rules/arcjet.yml +++ b/crates/kingfisher-rules/data/rules/arcjet.yml @@ -5,15 +5,24 @@ rules: (?x) \b ( - ajkey_[A-Za-z0-9]{24,44} + ajkey_[a-z0-9]{26} ) \b pattern_requirements: - min_digits: 1 - min_entropy: 3.0 + min_digits: 3 + min_lowercase: 6 + min_entropy: 3.5 confidence: medium + categories: [api, key] examples: - - 'ARCJET_KEY=ajkey_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6' - - 'ARCJET_KEY="ajkey_xK8m2LpQr5nW0vYz3cJ7aB4dE6fG8h"' + - 'ARCJET_KEY=ajkey_01kpe3gbb3ek3asrwvfsmtjtam' + - 'ARCJET_KEY="ajkey_01kpe3k7r4n6v9s2q1w8x5y7zc"' references: - - https://docs.arcjet.com/get-started + - https://docs.arcjet.com/environment + - https://docs.arcjet.com/reference/nodejs + - https://docs.arcjet.com/troubleshooting/ + - https://github.com/arcjet/arcjet-js/blob/main/protocol/client.ts + - https://github.com/arcjet/arcjet-js/blob/main/protocol/proto/decide/v1alpha1/decide_pb.d.ts + # No standalone validation is added: Arcjet's public DecideService endpoint + # accepts multiple mutated ajkey_ values and returns CONCLUSION_ALLOW, so it + # is not reliable for distinguishing valid from invalid site keys. \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/axiom.yml b/crates/kingfisher-rules/data/rules/axiom.yml index 0c06458..db3c160 100644 --- a/crates/kingfisher-rules/data/rules/axiom.yml +++ b/crates/kingfisher-rules/data/rules/axiom.yml @@ -10,7 +10,7 @@ rules: \b confidence: medium examples: - - 'AXIOM_TOKEN=xaat-a1b2c3d4-e5f6-7890-abcd-ef1234567890' + - 'AXIOM_TOKEN=xaat-fa97e617-e653-45a8-2f3b-f1c0ccf731ae' - 'Authorization: Bearer xaat-deadbeef-1234-5678-9abc-def012345678' references: - https://axiom.co/docs/reference/tokens @@ -41,7 +41,7 @@ rules: \b confidence: medium examples: - - 'AXIOM_TOKEN=xapt-a1b2c3d4-e5f6-7890-abcd-ef1234567890' + - 'AXIOM_TOKEN=xapt-726c99a4-1d8c-4ccb-8e54-e89f21b25f4e' - 'Authorization: Bearer xapt-deadbeef-1234-5678-9abc-def012345678' references: - https://axiom.co/docs/reference/tokens @@ -57,5 +57,9 @@ rules: response_matcher: - report_response: true - type: StatusMatch - status: [200] + status: [400] - type: JsonValid + - type: WordMatch + words: + - '"forbidden"' + negative: true \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/blockprotocol.yml b/crates/kingfisher-rules/data/rules/blockprotocol.yml index 3ac0aab..34925bc 100644 --- a/crates/kingfisher-rules/data/rules/blockprotocol.yml +++ b/crates/kingfisher-rules/data/rules/blockprotocol.yml @@ -5,7 +5,7 @@ rules: (?x) \b ( - b10ck5\.[a-zA-Z0-9]{28,36}\.[a-zA-Z0-9]{32,40} + b10ck5\.[a-zA-Z0-9]{32}\.[a-zA-Z0-9]{36} ) \b pattern_requirements: @@ -14,7 +14,7 @@ rules: confidence: high categories: [api, key] examples: - - 'BLOCK_PROTOCOL_API_KEY=b10ck5.AbCdEfGhIjKlMnOpQrStUvWxYz1234.AbCdEfGhIjKlMnOpQrStUvWxYz12345678' + - 'BLOCK_PROTOCOL_API_KEY=b10ck5.AbCdEfGhIjKlMnOpQrStUvWxYz123456.AbCdEfGhIjKlMnOpQrStUvWxYz1234567890' references: - https://blockprotocol.org/docs/hub/api validation: diff --git a/crates/kingfisher-rules/data/rules/browseruse.yml b/crates/kingfisher-rules/data/rules/browseruse.yml index be13e63..67bda1f 100644 --- a/crates/kingfisher-rules/data/rules/browseruse.yml +++ b/crates/kingfisher-rules/data/rules/browseruse.yml @@ -2,13 +2,10 @@ rules: - name: Browser Use API Key id: kingfisher.browseruse.1 pattern: | - (?xi) - \b - (?:browser[-_\s]?use|BROWSER_USE_API_KEY) - (?:.|[\n\r]){0,48}? + (?x) \b ( - bu_[A-Za-z0-9_-]{32}(?:[A-Za-z0-9_-]{16}){0,4} + bu_[A-Za-z0-9_-]{42,46} ) \b pattern_requirements: @@ -18,8 +15,8 @@ rules: min_entropy: 3.5 confidence: medium examples: - - 'BROWSER_USE_API_KEY="bu_w82Kp7LzR4QnV6tYp9CmX3aSb5DgHjK1"' - - 'browser-use apiKey: "bu_P7rT2mK9vL4qN8sX6cA3dF5gH1jZ0QaB"' + - 'BROWSER_USE_API_KEY="bu_7U8x7aOi0uXig8bf3ubvJLqb7TWf_pskUMN5XTPfUCg"' + - 'browser-use apiKey: "bu_oMANI1IOblFfFeBillhzHIuOG9MmVO7qAnSE4_eRBWk"' references: - https://docs.browser-use.com/cloud/quickstart - https://docs.browser-use.com/llms-full.txt diff --git a/crates/kingfisher-rules/data/rules/calcom.yml b/crates/kingfisher-rules/data/rules/calcom.yml index 502f82f..97380a4 100644 --- a/crates/kingfisher-rules/data/rules/calcom.yml +++ b/crates/kingfisher-rules/data/rules/calcom.yml @@ -5,7 +5,7 @@ rules: (?x) \b ( - cal_live_[A-Za-z0-9]{24,44} + cal(?:_live)?_[A-Za-z0-9]{24,44} ) \b pattern_requirements: @@ -13,6 +13,7 @@ rules: min_entropy: 3.0 confidence: medium examples: + - 'CAL_API_KEY=cal_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6' - 'CAL_API_KEY=cal_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6' - 'Authorization: Bearer cal_live_xK8m2LpQr5nW0vYz3cJ7aB4dE6fG8h' references: diff --git a/crates/kingfisher-rules/data/rules/convex.yml b/crates/kingfisher-rules/data/rules/convex.yml index d6d0b35..e9cff92 100644 --- a/crates/kingfisher-rules/data/rules/convex.yml +++ b/crates/kingfisher-rules/data/rules/convex.yml @@ -5,12 +5,19 @@ rules: (?x) \b ( - (?:prod|dev|preview):[a-z]+-[a-z]+-\d+\|eyJ[A-Za-z0-9_.-]{50,500} + (?: + (?:prod|dev):[a-z]+-[a-z]+-\d+ + | + (?:preview|project):[a-z0-9-]+:[a-z0-9-]+ + ) + \|eyJ[A-Za-z0-9._=-]{20,500} ) (?:\b|$) min_entropy: 3.5 confidence: medium examples: - 'CONVEX_DEPLOY_KEY=prod:qualified-jaguar-123|eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiIwMThmNmY5NThmNTI3NjkwYjE1M2NkNDMiLCJpYXQiOjE3MjQ3NzA4MTJ9.bQkJz3sXt' + - 'CONVEX_DEPLOY_KEY=preview:my-team:my-project|eyJ2ZXIiOiIxIiwiYWxnIjoiSFMyNTYifQ.eyJpc3MiOiJjb252ZXgifQ.dGVzdA==' + - 'CONVEX_DEPLOY_KEY=project:my-team:my-project|eyJ2ZXIiOiIxIiwiYWxnIjoiSFMyNTYifQ.eyJpc3MiOiJjb252ZXgifQ.dGVzdA==' references: - - https://docs.convex.dev/production/hosting/deploy-key + - https://docs.convex.dev/cli/deploy-key-types diff --git a/crates/kingfisher-rules/data/rules/crossmint.yml b/crates/kingfisher-rules/data/rules/crossmint.yml index 175bb5d..a4b1bfe 100644 --- a/crates/kingfisher-rules/data/rules/crossmint.yml +++ b/crates/kingfisher-rules/data/rules/crossmint.yml @@ -22,7 +22,12 @@ rules: content: request: method: GET - url: https://www.crossmint.com/api/v1-alpha2/wallets + url: > + {%- if TOKEN contains "sk_staging_" -%} + https://staging.crossmint.com/api/v1-alpha2/wallets + {%- else -%} + https://www.crossmint.com/api/v1-alpha2/wallets + {%- endif -%} headers: x-api-key: "{{ TOKEN }}" Accept: application/json diff --git a/crates/kingfisher-rules/src/liquid_filters.rs b/crates/kingfisher-rules/src/liquid_filters.rs index 16219da..1fd7920 100644 --- a/crates/kingfisher-rules/src/liquid_filters.rs +++ b/crates/kingfisher-rules/src/liquid_filters.rs @@ -26,7 +26,7 @@ macro_rules! static_filter { // ── original, zero-arg variant ──────────────────────────────── ( $(#[$outer:meta])* - $name:ident, $display:literal, $body:expr + $name:ident, $display:literal, $body:expr_2021 ) => { $(#[$outer])* #[derive(Debug, Clone, FilterReflection, ParseFilter, Default)] @@ -54,7 +54,7 @@ macro_rules! static_filter { $(#[$outer:meta])* $name:ident { $( $(#[$f_meta:meta])* $field:ident : $ty:ty ),+ $(,)? }, $display:literal, - $body:expr + $body:expr_2021 ) => { $(#[$outer])* #[derive(Debug, Clone, Default, FilterReflection, ParseFilter)] // ← added Default diff --git a/crates/kingfisher-rules/src/rule.rs b/crates/kingfisher-rules/src/rule.rs index 07eec09..f42a534 100644 --- a/crates/kingfisher-rules/src/rule.rs +++ b/crates/kingfisher-rules/src/rule.rs @@ -6,17 +6,17 @@ use std::{ borrow::Cow, cmp::Ordering, collections::BTreeMap, fmt, hash::Hash, path::Path, str::FromStr, + sync::LazyLock, }; use anyhow::{anyhow, Context, Result}; -use lazy_static::lazy_static; use liquid::{ model::{KString, Value}, object, ParserBuilder, }; use regex::Regex; use schemars::{ - gen::SchemaGenerator, + r#gen::SchemaGenerator, schema::{Schema, SchemaObject}, JsonSchema, }; @@ -649,16 +649,17 @@ pub struct RuleSyntax { pub tls_mode: Option, } -lazy_static! { - /// Regex pattern used to remove vectorscan-style comments from rule patterns. - pub static ref RULE_COMMENTS_PATTERN: Regex = Regex::new( - r"(?m)(\(\?#[^)]*\))|(\s\#[\sa-zA-Z]*$)" - ).expect("comment-stripping regex should compile"); - static ref PATTERN_REQUIREMENTS_TEMPLATE_PARSER: liquid::Parser = - liquid_filters::register_all(ParserBuilder::with_stdlib()) - .build() - .expect("pattern requirement template parser should compile"); -} +/// Regex pattern used to remove vectorscan-style comments from rule patterns. +pub static RULE_COMMENTS_PATTERN: LazyLock = LazyLock::new(|| { + Regex::new(r"(?m)(\(\?#[^)]*\))|(\s\#[\sa-zA-Z]*$)") + .expect("comment-stripping regex should compile") +}); + +static PATTERN_REQUIREMENTS_TEMPLATE_PARSER: LazyLock = LazyLock::new(|| { + liquid_filters::register_all(ParserBuilder::with_stdlib()) + .build() + .expect("pattern requirement template parser should compile") +}); impl RuleSyntax { /// Maximum allowed regex size. diff --git a/crates/kingfisher-scanner/Cargo.toml b/crates/kingfisher-scanner/Cargo.toml index 743b0d1..d4f01fc 100644 --- a/crates/kingfisher-scanner/Cargo.toml +++ b/crates/kingfisher-scanner/Cargo.toml @@ -149,7 +149,6 @@ rustc-hash.workspace = true # Concurrency parking_lot.workspace = true thread_local = "1.1" -once_cell.workspace = true crossbeam-skiplist = "0.1.3" # HTTP status codes diff --git a/crates/kingfisher-scanner/src/finding.rs b/crates/kingfisher-scanner/src/finding.rs index 3ac5765..04675e5 100644 --- a/crates/kingfisher-scanner/src/finding.rs +++ b/crates/kingfisher-scanner/src/finding.rs @@ -1,7 +1,7 @@ //! Finding types representing detected secrets. use std::collections::HashMap; -use std::sync::Arc; +use std::sync::{Arc, LazyLock}; use kingfisher_core::{BlobId, Location}; use kingfisher_rules::{Confidence, Rule}; @@ -11,8 +11,8 @@ use serde::{Deserialize, Serialize}; use smallvec::SmallVec; // Thread-safe string interner for capture values -static STRING_POOL: once_cell::sync::Lazy>> = - once_cell::sync::Lazy::new(|| RwLock::new(std::collections::HashSet::new())); +static STRING_POOL: LazyLock>> = + LazyLock::new(|| RwLock::new(std::collections::HashSet::new())); /// Intern a string to get a static reference. /// diff --git a/crates/kingfisher-scanner/src/validation/aws.rs b/crates/kingfisher-scanner/src/validation/aws.rs index 6e32627..ce03ee0 100644 --- a/crates/kingfisher-scanner/src/validation/aws.rs +++ b/crates/kingfisher-scanner/src/validation/aws.rs @@ -3,7 +3,11 @@ //! This module provides functionality to validate AWS access keys by making //! an STS GetCallerIdentity call. -use std::{collections::HashSet, sync::RwLock, time::Duration}; +use std::{ + collections::HashSet, + sync::{LazyLock, OnceLock, RwLock}, + time::Duration, +}; use anyhow::{anyhow, Result}; use aws_config::{retry::RetryConfig, BehaviorVersion, SdkConfig}; @@ -35,7 +39,6 @@ use http::{ header::{HeaderValue, USER_AGENT}, StatusCode, }; -use once_cell::sync::{Lazy, OnceCell}; use rand::{rng, RngExt}; use regex::Regex; use tokio::{ @@ -45,7 +48,7 @@ use tokio::{ use super::GLOBAL_USER_AGENT; -static AWS_VALIDATION_SEMAPHORE: OnceCell = OnceCell::new(); +static AWS_VALIDATION_SEMAPHORE: OnceLock = OnceLock::new(); /// Built-in list of known canary/honeypot AWS account IDs that should be skipped. const BUILTIN_SKIP_ACCOUNT_IDS: &[&str] = &[ @@ -60,7 +63,7 @@ const BUILTIN_SKIP_ACCOUNT_IDS: &[&str] = &[ "992382622183", ]; -static AWS_SKIP_ACCOUNT_IDS: Lazy>> = Lazy::new(|| { +static AWS_SKIP_ACCOUNT_IDS: LazyLock>> = LazyLock::new(|| { let mut set = HashSet::new(); set.extend(BUILTIN_SKIP_ACCOUNT_IDS.iter().map(|id| id.to_string())); RwLock::new(set) @@ -97,7 +100,7 @@ fn extract_account_id(input: &str) -> Option { return Some(trimmed.to_string()); } - static ACCOUNT_ID_RE: Lazy = Lazy::new(|| Regex::new(r"(\d{12})").expect("valid regex")); + static ACCOUNT_ID_RE: LazyLock = LazyLock::new(|| Regex::new(r"(\d{12})").expect("valid regex")); ACCOUNT_ID_RE.captures(trimmed).and_then(|caps| caps.get(1)).map(|m| m.as_str().to_string()) } diff --git a/crates/kingfisher-scanner/src/validation/coinbase.rs b/crates/kingfisher-scanner/src/validation/coinbase.rs index 67c0817..ec4951f 100644 --- a/crates/kingfisher-scanner/src/validation/coinbase.rs +++ b/crates/kingfisher-scanner/src/validation/coinbase.rs @@ -82,9 +82,8 @@ fn build_jwt( let nonce: [u8; 16] = rand::random(); - if let Ok(secret_key) = - SecretKey::from_sec1_pem(&pem).or_else(|_| SecretKey::from_pkcs8_pem(&pem)) - { + match SecretKey::from_sec1_pem(&pem).or_else(|_| SecretKey::from_pkcs8_pem(&pem)) + { Ok(secret_key) => { let signing_key = SigningKey::from(secret_key); let header = serde_json::json!({ "typ": "JWT", @@ -109,7 +108,7 @@ fn build_jwt( let sig_b64 = URL_SAFE_NO_PAD.encode(sig.to_bytes()); return Ok(format!("{signing_input}.{sig_b64}")); - } else { + } _ => { let key_bytes = base64::engine::general_purpose::STANDARD .decode(pem.as_bytes()) .map_err(|e| anyhow!("invalid base64 key: {e}"))?; @@ -148,5 +147,5 @@ fn build_jwt( let sig: ed25519_dalek::Signature = signing_key.sign(signing_input.as_bytes()); let sig_b64 = URL_SAFE_NO_PAD.encode(sig.to_bytes()); return Ok(format!("{signing_input}.{sig_b64}")); - } + }} } diff --git a/crates/kingfisher-scanner/src/validation/gcp.rs b/crates/kingfisher-scanner/src/validation/gcp.rs index 0cbc5ae..d7dd63b 100644 --- a/crates/kingfisher-scanner/src/validation/gcp.rs +++ b/crates/kingfisher-scanner/src/validation/gcp.rs @@ -1,9 +1,8 @@ -use std::sync::Arc; +use std::sync::{Arc, OnceLock}; use anyhow::{anyhow, Result}; use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; use chrono::{Duration as ChronoDuration, Utc}; -use once_cell::sync::OnceCell; use pem::parse; use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; use reqwest::{Client, Proxy}; @@ -14,7 +13,7 @@ use tracing::debug; use super::GLOBAL_USER_AGENT; -static GLOBAL_VALIDATOR: OnceCell = OnceCell::new(); +static GLOBAL_VALIDATOR: OnceLock = OnceLock::new(); pub struct GcpValidator { semaphore: Arc, @@ -39,7 +38,11 @@ pub struct GcpRevocationOutcome { impl GcpValidator { pub fn global() -> Result<&'static Self> { - GLOBAL_VALIDATOR.get_or_try_init(Self::new) + if let Some(v) = GLOBAL_VALIDATOR.get() { + return Ok(v); + } + let v = Self::new()?; + Ok(GLOBAL_VALIDATOR.get_or_init(|| v)) } /// Retrieve a reference to the underlying HTTP client. diff --git a/crates/kingfisher-scanner/src/validation/jwt.rs b/crates/kingfisher-scanner/src/validation/jwt.rs index b04a206..19d03cf 100644 --- a/crates/kingfisher-scanner/src/validation/jwt.rs +++ b/crates/kingfisher-scanner/src/validation/jwt.rs @@ -5,12 +5,12 @@ use chrono::Utc; use jsonwebtoken::{ decode, decode_header, jwk::JwkSet, Algorithm, DecodingKey, Validation as JwtValidation, }; -use once_cell::sync::Lazy; use reqwest::{redirect::Policy, Client, Url}; use serde::Deserialize; +use std::sync::LazyLock; /// Global redirect-free client with strict TLS validation. -static STRICT_CLIENT: Lazy = Lazy::new(|| { +static STRICT_CLIENT: LazyLock = LazyLock::new(|| { Client::builder() .redirect(Policy::none()) .danger_accept_invalid_certs(false) @@ -19,7 +19,7 @@ static STRICT_CLIENT: Lazy = Lazy::new(|| { }); /// Global redirect-free client with lax TLS validation (accepts any cert). -static LAX_CLIENT: Lazy = Lazy::new(|| { +static LAX_CLIENT: LazyLock = LazyLock::new(|| { Client::builder() .redirect(Policy::none()) .danger_accept_invalid_certs(true) diff --git a/crates/kingfisher-scanner/src/validation/mod.rs b/crates/kingfisher-scanner/src/validation/mod.rs index b242e90..b1d36e9 100644 --- a/crates/kingfisher-scanner/src/validation/mod.rs +++ b/crates/kingfisher-scanner/src/validation/mod.rs @@ -74,7 +74,7 @@ pub use http_validation::{ pub use raw::{required_vars as raw_required_vars, validate_raw, RawValidationOutcome}; #[cfg(feature = "validation-http")] -#[allow(deprecated)] +#[expect(deprecated)] pub use http_validation::check_url_resolvable_safe; #[cfg(feature = "validation-aws")] @@ -84,9 +84,8 @@ pub use aws::{ validate_aws_credentials, validate_aws_credentials_input, }; -use once_cell::sync::OnceCell; use std::{ - sync::Arc, + sync::{Arc, LazyLock, OnceLock}, time::{Duration, Instant}, }; @@ -94,11 +93,10 @@ use crossbeam_skiplist::SkipMap; /// User agent string used for HTTP validation requests. #[cfg(feature = "validation-http")] -pub static GLOBAL_USER_AGENT: once_cell::sync::Lazy = - once_cell::sync::Lazy::new(build_user_agent); +pub static GLOBAL_USER_AGENT: LazyLock = LazyLock::new(build_user_agent); #[cfg(feature = "validation-http")] -static USER_AGENT_SUFFIX: OnceCell = OnceCell::new(); +static USER_AGENT_SUFFIX: OnceLock = OnceLock::new(); #[cfg(feature = "validation-http")] const BROWSER_USER_AGENT: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) \ diff --git a/crates/kingfisher-scanner/src/validation/postgres.rs b/crates/kingfisher-scanner/src/validation/postgres.rs index e19b61e..c79f763 100644 --- a/crates/kingfisher-scanner/src/validation/postgres.rs +++ b/crates/kingfisher-scanner/src/validation/postgres.rs @@ -1,7 +1,10 @@ -use std::{str::FromStr, sync::Arc, time::Duration}; +use std::{ + str::FromStr, + sync::{Arc, OnceLock}, + time::Duration, +}; use anyhow::{anyhow, Result}; -use once_cell::sync::OnceCell; use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; use rustls::crypto::{ring, verify_tls12_signature, verify_tls13_signature, CryptoProvider}; use rustls::pki_types::{CertificateDer, ServerName, UnixTime}; @@ -19,7 +22,7 @@ use tracing::debug; const CONNECT_TIMEOUT: Duration = Duration::from_secs(5); -static INIT_PROVIDER: OnceCell<()> = OnceCell::new(); +static INIT_PROVIDER: OnceLock<()> = OnceLock::new(); fn ensure_crypto_provider() { INIT_PROVIDER.get_or_init(|| { let _ = CryptoProvider::install_default(ring::default_provider()); diff --git a/crates/kingfisher-scanner/src/validation/raw.rs b/crates/kingfisher-scanner/src/validation/raw.rs index 3436272..aaf33d4 100644 --- a/crates/kingfisher-scanner/src/validation/raw.rs +++ b/crates/kingfisher-scanner/src/validation/raw.rs @@ -13,7 +13,6 @@ use http::StatusCode; use ldap3::LdapConnSettings; use liquid::Object; use liquid_core::ValueView; -use once_cell::sync::OnceCell; use percent_encoding::percent_decode_str; use reqwest::Client; use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; @@ -37,7 +36,7 @@ pub struct RawValidationOutcome { pub body: String, } -static INIT_PROVIDER: OnceCell<()> = OnceCell::new(); +static INIT_PROVIDER: OnceLock<()> = OnceLock::new(); static LAX_PROVIDER: OnceLock> = OnceLock::new(); fn ensure_crypto_provider() { diff --git a/crates/kingfisher-scanner/src/validation/validation_body.rs b/crates/kingfisher-scanner/src/validation/validation_body.rs index 1f03573..022869d 100644 --- a/crates/kingfisher-scanner/src/validation/validation_body.rs +++ b/crates/kingfisher-scanner/src/validation/validation_body.rs @@ -2,7 +2,7 @@ #![allow(dead_code)] // Public API for serde attributes in downstream crates -use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema}; +use schemars::{r#gen::SchemaGenerator, schema::Schema, JsonSchema}; use serde::{Deserialize, Deserializer, Serializer}; use std::borrow::Cow; @@ -51,8 +51,8 @@ where } /// Generate a JSON schema for ValidationResponseBody. -pub fn schema(gen: &mut SchemaGenerator) -> Schema { - String::json_schema(gen) +pub fn schema(r#gen: &mut SchemaGenerator) -> Schema { + String::json_schema(r#gen) } #[cfg(test)] diff --git a/src/access_map/algolia.rs b/src/access_map/algolia.rs index 826bc72..2a70e56 100644 --- a/src/access_map/algolia.rs +++ b/src/access_map/algolia.rs @@ -17,7 +17,7 @@ use super::{ #[derive(Debug, Deserialize)] struct AlgoliaKeyInfo { - #[allow(dead_code)] + #[expect(dead_code)] #[serde(default)] value: String, #[serde(default)] diff --git a/src/access_map/artifactory.rs b/src/access_map/artifactory.rs index 4388567..86ca74a 100644 --- a/src/access_map/artifactory.rs +++ b/src/access_map/artifactory.rs @@ -18,7 +18,7 @@ struct ArtifactoryUser { name: Option, email: Option, admin: Option, - #[allow(dead_code)] + #[expect(dead_code)] #[serde(rename = "profileUpdatable")] profile_updatable: Option, #[serde(default)] diff --git a/src/access_map/azure_devops.rs b/src/access_map/azure_devops.rs index b1e20e6..8ff4464 100644 --- a/src/access_map/azure_devops.rs +++ b/src/access_map/azure_devops.rs @@ -452,10 +452,10 @@ fn select_matching_pat( let mut candidates: Vec<&AzureDevopsPat> = pats .iter() .filter(|pat| { - if let Some(user_id) = user_id { - if let Some(pat_user_id) = pat.user_id.as_deref() { - return pat_user_id == user_id; - } + if let Some(user_id) = user_id + && let Some(pat_user_id) = pat.user_id.as_deref() + { + return pat_user_id == user_id; } true }) diff --git a/src/access_map/circleci.rs b/src/access_map/circleci.rs index d033db9..ed89c59 100644 --- a/src/access_map/circleci.rs +++ b/src/access_map/circleci.rs @@ -47,7 +47,7 @@ struct CircleCiPipeline { #[serde(default)] project_slug: Option, #[serde(default)] - #[allow(dead_code)] + #[expect(dead_code)] created_at: Option, } diff --git a/src/access_map/digitalocean.rs b/src/access_map/digitalocean.rs index 0965377..679bc7a 100644 --- a/src/access_map/digitalocean.rs +++ b/src/access_map/digitalocean.rs @@ -25,7 +25,7 @@ struct DigitalOceanAccount { #[serde(default)] uuid: Option, #[serde(default)] - #[allow(dead_code)] + #[expect(dead_code)] status: Option, #[serde(default)] team: Option, @@ -36,7 +36,7 @@ struct DigitalOceanTeam { #[serde(default)] name: Option, #[serde(default)] - #[allow(dead_code)] + #[expect(dead_code)] uuid: Option, } diff --git a/src/access_map/fastly.rs b/src/access_map/fastly.rs index 50cf29d..cc4b626 100644 --- a/src/access_map/fastly.rs +++ b/src/access_map/fastly.rs @@ -36,14 +36,14 @@ struct FastlyService { name: Option, #[serde(rename = "type", default)] service_type: Option, - #[allow(dead_code)] + #[expect(dead_code)] #[serde(default)] customer_id: Option, } #[derive(Deserialize)] struct FastlyCustomer { - #[allow(dead_code)] + #[expect(dead_code)] #[serde(default)] id: Option, #[serde(default)] diff --git a/src/access_map/gcp.rs b/src/access_map/gcp.rs index a468a01..4e72acc 100644 --- a/src/access_map/gcp.rs +++ b/src/access_map/gcp.rs @@ -468,12 +468,11 @@ fn extract_roles(policy: &Value, client_email: &str) -> Vec { let mut role_bindings = Vec::new(); if let Some(bindings) = policy["bindings"].as_array() { for binding in bindings { - if let Some(role_name) = binding["role"].as_str() { - if let Some(members) = binding["members"].as_array() { - if members.iter().any(|m| m.as_str() == Some(&email_member)) { - role_bindings.push(role_name.to_string()); - } - } + if let Some(role_name) = binding["role"].as_str() + && let Some(members) = binding["members"].as_array() + && members.iter().any(|m| m.as_str() == Some(&email_member)) + { + role_bindings.push(role_name.to_string()); } } } diff --git a/src/access_map/github.rs b/src/access_map/github.rs index a018553..664505d 100644 --- a/src/access_map/github.rs +++ b/src/access_map/github.rs @@ -33,7 +33,7 @@ const USER_LEVEL_PERMISSIONS: &[&str] = &[ #[derive(Deserialize)] struct GitHubUser { login: String, - #[allow(dead_code)] + #[expect(dead_code)] id: u64, #[serde(default)] name: Option, @@ -92,7 +92,7 @@ struct GitHubInstallation { #[serde(default)] permissions: BTreeMap, #[serde(default)] - #[allow(dead_code)] + #[expect(dead_code)] repository_selection: Option, #[serde(default)] account: Option, diff --git a/src/access_map/harness.rs b/src/access_map/harness.rs index cac4fb2..4775bd5 100644 --- a/src/access_map/harness.rs +++ b/src/access_map/harness.rs @@ -338,12 +338,11 @@ fn parse_collection(value: Value) -> Vec { fn extract_first_string(value: Option<&Value>, paths: &[&str]) -> Option { let value = value?; for path in paths { - if let Some(v) = value_at_path(value, path) { - if let Some(s) = v.as_str() { - if !s.is_empty() { - return Some(s.to_string()); - } - } + if let Some(v) = value_at_path(value, path) + && let Some(s) = v.as_str() + && !s.is_empty() + { + return Some(s.to_string()); } } None diff --git a/src/access_map/hubspot.rs b/src/access_map/hubspot.rs index 4d61dd7..5c56d1c 100644 --- a/src/access_map/hubspot.rs +++ b/src/access_map/hubspot.rs @@ -22,7 +22,7 @@ struct HubSpotAccountInfo { #[serde(default)] time_zone: Option, #[serde(default)] - #[allow(dead_code)] + #[expect(dead_code)] company_currency: Option, #[serde(default)] ui_domain: Option, @@ -31,7 +31,7 @@ struct HubSpotAccountInfo { #[derive(Deserialize)] struct HubSpotOwner { #[serde(default)] - #[allow(dead_code)] + #[expect(dead_code)] id: Option, #[serde(default)] email: Option, diff --git a/src/access_map/ibm_cloud.rs b/src/access_map/ibm_cloud.rs index ad31931..045adc8 100644 --- a/src/access_map/ibm_cloud.rs +++ b/src/access_map/ibm_cloud.rs @@ -18,7 +18,7 @@ struct IbmApiKeyDetails { #[serde(default)] id: Option, #[serde(default)] - #[allow(dead_code)] + #[expect(dead_code)] entity_tag: Option, #[serde(default)] iam_id: Option, @@ -27,7 +27,7 @@ struct IbmApiKeyDetails { #[serde(default)] name: Option, #[serde(default)] - #[allow(dead_code)] + #[expect(dead_code)] description: Option, #[serde(default)] created_at: Option, @@ -40,10 +40,10 @@ struct IbmTokenResponse { #[serde(default)] access_token: Option, #[serde(default)] - #[allow(dead_code)] + #[expect(dead_code)] token_type: Option, #[serde(default)] - #[allow(dead_code)] + #[expect(dead_code)] expires_in: Option, #[serde(default)] scope: Option, @@ -52,7 +52,7 @@ struct IbmTokenResponse { #[derive(Deserialize)] struct IbmResourceInstance { #[serde(default)] - #[allow(dead_code)] + #[expect(dead_code)] id: Option, #[serde(default)] name: Option, diff --git a/src/access_map/jira.rs b/src/access_map/jira.rs index a86cd54..a90a0c7 100644 --- a/src/access_map/jira.rs +++ b/src/access_map/jira.rs @@ -40,7 +40,7 @@ struct JiraPermissionEntry { #[derive(Deserialize)] struct JiraProject { - #[allow(dead_code)] + #[expect(dead_code)] id: Option, key: String, name: String, diff --git a/src/access_map/openai.rs b/src/access_map/openai.rs index 62587ec..33f0694 100644 --- a/src/access_map/openai.rs +++ b/src/access_map/openai.rs @@ -27,7 +27,7 @@ struct OpenAiMe { #[serde(default)] email: Option, #[serde(default)] - #[allow(dead_code)] + #[expect(dead_code)] role: Option, #[serde(default)] orgs: Option, diff --git a/src/access_map/paypal.rs b/src/access_map/paypal.rs index fed7d78..6b5e064 100644 --- a/src/access_map/paypal.rs +++ b/src/access_map/paypal.rs @@ -26,7 +26,7 @@ struct TokenResponse { scope: String, #[serde(default)] expires_in: i64, - #[allow(dead_code)] + #[expect(dead_code)] #[serde(default)] nonce: String, } diff --git a/src/access_map/postgres.rs b/src/access_map/postgres.rs index 9029d49..b433cd6 100644 --- a/src/access_map/postgres.rs +++ b/src/access_map/postgres.rs @@ -297,7 +297,7 @@ impl RoleAttributes { #[derive(Debug)] struct DatabasePrivilege { name: String, - #[allow(dead_code)] + #[expect(dead_code)] owner: String, privileges: Vec, } diff --git a/src/access_map/sendgrid.rs b/src/access_map/sendgrid.rs index 4afb59f..42766ee 100644 --- a/src/access_map/sendgrid.rs +++ b/src/access_map/sendgrid.rs @@ -23,7 +23,7 @@ struct SendGridAccount { #[derive(Deserialize)] struct SendGridProfile { #[serde(default)] - #[allow(dead_code)] + #[expect(dead_code)] address: Option, #[serde(default)] city: Option, @@ -36,7 +36,7 @@ struct SendGridProfile { #[serde(default)] last_name: Option, #[serde(default)] - #[allow(dead_code)] + #[expect(dead_code)] phone: Option, #[serde(default)] username: Option, @@ -51,7 +51,7 @@ struct SendGridScopesResponse { #[derive(Deserialize)] struct SendGridApiKey { #[serde(default)] - #[allow(dead_code)] + #[expect(dead_code)] api_key_id: Option, #[serde(default)] name: Option, diff --git a/src/access_map/sendinblue.rs b/src/access_map/sendinblue.rs index 4bd7e14..a45b957 100644 --- a/src/access_map/sendinblue.rs +++ b/src/access_map/sendinblue.rs @@ -38,7 +38,7 @@ struct BrevoPlan { #[derive(Deserialize)] struct BrevoSender { #[serde(default)] - #[allow(dead_code)] + #[expect(dead_code)] id: Option, #[serde(default)] name: Option, diff --git a/src/access_map/square.rs b/src/access_map/square.rs index 10c01ec..128bacd 100644 --- a/src/access_map/square.rs +++ b/src/access_map/square.rs @@ -27,10 +27,10 @@ struct SquareMerchant { business_name: Option, #[serde(default)] country: Option, - #[allow(dead_code)] + #[expect(dead_code)] #[serde(default)] currency: Option, - #[allow(dead_code)] + #[expect(dead_code)] #[serde(default)] status: Option, } @@ -43,7 +43,7 @@ struct SquareLocationsResponse { #[derive(Deserialize)] struct SquareLocation { - #[allow(dead_code)] + #[expect(dead_code)] #[serde(default)] id: Option, #[serde(default)] @@ -297,7 +297,7 @@ fn classify_key_type(token: &str) -> KeyClassification { } enum ScopeRisk { - #[allow(dead_code)] + #[expect(dead_code)] Admin, Risky, Read, diff --git a/src/access_map/stripe.rs b/src/access_map/stripe.rs index 76edfc3..989d94c 100644 --- a/src/access_map/stripe.rs +++ b/src/access_map/stripe.rs @@ -311,7 +311,7 @@ fn classify_key_prefix(token: &str) -> KeyType { } enum ScopeRisk { - #[allow(dead_code)] + #[expect(dead_code)] Admin, Risky, Read, diff --git a/src/access_map/terraform.rs b/src/access_map/terraform.rs index 9c83bdf..6272df7 100644 --- a/src/access_map/terraform.rs +++ b/src/access_map/terraform.rs @@ -44,7 +44,7 @@ struct TerraformOrgsResponse { #[derive(Deserialize)] struct TerraformOrgData { - #[allow(dead_code)] + #[expect(dead_code)] #[serde(default)] id: Option, #[serde(default)] @@ -55,7 +55,7 @@ struct TerraformOrgData { struct TerraformOrgAttributes { #[serde(default)] name: Option, - #[allow(dead_code)] + #[expect(dead_code)] #[serde(default)] email: Option, #[serde(default)] @@ -86,7 +86,7 @@ struct TerraformWorkspacesResponse { #[derive(Deserialize)] struct TerraformWorkspaceData { - #[allow(dead_code)] + #[expect(dead_code)] #[serde(default)] id: Option, #[serde(default)] diff --git a/src/access_map/xray.rs b/src/access_map/xray.rs index bc91f1f..5660d58 100644 --- a/src/access_map/xray.rs +++ b/src/access_map/xray.rs @@ -18,7 +18,7 @@ struct XrayRepo { name: Option, #[serde(rename = "type")] repo_type: Option, - #[allow(dead_code)] + #[expect(dead_code)] #[serde(rename = "pkg_type")] package_type: Option, } @@ -28,7 +28,7 @@ struct XrayPolicy { name: Option, #[serde(rename = "type")] policy_type: Option, - #[allow(dead_code)] + #[expect(dead_code)] author: Option, } diff --git a/src/access_map/zendesk.rs b/src/access_map/zendesk.rs index 8e58b65..1ce6924 100644 --- a/src/access_map/zendesk.rs +++ b/src/access_map/zendesk.rs @@ -42,7 +42,7 @@ struct ZendeskGroupsResponse { #[derive(Deserialize)] struct ZendeskGroup { - #[allow(dead_code)] + #[expect(dead_code)] id: Option, name: Option, } diff --git a/src/git_metadata_graph.rs b/src/git_metadata_graph.rs index efa204b..fb2c1c6 100644 --- a/src/git_metadata_graph.rs +++ b/src/git_metadata_graph.rs @@ -421,7 +421,7 @@ fn render_symbol_path(symbols: &BStringTable, path: &[Symbol]) -> BString { BString::from(buf) } -#[allow(clippy::too_many_arguments)] +#[expect(clippy::too_many_arguments)] fn visit_tree( repo: &gix::Repository, symbols: &mut BStringTable, diff --git a/src/github.rs b/src/github.rs index 6fbd956..a4614da 100644 --- a/src/github.rs +++ b/src/github.rs @@ -109,10 +109,10 @@ fn parse_excluded_repo(raw: &str) -> Option { return Some(name); } - if let Some(idx) = trimmed.rfind(':') { - if let Some(name) = parse_repo_name_from_path(&trimmed[idx + 1..]) { - return Some(name); - } + if let Some(idx) = trimmed.rfind(':') + && let Some(name) = parse_repo_name_from_path(&trimmed[idx + 1..]) + { + return Some(name); } parse_repo_name_from_path(trimmed) diff --git a/src/gitlab.rs b/src/gitlab.rs index b72ffc1..ce6014c 100644 --- a/src/gitlab.rs +++ b/src/gitlab.rs @@ -108,10 +108,10 @@ fn parse_excluded_project(raw: &str) -> Option { return Some(name); } - if let Some(idx) = trimmed.rfind(':') { - if let Some(name) = parse_project_path(&trimmed[idx + 1..]) { - return Some(name); - } + if let Some(idx) = trimmed.rfind(':') + && let Some(name) = parse_project_path(&trimmed[idx + 1..]) + { + return Some(name); } parse_project_path(trimmed) diff --git a/src/matcher/captures.rs b/src/matcher/captures.rs index 075f66f..3e4d102 100644 --- a/src/matcher/captures.rs +++ b/src/matcher/captures.rs @@ -1,7 +1,7 @@ use bstr::BString; use regex::bytes::Regex; use schemars::{ - gen::SchemaGenerator, + r#gen::SchemaGenerator, schema::{ArrayValidation, InstanceType, Schema}, JsonSchema, }; @@ -30,8 +30,8 @@ impl JsonSchema for Groups { "Groups".to_string() } - fn json_schema(gen: &mut SchemaGenerator) -> Schema { - let group_schema = gen.subschema_for::(); + fn json_schema(r#gen: &mut SchemaGenerator) -> Schema { + let group_schema = r#gen.subschema_for::(); Schema::Object(schemars::schema::SchemaObject { instance_type: Some(InstanceType::Array.into()), array: Some(Box::new(ArrayValidation { diff --git a/src/matcher/filter.rs b/src/matcher/filter.rs index 8987976..7964ccc 100644 --- a/src/matcher/filter.rs +++ b/src/matcher/filter.rs @@ -161,7 +161,7 @@ fn check_uri_validation(rule: &Rule, matching_input_bytes: &[u8]) -> bool { // filter_match — main entry point // ------------------------------------------------------------------------------------------------- -#[allow(clippy::too_many_arguments)] +#[expect(clippy::too_many_arguments)] pub(crate) fn filter_match<'b>( blob: &'b Blob, rule: Arc, diff --git a/src/reporter.rs b/src/reporter.rs index cc45332..16d1671 100644 --- a/src/reporter.rs +++ b/src/reporter.rs @@ -1268,12 +1268,11 @@ impl DetailsReporter { self.styles.style_finding_active_heading.apply_to(val) } - #[allow(dead_code)] + #[expect(dead_code)] fn style_rule(&self, val: D) -> StyledObject { self.styles.style_rule.apply_to(val) } - #[allow(dead_code)] fn style_heading(&self, val: D) -> StyledObject { self.styles.style_heading.apply_to(val) } diff --git a/src/scanner/runner.rs b/src/scanner/runner.rs index d61c671..65b229a 100644 --- a/src/scanner/runner.rs +++ b/src/scanner/runner.rs @@ -512,7 +512,7 @@ fn effective_max_validation_body_len(args: &scan::ScanArgs) -> usize { } /// Runs the validation phase on matches in the datastore. -#[allow(clippy::too_many_arguments)] +#[expect(clippy::too_many_arguments)] async fn run_validation_phase( datastore: &Arc>, validation_deps: &Option, @@ -545,7 +545,7 @@ async fn run_validation_phase( // Sequential scan path // ================================================================================================= -#[allow(clippy::too_many_arguments)] +#[expect(clippy::too_many_arguments)] async fn run_sequential_scan( args: &scan::ScanArgs, global_args: &global::GlobalArgs, @@ -640,7 +640,7 @@ async fn run_sequential_scan( // Parallel scan path // ================================================================================================= -#[allow(clippy::too_many_arguments)] +#[expect(clippy::too_many_arguments)] async fn run_parallel_scan( args: &scan::ScanArgs, global_args: &global::GlobalArgs, @@ -883,11 +883,11 @@ async fn run_parallel_scan( aggregate_summary, ); - if let Some(collector) = access_map_collector.take() { + match access_map_collector.take() { Some(collector) => { finalize_access_map(datastore, collector, args).await?; - } else { + } _ => { maybe_hint_access_map(datastore, args); - } + }} Ok(()) } diff --git a/src/scanner/validation.rs b/src/scanner/validation.rs index f6459db..74b9c66 100644 --- a/src/scanner/validation.rs +++ b/src/scanner/validation.rs @@ -397,7 +397,7 @@ impl AccessMapCollector { } } -#[allow(clippy::too_many_arguments)] +#[expect(clippy::too_many_arguments)] pub async fn run_secret_validation( datastore: Arc>, parser: &Parser, diff --git a/src/snippet.rs b/src/snippet.rs index 2b9c058..b76f742 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -5,7 +5,7 @@ use std::{ use base64::{engine::general_purpose, Engine as _}; use bstr::{BString, ByteSlice}; -use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema}; +use schemars::{r#gen::SchemaGenerator, schema::Schema, JsonSchema}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct Base64BString(#[serde(with = "Base64BString")] pub BString); @@ -19,8 +19,8 @@ impl JsonSchema for Base64BString { "Base64String".to_string() } - fn json_schema(gen: &mut SchemaGenerator) -> Schema { - String::json_schema(gen) + fn json_schema(r#gen: &mut SchemaGenerator) -> Schema { + String::json_schema(r#gen) } } impl Base64BString { diff --git a/src/teams.rs b/src/teams.rs index 7310586..4b9d222 100644 --- a/src/teams.rs +++ b/src/teams.rs @@ -28,7 +28,7 @@ struct SearchResult { struct HitsContainer { hits: Option>, more_results_available: Option, - #[allow(dead_code)] + #[expect(dead_code)] total: Option, } diff --git a/src/validation.rs b/src/validation.rs index 61503fb..dbe536f 100644 --- a/src/validation.rs +++ b/src/validation.rs @@ -586,15 +586,14 @@ async fn timed_validate_single_match<'a>( continue; } let dep_name = dep.variable.to_uppercase(); - if let Some(vals) = dependent_variables.get(&dep_name) { - if let Some((val, span)) = + if let Some(vals) = dependent_variables.get(&dep_name) + && let Some((val, span)) = select_closest_dependency_value(vals, m.matching_input_offset_span) - { - captured_values.push((dep_name.clone(), val.clone(), span.start, span.end)); - // Store the dependent capture for later use in reporting - // (e.g., generating validate/revoke commands) - m.dependent_captures.insert(dep_name, val); - } + { + captured_values.push((dep_name.clone(), val.clone(), span.start, span.end)); + // Store the dependent capture for later use in reporting + // (e.g., generating validate/revoke commands) + m.dependent_captures.insert(dep_name, val); } } diff --git a/src/validation_body.rs b/src/validation_body.rs index 96ce95f..e7fcf0e 100644 --- a/src/validation_body.rs +++ b/src/validation_body.rs @@ -1,4 +1,4 @@ -use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema}; +use schemars::{r#gen::SchemaGenerator, schema::Schema, JsonSchema}; use serde::{Deserialize, Deserializer, Serializer}; use std::borrow::Cow; @@ -41,6 +41,6 @@ where Ok(from_string(body)) } -pub fn schema(gen: &mut SchemaGenerator) -> Schema { - String::json_schema(gen) +pub fn schema(r#gen: &mut SchemaGenerator) -> Schema { + String::json_schema(r#gen) } diff --git a/tests/int_slack.rs b/tests/int_slack.rs index 45eedd6..78aaacf 100644 --- a/tests/int_slack.rs +++ b/tests/int_slack.rs @@ -191,7 +191,8 @@ async fn test_scan_slack_messages() -> Result<()> { .mount(&server) .await; - env::set_var("KF_SLACK_TOKEN", "xoxp-test"); + // TODO: Audit that the environment access only happens in single-threaded code. + unsafe { env::set_var("KF_SLACK_TOKEN", "xoxp-test") }; let temp_dir = TempDir::new()?; let clone_dir = temp_dir.path().to_path_buf(); diff --git a/tests/int_teams.rs b/tests/int_teams.rs index 49a136c..667449b 100644 --- a/tests/int_teams.rs +++ b/tests/int_teams.rs @@ -68,7 +68,8 @@ async fn test_scan_teams_messages() -> Result<()> { .mount(&server) .await; - env::set_var("KF_TEAMS_TOKEN", "test-token"); + // TODO: Audit that the environment access only happens in single-threaded code. + unsafe { env::set_var("KF_TEAMS_TOKEN", "test-token") }; let temp_dir = TempDir::new()?; let clone_dir = temp_dir.path().to_path_buf();