From ae5c8eecbedaaa96d5a28b64e18080fd071c2180 Mon Sep 17 00:00:00 2001 From: Mick Grove Date: Wed, 24 Sep 2025 12:22:56 -0700 Subject: [PATCH] =?UTF-8?q?Replaced=20Match::finding=5Fid=E2=80=99s=20SHA1?= =?UTF-8?q?-based=20hashing=20with=20a=20fast=20xxh3=5F64=20digest=20that?= =?UTF-8?q?=20keeps=20IDs=20deterministic=20while=20eliminating=20a=20hot-?= =?UTF-8?q?path=20SHA1=20dependency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + src/matcher.rs | 34 +++++----------------------------- src/rules/rule.rs | 8 ++++---- src/update.rs | 2 +- 4 files changed, 11 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fb02ca..77cd937 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. ## [v1.54.0] - Added first-class Gitea support, including CLI commands, environment-based authentication, documentation, and integration with scans and repository enumeration. - Populate the finding path from git blob metadata so history-derived secrets display their file location instead of an empty path +- Replaced Match::finding_id’s SHA1-based hashing with a fast xxh3_64 digest that keeps IDs deterministic while eliminating a hot-path SHA1 dependency ## [v1.53.0] - Added first-class Bitbucket support, including CLI commands, authentication helpers, documentation, and integration testing. diff --git a/src/matcher.rs b/src/matcher.rs index 8bd71d6..3112a9e 100644 --- a/src/matcher.rs +++ b/src/matcher.rs @@ -1,7 +1,6 @@ use std::{ borrow::Cow, hash::{Hash, Hasher}, - io::Write, str, sync::{Arc, Mutex}, }; @@ -18,7 +17,6 @@ use schemars::{ JsonSchema, }; use serde::{Deserialize, Serialize}; -use sha1::{Digest, Sha1}; use smallvec::SmallVec; use tracing::debug; use xxhash_rust::xxh3::xxh3_64; @@ -863,34 +861,12 @@ impl Match { } pub fn finding_id(&self) -> String { - let mut h = Sha1::new(); - write!(&mut h, "{}\0", self.rule.finding_sha1_fingerprint()) - .expect("should be able to write to memory"); - serde_json::to_writer(&mut h, &self.groups) + let mut buffer = Vec::with_capacity(128); + buffer.extend_from_slice(self.rule.finding_sha1_fingerprint().as_bytes()); + buffer.push(0); + serde_json::to_writer(&mut buffer, &self.groups) .expect("should be able to serialize groups as JSON"); - let hash: sha2::digest::generic_array::GenericArray< - u8, - sha2::digest::typenum::UInt< - sha2::digest::typenum::UInt< - sha2::digest::typenum::UInt< - sha2::digest::typenum::UInt< - sha2::digest::typenum::UInt< - sha2::digest::typenum::UTerm, - sha2::digest::consts::B1, - >, - sha2::digest::consts::B0, - >, - sha2::digest::consts::B1, - >, - sha2::digest::consts::B0, - >, - sha2::digest::consts::B0, - >, - > = h.finalize(); - // Take the first 8 bytes of the hash - let mut num = u64::from_be_bytes([ - hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7], - ]); + let mut num = xxh3_64(&buffer); // Ensure the number is positive and within i64 range num &= 0x7FFF_FFFF_FFFF_FFFF; // Clear the sign bit to make it positive // Convert to string diff --git a/src/rules/rule.rs b/src/rules/rule.rs index 1947a0a..dbf52ef 100644 --- a/src/rules/rule.rs +++ b/src/rules/rule.rs @@ -17,7 +17,8 @@ use schemars::{ JsonSchema, }; use serde::{Deserialize, Serialize}; -use sha1::{Digest, Sha1}; +// use sha1::{Digest, Sha1}; +use xxhash_rust::xxh3::xxh3_64; /// Returns false as the default value. fn default_false() -> bool { @@ -341,9 +342,8 @@ impl RuleSyntax { /// Computes a content-based fingerprint of the rule's pattern. pub fn finding_sha1_fingerprint(&self) -> String { - let mut hasher = Sha1::new(); - hasher.update(self.pattern.as_bytes()); - format!("{:x}", hasher.finalize()) + let hash = xxh3_64(self.pattern.as_bytes()); + format!("{:x}", hash) } /// Serializes the rule syntax to JSON. diff --git a/src/update.rs b/src/update.rs index b765768..db8d9cb 100644 --- a/src/update.rs +++ b/src/update.rs @@ -102,7 +102,7 @@ pub fn check_for_update(global_args: &GlobalArgs, base_url: Option<&str>) -> Opt // ───────────── Case 1: running == latest ───────────── if release.version == running_v { let plain = format!("Kingfisher {running_v} is up to date"); - info!("{}", plain.as_str()); + info!("{}", plain); return Some(plain); }