Skip to content

Commit 76d1c57

Browse files
refactor(rules): deduplicate hardcoded-secret regex (closes #274) (#277)
* feat(pq): expand seed lists and fix fabric attribution (refs #262) - Add Cargo seeds: k256, secp256k1, libsecp256k1, ed448-goldilocks (tier 1), openssl (tier 2) - Add pip seeds: pyjwt, authlib, python-jose, jwcrypto (0.8), m2crypto (0.6) - Fix fabric: drop misleading RSA algorithm (wraps paramiko, no crypto itself) * feat(sarif): include depName in SARIF properties (refs #262) * docs(pq): note dev-dependencies limitation in Cargo rule description (refs #262) * refactor(pq): dedup BFS reached_seeds with HashMap (refs #262) * style(pq): reorder PIP_PACKAGES by descending confidence (refs #262) * refactor(rules): deduplicate hardcoded-secret regex across languages (closes #274) Extract shared HARDCODED_SECRET_PATTERN and CSHARP_HARDCODED_SECRET_PATTERN constants into common.rs; replace inline copies in all 10 language rule files. Adds test asserting the C# pattern is a superset of the base pattern. Also normalises PHP to the full keyword set (was missing auth, credential, private_key). * fix: cargo fmt imports and deterministic seed selection - Fix import formatting in javascript.rs and python.rs - Make reached_seeds best-pick deterministic by breaking confidence ties on seed name --------- Co-authored-by: Peak Twilight <doruk@doruk.ch>
1 parent 9d38359 commit 76d1c57

12 files changed

Lines changed: 86 additions & 24 deletions

File tree

src/rules/common.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@ pub fn shannon_entropy(s: &str) -> f32 {
5050
entropy
5151
}
5252

53+
/// Base regex pattern shared by all `*/no-hardcoded-secret` rules.
54+
///
55+
/// Each language rule file should use this constant (or
56+
/// [`CSHARP_HARDCODED_SECRET_PATTERN`] for C#) instead of inlining its
57+
/// own copy of the pattern.
58+
pub const HARDCODED_SECRET_PATTERN: &str =
59+
r"(?i)(password|secret|api_?key|token|auth|credential|private_?key)";
60+
61+
/// Extended variant for C# that adds `connection_?string` /
62+
/// `connectionstring` to the base keyword set.
63+
pub const CSHARP_HARDCODED_SECRET_PATTERN: &str = r"(?i)(password|secret|api_?key|token|auth|credential|private_?key|connection_?string|connectionstring)";
64+
5365
/// Default minimum length for strings flagged as a hardcoded secret.
5466
///
5567
/// Historically this was hardcoded as `>= 4` across every language-specific
@@ -338,4 +350,31 @@ mod tests {
338350
assert_eq!(confidence_for_hops(3), 0.6);
339351
assert_eq!(confidence_for_hops(10), 0.6);
340352
}
353+
354+
#[test]
355+
fn csharp_pattern_is_superset_of_base() {
356+
let base = regex::Regex::new(HARDCODED_SECRET_PATTERN).unwrap();
357+
let extended = regex::Regex::new(CSHARP_HARDCODED_SECRET_PATTERN).unwrap();
358+
359+
// Every keyword the base pattern matches must also match the C# pattern.
360+
for kw in &[
361+
"password",
362+
"secret",
363+
"api_key",
364+
"apikey",
365+
"token",
366+
"auth",
367+
"credential",
368+
"private_key",
369+
"privatekey",
370+
] {
371+
assert!(base.is_match(kw), "base should match {kw}");
372+
assert!(extended.is_match(kw), "csharp should match {kw}");
373+
}
374+
// C#-specific extras
375+
for kw in &["connection_string", "connectionstring"] {
376+
assert!(!base.is_match(kw), "base should NOT match {kw}");
377+
assert!(extended.is_match(kw), "csharp should match {kw}");
378+
}
379+
}
341380
}

src/rules/csharp.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use crate::impl_rule;
2-
use crate::rules::common::{is_secret_value_long_enough, make_finding, walk_tree};
2+
use crate::rules::common::{
3+
is_secret_value_long_enough, make_finding, walk_tree, CSHARP_HARDCODED_SECRET_PATTERN,
4+
};
35
use crate::{Language, Severity};
46
use regex::Regex;
57

@@ -443,10 +445,7 @@ impl_rule! {
443445
fn check(_self, source, tree) {
444446

445447
let mut findings = Vec::new();
446-
let secret_pattern = Regex::new(
447-
r"(?i)(password|secret|api_?key|apikey|token|credential|private_?key|connection_?string|connectionstring)",
448-
)
449-
.unwrap();
448+
let secret_pattern = Regex::new(CSHARP_HARDCODED_SECRET_PATTERN).unwrap();
450449

451450
walk_tree(tree.root_node(), source, &mut |node, src| {
452451
// variable_declarator: string password = "hardcoded"

src/rules/go.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::impl_rule;
22
use crate::rules::common::AliasTable;
33
use crate::rules::common::{
44
get_source_line, is_secret_value_long_enough, make_finding, make_finding_from_offsets,
5-
walk_tree,
5+
walk_tree, HARDCODED_SECRET_PATTERN,
66
};
77
use crate::rules::go_taint::{
88
self, go_aliases_from_tree, go_taint_sources, NodeMatcher as GoNodeMatcher,
@@ -149,7 +149,7 @@ impl_rule! {
149149

150150
let mut findings = Vec::new();
151151
let secret_pattern =
152-
Regex::new(r"(?i)(password|secret|api_?key|token|auth|credential|private_?key)")
152+
Regex::new(HARDCODED_SECRET_PATTERN)
153153
.unwrap();
154154

155155
walk_tree(tree.root_node(), source, &mut |node, src| {

src/rules/java.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::impl_rule;
22
use crate::rules::common::{
33
is_secret_value_long_enough, make_finding, make_finding_from_offsets, walk_tree,
4+
HARDCODED_SECRET_PATTERN,
45
};
56
use crate::{Language, Severity};
67
use regex::Regex;
@@ -604,7 +605,7 @@ impl_rule! {
604605

605606
let mut findings = Vec::new();
606607
let secret_pattern =
607-
Regex::new(r"(?i)(password|secret|api_?key|apiKey|token|auth|credential|private_?key)")
608+
Regex::new(HARDCODED_SECRET_PATTERN)
608609
.unwrap();
609610

610611
walk_tree(tree.root_node(), source, &mut |node, src| {

src/rules/javascript.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use crate::impl_rule;
2-
use crate::rules::common::{get_source_line, is_secret_value_long_enough, make_finding, walk_tree};
2+
use crate::rules::common::{
3+
get_source_line, is_secret_value_long_enough, make_finding, walk_tree, HARDCODED_SECRET_PATTERN,
4+
};
35
use crate::rules::FileContext;
46
use crate::{Finding, Language, Severity};
57
use regex::Regex;
@@ -56,7 +58,7 @@ impl_rule! {
5658

5759
let mut findings = Vec::new();
5860
let secret_pattern =
59-
Regex::new(r"(?i)(password|secret|api_?key|token|auth|credential|private_?key)")
61+
Regex::new(HARDCODED_SECRET_PATTERN)
6062
.unwrap();
6163

6264
walk_tree(tree.root_node(), source, &mut |node, src| {

src/rules/kotlin.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::impl_rule;
22
use crate::rules::common::{
33
is_secret_value_long_enough, make_finding, make_finding_from_offsets, walk_tree,
4+
HARDCODED_SECRET_PATTERN,
45
};
56
use crate::{Finding, Language, Severity};
67
use regex::Regex;
@@ -521,7 +522,7 @@ impl_rule! {
521522

522523
let mut findings = Vec::new();
523524
let secret_pattern =
524-
Regex::new(r"(?i)(password|secret|api_?key|apiKey|token|auth|credential|private_?key)")
525+
Regex::new(HARDCODED_SECRET_PATTERN)
525526
.unwrap();
526527

527528
walk_tree(tree.root_node(), source, &mut |node, src| {

src/rules/manifest.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,14 @@ impl Rule for CargoLockPqCrypto {
307307
continue;
308308
};
309309
if let Some(entry) = seed_map.get(neighbor_name) {
310-
reached_seeds.entry(entry.name).or_insert(entry);
310+
reached_seeds
311+
.entry(entry.name)
312+
.and_modify(|existing| {
313+
if entry.confidence > existing.confidence {
314+
*existing = entry;
315+
}
316+
})
317+
.or_insert(entry);
311318
} else {
312319
queue.push_back(neighbor);
313320
}
@@ -319,9 +326,13 @@ impl Rule for CargoLockPqCrypto {
319326
}
320327

321328
// Pick the highest-confidence seed
322-
let best = reached_seeds
323-
.values()
324-
.max_by(|a, b| a.confidence.total_cmp(&b.confidence))
329+
let (_, best) = reached_seeds
330+
.iter()
331+
.max_by(|(k1, v1), (k2, v2)| {
332+
v1.confidence
333+
.total_cmp(&v2.confidence)
334+
.then_with(|| k1.cmp(k2))
335+
})
325336
.unwrap();
326337

327338
// Find byte offset of this package entry.

src/rules/php.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use crate::impl_rule;
2-
use crate::rules::common::{is_secret_value_long_enough, make_finding, walk_tree};
2+
use crate::rules::common::{
3+
is_secret_value_long_enough, make_finding, walk_tree, HARDCODED_SECRET_PATTERN,
4+
};
35
use crate::{Language, Severity};
46
use regex::Regex;
57

@@ -359,7 +361,7 @@ impl_rule! {
359361
fn check(_self, source, tree) {
360362

361363
let mut findings = Vec::new();
362-
let secret_pattern = Regex::new(r"(?i)(password|secret|api_?key|token)").unwrap();
364+
let secret_pattern = Regex::new(HARDCODED_SECRET_PATTERN).unwrap();
363365

364366
walk_tree(tree.root_node(), source, &mut |node, src| {
365367
// Detect: $password = "hardcoded";

src/rules/python.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use crate::impl_rule;
2-
use crate::rules::common::{get_source_line, is_secret_value_long_enough, make_finding, walk_tree};
2+
use crate::rules::common::{
3+
get_source_line, is_secret_value_long_enough, make_finding, walk_tree, HARDCODED_SECRET_PATTERN,
4+
};
35
use crate::rules::FileContext;
46
use crate::{Finding, Language, Severity};
57
use regex::Regex;
@@ -75,7 +77,7 @@ impl_rule! {
7577

7678
let mut findings = Vec::new();
7779
let secret_pattern =
78-
Regex::new(r"(?i)(password|secret|api_?key|token|auth|credential|private_?key)")
80+
Regex::new(HARDCODED_SECRET_PATTERN)
7981
.unwrap();
8082

8183
walk_tree(tree.root_node(), source, &mut |node, src| {

src/rules/ruby.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use crate::impl_rule;
2-
use crate::rules::common::{is_secret_value_long_enough, make_finding, walk_tree};
2+
use crate::rules::common::{
3+
is_secret_value_long_enough, make_finding, walk_tree, HARDCODED_SECRET_PATTERN,
4+
};
35
use crate::{Language, Severity};
46
use regex::Regex;
57

@@ -484,7 +486,7 @@ impl_rule! {
484486

485487
let mut findings = Vec::new();
486488
let secret_pattern =
487-
Regex::new(r"(?i)(password|secret|api_?key|token|auth|credential|private_?key)")
489+
Regex::new(HARDCODED_SECRET_PATTERN)
488490
.unwrap();
489491

490492
walk_tree(tree.root_node(), source, &mut |node, src| {

0 commit comments

Comments
 (0)