performance improvements and rule improvements

This commit is contained in:
Mick Grove 2026-04-17 11:01:46 -07:00
commit 74cad26aed
64 changed files with 219 additions and 201 deletions

View file

@ -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.

3
Cargo.lock generated
View file

@ -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",

View file

@ -8,7 +8,7 @@ members = [
resolver = "2"
[workspace.package]
edition = "2021"
edition = "2024"
rust-version = "1.94"
license = "Apache-2.0"
authors = ["Mick Grove <mick.grove@mongodb.com>"]
@ -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"

View file

@ -26,7 +26,6 @@ hex.workspace = true
# Memory management
memmap2 = "0.9"
once_cell.workspace = true
parking_lot.workspace = true
# Collections

View file

@ -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<BlobId>,
id: OnceLock<BlobId>,
/// 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<u8>) -> 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<u8>) -> 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();

View file

@ -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<Vec<(&'static [u8], LanguageType)>> = Lazy::new(|| {
static SHEBANG_PREFIXES: LazyLock<Vec<(&'static [u8], LanguageType)>> = 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<String> {
// 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).

View file

@ -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();

View file

@ -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<DashMap<PathBuf, Arc<str>>> = Lazy::new(DashMap::default);
static URL_CACHE: LazyLock<DashMap<PathBuf, Arc<str>>> = LazyLock::new(DashMap::default);
fn compute_url(repo_path: &Path) -> anyhow::Result<String> {
let repo = gix::open(repo_path)?;
@ -35,12 +34,10 @@ fn compute_url(repo_path: &Path) -> anyhow::Result<String> {
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<Arc<str>> {
/// 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 = <Vec<Origin>>::json_schema(gen);
fn json_schema(r#gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
let s = <Vec<Origin>>::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()

View file

@ -26,7 +26,6 @@ thiserror.workspace = true
# Regex
regex.workspace = true
lazy_static = "1.5"
# Hashing
xxhash-rust.workspace = true

View file

@ -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.

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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<TlsMode>,
}
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<Regex> = LazyLock::new(|| {
Regex::new(r"(?m)(\(\?#[^)]*\))|(\s\#[\sa-zA-Z]*$)")
.expect("comment-stripping regex should compile")
});
static PATTERN_REQUIREMENTS_TEMPLATE_PARSER: LazyLock<liquid::Parser> = LazyLock::new(|| {
liquid_filters::register_all(ParserBuilder::with_stdlib())
.build()
.expect("pattern requirement template parser should compile")
});
impl RuleSyntax {
/// Maximum allowed regex size.

View file

@ -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

View file

@ -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<RwLock<std::collections::HashSet<&'static str>>> =
once_cell::sync::Lazy::new(|| RwLock::new(std::collections::HashSet::new()));
static STRING_POOL: LazyLock<RwLock<std::collections::HashSet<&'static str>>> =
LazyLock::new(|| RwLock::new(std::collections::HashSet::new()));
/// Intern a string to get a static reference.
///

View file

@ -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<Semaphore> = OnceCell::new();
static AWS_VALIDATION_SEMAPHORE: OnceLock<Semaphore> = 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<RwLock<HashSet<String>>> = Lazy::new(|| {
static AWS_SKIP_ACCOUNT_IDS: LazyLock<RwLock<HashSet<String>>> = 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<String> {
return Some(trimmed.to_string());
}
static ACCOUNT_ID_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(\d{12})").expect("valid regex"));
static ACCOUNT_ID_RE: LazyLock<Regex> = 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())
}

View file

@ -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}"));
}
}}
}

View file

@ -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<GcpValidator> = OnceCell::new();
static GLOBAL_VALIDATOR: OnceLock<GcpValidator> = OnceLock::new();
pub struct GcpValidator {
semaphore: Arc<Semaphore>,
@ -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.

View file

@ -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<Client> = Lazy::new(|| {
static STRICT_CLIENT: LazyLock<Client> = LazyLock::new(|| {
Client::builder()
.redirect(Policy::none())
.danger_accept_invalid_certs(false)
@ -19,7 +19,7 @@ static STRICT_CLIENT: Lazy<Client> = Lazy::new(|| {
});
/// Global redirect-free client with lax TLS validation (accepts any cert).
static LAX_CLIENT: Lazy<Client> = Lazy::new(|| {
static LAX_CLIENT: LazyLock<Client> = LazyLock::new(|| {
Client::builder()
.redirect(Policy::none())
.danger_accept_invalid_certs(true)

View file

@ -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<String> =
once_cell::sync::Lazy::new(build_user_agent);
pub static GLOBAL_USER_AGENT: LazyLock<String> = LazyLock::new(build_user_agent);
#[cfg(feature = "validation-http")]
static USER_AGENT_SUFFIX: OnceCell<String> = OnceCell::new();
static USER_AGENT_SUFFIX: OnceLock<String> = OnceLock::new();
#[cfg(feature = "validation-http")]
const BROWSER_USER_AGENT: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) \

View file

@ -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());

View file

@ -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<Arc<CryptoProvider>> = OnceLock::new();
fn ensure_crypto_provider() {

View file

@ -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)]

View file

@ -17,7 +17,7 @@ use super::{
#[derive(Debug, Deserialize)]
struct AlgoliaKeyInfo {
#[allow(dead_code)]
#[expect(dead_code)]
#[serde(default)]
value: String,
#[serde(default)]

View file

@ -18,7 +18,7 @@ struct ArtifactoryUser {
name: Option<String>,
email: Option<String>,
admin: Option<bool>,
#[allow(dead_code)]
#[expect(dead_code)]
#[serde(rename = "profileUpdatable")]
profile_updatable: Option<bool>,
#[serde(default)]

View file

@ -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
})

View file

@ -47,7 +47,7 @@ struct CircleCiPipeline {
#[serde(default)]
project_slug: Option<String>,
#[serde(default)]
#[allow(dead_code)]
#[expect(dead_code)]
created_at: Option<String>,
}

View file

@ -25,7 +25,7 @@ struct DigitalOceanAccount {
#[serde(default)]
uuid: Option<String>,
#[serde(default)]
#[allow(dead_code)]
#[expect(dead_code)]
status: Option<String>,
#[serde(default)]
team: Option<DigitalOceanTeam>,
@ -36,7 +36,7 @@ struct DigitalOceanTeam {
#[serde(default)]
name: Option<String>,
#[serde(default)]
#[allow(dead_code)]
#[expect(dead_code)]
uuid: Option<String>,
}

View file

@ -36,14 +36,14 @@ struct FastlyService {
name: Option<String>,
#[serde(rename = "type", default)]
service_type: Option<String>,
#[allow(dead_code)]
#[expect(dead_code)]
#[serde(default)]
customer_id: Option<String>,
}
#[derive(Deserialize)]
struct FastlyCustomer {
#[allow(dead_code)]
#[expect(dead_code)]
#[serde(default)]
id: Option<String>,
#[serde(default)]

View file

@ -468,12 +468,11 @@ fn extract_roles(policy: &Value, client_email: &str) -> Vec<String> {
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());
}
}
}

View file

@ -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<String>,
@ -92,7 +92,7 @@ struct GitHubInstallation {
#[serde(default)]
permissions: BTreeMap<String, String>,
#[serde(default)]
#[allow(dead_code)]
#[expect(dead_code)]
repository_selection: Option<String>,
#[serde(default)]
account: Option<GitHubInstallationAccount>,

View file

@ -338,12 +338,11 @@ fn parse_collection<T: DeserializeOwned>(value: Value) -> Vec<T> {
fn extract_first_string(value: Option<&Value>, paths: &[&str]) -> Option<String> {
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

View file

@ -22,7 +22,7 @@ struct HubSpotAccountInfo {
#[serde(default)]
time_zone: Option<String>,
#[serde(default)]
#[allow(dead_code)]
#[expect(dead_code)]
company_currency: Option<String>,
#[serde(default)]
ui_domain: Option<String>,
@ -31,7 +31,7 @@ struct HubSpotAccountInfo {
#[derive(Deserialize)]
struct HubSpotOwner {
#[serde(default)]
#[allow(dead_code)]
#[expect(dead_code)]
id: Option<String>,
#[serde(default)]
email: Option<String>,

View file

@ -18,7 +18,7 @@ struct IbmApiKeyDetails {
#[serde(default)]
id: Option<String>,
#[serde(default)]
#[allow(dead_code)]
#[expect(dead_code)]
entity_tag: Option<String>,
#[serde(default)]
iam_id: Option<String>,
@ -27,7 +27,7 @@ struct IbmApiKeyDetails {
#[serde(default)]
name: Option<String>,
#[serde(default)]
#[allow(dead_code)]
#[expect(dead_code)]
description: Option<String>,
#[serde(default)]
created_at: Option<String>,
@ -40,10 +40,10 @@ struct IbmTokenResponse {
#[serde(default)]
access_token: Option<String>,
#[serde(default)]
#[allow(dead_code)]
#[expect(dead_code)]
token_type: Option<String>,
#[serde(default)]
#[allow(dead_code)]
#[expect(dead_code)]
expires_in: Option<u64>,
#[serde(default)]
scope: Option<String>,
@ -52,7 +52,7 @@ struct IbmTokenResponse {
#[derive(Deserialize)]
struct IbmResourceInstance {
#[serde(default)]
#[allow(dead_code)]
#[expect(dead_code)]
id: Option<String>,
#[serde(default)]
name: Option<String>,

View file

@ -40,7 +40,7 @@ struct JiraPermissionEntry {
#[derive(Deserialize)]
struct JiraProject {
#[allow(dead_code)]
#[expect(dead_code)]
id: Option<String>,
key: String,
name: String,

View file

@ -27,7 +27,7 @@ struct OpenAiMe {
#[serde(default)]
email: Option<String>,
#[serde(default)]
#[allow(dead_code)]
#[expect(dead_code)]
role: Option<String>,
#[serde(default)]
orgs: Option<OpenAiOrgsData>,

View file

@ -26,7 +26,7 @@ struct TokenResponse {
scope: String,
#[serde(default)]
expires_in: i64,
#[allow(dead_code)]
#[expect(dead_code)]
#[serde(default)]
nonce: String,
}

View file

@ -297,7 +297,7 @@ impl RoleAttributes {
#[derive(Debug)]
struct DatabasePrivilege {
name: String,
#[allow(dead_code)]
#[expect(dead_code)]
owner: String,
privileges: Vec<String>,
}

View file

@ -23,7 +23,7 @@ struct SendGridAccount {
#[derive(Deserialize)]
struct SendGridProfile {
#[serde(default)]
#[allow(dead_code)]
#[expect(dead_code)]
address: Option<String>,
#[serde(default)]
city: Option<String>,
@ -36,7 +36,7 @@ struct SendGridProfile {
#[serde(default)]
last_name: Option<String>,
#[serde(default)]
#[allow(dead_code)]
#[expect(dead_code)]
phone: Option<String>,
#[serde(default)]
username: Option<String>,
@ -51,7 +51,7 @@ struct SendGridScopesResponse {
#[derive(Deserialize)]
struct SendGridApiKey {
#[serde(default)]
#[allow(dead_code)]
#[expect(dead_code)]
api_key_id: Option<String>,
#[serde(default)]
name: Option<String>,

View file

@ -38,7 +38,7 @@ struct BrevoPlan {
#[derive(Deserialize)]
struct BrevoSender {
#[serde(default)]
#[allow(dead_code)]
#[expect(dead_code)]
id: Option<u64>,
#[serde(default)]
name: Option<String>,

View file

@ -27,10 +27,10 @@ struct SquareMerchant {
business_name: Option<String>,
#[serde(default)]
country: Option<String>,
#[allow(dead_code)]
#[expect(dead_code)]
#[serde(default)]
currency: Option<String>,
#[allow(dead_code)]
#[expect(dead_code)]
#[serde(default)]
status: Option<String>,
}
@ -43,7 +43,7 @@ struct SquareLocationsResponse {
#[derive(Deserialize)]
struct SquareLocation {
#[allow(dead_code)]
#[expect(dead_code)]
#[serde(default)]
id: Option<String>,
#[serde(default)]
@ -297,7 +297,7 @@ fn classify_key_type(token: &str) -> KeyClassification {
}
enum ScopeRisk {
#[allow(dead_code)]
#[expect(dead_code)]
Admin,
Risky,
Read,

View file

@ -311,7 +311,7 @@ fn classify_key_prefix(token: &str) -> KeyType {
}
enum ScopeRisk {
#[allow(dead_code)]
#[expect(dead_code)]
Admin,
Risky,
Read,

View file

@ -44,7 +44,7 @@ struct TerraformOrgsResponse {
#[derive(Deserialize)]
struct TerraformOrgData {
#[allow(dead_code)]
#[expect(dead_code)]
#[serde(default)]
id: Option<String>,
#[serde(default)]
@ -55,7 +55,7 @@ struct TerraformOrgData {
struct TerraformOrgAttributes {
#[serde(default)]
name: Option<String>,
#[allow(dead_code)]
#[expect(dead_code)]
#[serde(default)]
email: Option<String>,
#[serde(default)]
@ -86,7 +86,7 @@ struct TerraformWorkspacesResponse {
#[derive(Deserialize)]
struct TerraformWorkspaceData {
#[allow(dead_code)]
#[expect(dead_code)]
#[serde(default)]
id: Option<String>,
#[serde(default)]

View file

@ -18,7 +18,7 @@ struct XrayRepo {
name: Option<String>,
#[serde(rename = "type")]
repo_type: Option<String>,
#[allow(dead_code)]
#[expect(dead_code)]
#[serde(rename = "pkg_type")]
package_type: Option<String>,
}
@ -28,7 +28,7 @@ struct XrayPolicy {
name: Option<String>,
#[serde(rename = "type")]
policy_type: Option<String>,
#[allow(dead_code)]
#[expect(dead_code)]
author: Option<String>,
}

View file

@ -42,7 +42,7 @@ struct ZendeskGroupsResponse {
#[derive(Deserialize)]
struct ZendeskGroup {
#[allow(dead_code)]
#[expect(dead_code)]
id: Option<u64>,
name: Option<String>,
}

View file

@ -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,

View file

@ -109,10 +109,10 @@ fn parse_excluded_repo(raw: &str) -> Option<String> {
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)

View file

@ -108,10 +108,10 @@ fn parse_excluded_project(raw: &str) -> Option<String> {
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)

View file

@ -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::<Group>();
fn json_schema(r#gen: &mut SchemaGenerator) -> Schema {
let group_schema = r#gen.subschema_for::<Group>();
Schema::Object(schemars::schema::SchemaObject {
instance_type: Some(InstanceType::Array.into()),
array: Some(Box::new(ArrayValidation {

View file

@ -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<Rule>,

View file

@ -1268,12 +1268,11 @@ impl DetailsReporter {
self.styles.style_finding_active_heading.apply_to(val)
}
#[allow(dead_code)]
#[expect(dead_code)]
fn style_rule<D>(&self, val: D) -> StyledObject<D> {
self.styles.style_rule.apply_to(val)
}
#[allow(dead_code)]
fn style_heading<D>(&self, val: D) -> StyledObject<D> {
self.styles.style_heading.apply_to(val)
}

View file

@ -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<Mutex<FindingsStore>>,
validation_deps: &Option<ValidationDeps>,
@ -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(())
}

View file

@ -397,7 +397,7 @@ impl AccessMapCollector {
}
}
#[allow(clippy::too_many_arguments)]
#[expect(clippy::too_many_arguments)]
pub async fn run_secret_validation(
datastore: Arc<Mutex<FindingsStore>>,
parser: &Parser,

View file

@ -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 {

View file

@ -28,7 +28,7 @@ struct SearchResult {
struct HitsContainer {
hits: Option<Vec<Hit>>,
more_results_available: Option<bool>,
#[allow(dead_code)]
#[expect(dead_code)]
total: Option<u32>,
}

View file

@ -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);
}
}

View file

@ -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)
}

View file

@ -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();

View file

@ -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();