forked from mirrors/kingfisher
performance improvements and rule improvements
This commit is contained in:
parent
7d16f05df6
commit
74cad26aed
64 changed files with 219 additions and 201 deletions
|
|
@ -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
3
Cargo.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ hex.workspace = true
|
|||
|
||||
# Memory management
|
||||
memmap2 = "0.9"
|
||||
once_cell.workspace = true
|
||||
parking_lot.workspace = true
|
||||
|
||||
# Collections
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ thiserror.workspace = true
|
|||
|
||||
# Regex
|
||||
regex.workspace = true
|
||||
lazy_static = "1.5"
|
||||
|
||||
# Hashing
|
||||
xxhash-rust.workspace = true
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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}"));
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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) \
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ use super::{
|
|||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AlgoliaKeyInfo {
|
||||
#[allow(dead_code)]
|
||||
#[expect(dead_code)]
|
||||
#[serde(default)]
|
||||
value: String,
|
||||
#[serde(default)]
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ struct CircleCiPipeline {
|
|||
#[serde(default)]
|
||||
project_slug: Option<String>,
|
||||
#[serde(default)]
|
||||
#[allow(dead_code)]
|
||||
#[expect(dead_code)]
|
||||
created_at: Option<String>,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ struct JiraPermissionEntry {
|
|||
|
||||
#[derive(Deserialize)]
|
||||
struct JiraProject {
|
||||
#[allow(dead_code)]
|
||||
#[expect(dead_code)]
|
||||
id: Option<String>,
|
||||
key: String,
|
||||
name: String,
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ struct TokenResponse {
|
|||
scope: String,
|
||||
#[serde(default)]
|
||||
expires_in: i64,
|
||||
#[allow(dead_code)]
|
||||
#[expect(dead_code)]
|
||||
#[serde(default)]
|
||||
nonce: String,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -297,7 +297,7 @@ impl RoleAttributes {
|
|||
#[derive(Debug)]
|
||||
struct DatabasePrivilege {
|
||||
name: String,
|
||||
#[allow(dead_code)]
|
||||
#[expect(dead_code)]
|
||||
owner: String,
|
||||
privileges: Vec<String>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -311,7 +311,7 @@ fn classify_key_prefix(token: &str) -> KeyType {
|
|||
}
|
||||
|
||||
enum ScopeRisk {
|
||||
#[allow(dead_code)]
|
||||
#[expect(dead_code)]
|
||||
Admin,
|
||||
Risky,
|
||||
Read,
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ struct ZendeskGroupsResponse {
|
|||
|
||||
#[derive(Deserialize)]
|
||||
struct ZendeskGroup {
|
||||
#[allow(dead_code)]
|
||||
#[expect(dead_code)]
|
||||
id: Option<u64>,
|
||||
name: Option<String>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue