formatted

This commit is contained in:
bahdotsh
2025-08-14 23:30:26 +05:30
parent 250a88ba94
commit db1d4bcf48
10 changed files with 106 additions and 60 deletions

View File

@@ -14,9 +14,7 @@ fn bench_basic_masking(c: &mut Criterion) {
let text = "The password is password123 and the API key is api_key_abcdef123456. Also super_secret_value_that_should_be_masked is here.";
c.bench_function("basic_masking", |b| {
b.iter(|| masker.mask(black_box(text)))
});
c.bench_function("basic_masking", |b| b.iter(|| masker.mask(black_box(text))));
}
fn bench_pattern_masking(c: &mut Criterion) {
@@ -58,9 +56,7 @@ fn bench_many_secrets(c: &mut Criterion) {
let text = "This text contains secret_50 and secret_75 but not others.";
c.bench_function("many_secrets", |b| {
b.iter(|| masker.mask(black_box(text)))
});
c.bench_function("many_secrets", |b| b.iter(|| masker.mask(black_box(text))));
}
fn bench_contains_secrets(c: &mut Criterion) {

View File

@@ -190,7 +190,10 @@ mod tests {
.expect("Failed to create manager");
// Use a unique test secret name to avoid conflicts
let test_secret_name = format!("TEST_SECRET_{}", uuid::Uuid::new_v4().to_string().replace('-', "_"));
let test_secret_name = format!(
"TEST_SECRET_{}",
uuid::Uuid::new_v4().to_string().replace('-', "_")
);
std::env::set_var(&test_secret_name, "secret_value");
let result = manager.get_secret(&test_secret_name).await;
@@ -210,7 +213,10 @@ mod tests {
.expect("Failed to create manager");
// Use a unique test secret name to avoid conflicts
let test_secret_name = format!("GITHUB_TOKEN_{}", uuid::Uuid::new_v4().to_string().replace('-', "_"));
let test_secret_name = format!(
"GITHUB_TOKEN_{}",
uuid::Uuid::new_v4().to_string().replace('-', "_")
);
std::env::set_var(&test_secret_name, "ghp_test_token");
let mut substitution = SecretSubstitution::new(&manager);

View File

@@ -137,16 +137,28 @@ impl SecretMasker {
let mut result = text.to_string();
// GitHub Personal Access Tokens
result = patterns.github_pat.replace_all(&result, "ghp_***").to_string();
result = patterns
.github_pat
.replace_all(&result, "ghp_***")
.to_string();
// GitHub App tokens
result = patterns.github_app.replace_all(&result, "ghs_***").to_string();
result = patterns
.github_app
.replace_all(&result, "ghs_***")
.to_string();
// GitHub OAuth tokens
result = patterns.github_oauth.replace_all(&result, "gho_***").to_string();
result = patterns
.github_oauth
.replace_all(&result, "gho_***")
.to_string();
// AWS Access Key IDs
result = patterns.aws_access_key.replace_all(&result, "AKIA***").to_string();
result = patterns
.aws_access_key
.replace_all(&result, "AKIA***")
.to_string();
// AWS Secret Access Keys (basic pattern)
// Only mask if it's clearly in a secret context (basic heuristic)
@@ -155,10 +167,16 @@ impl SecretMasker {
}
// JWT tokens (basic pattern)
result = patterns.jwt.replace_all(&result, "eyJ***.eyJ***.***").to_string();
result = patterns
.jwt
.replace_all(&result, "eyJ***.eyJ***.***")
.to_string();
// API keys with common prefixes
result = patterns.api_key.replace_all(&result, "${1}=***").to_string();
result = patterns
.api_key
.replace_all(&result, "${1}=***")
.to_string();
result
}
@@ -179,11 +197,11 @@ impl SecretMasker {
fn has_secret_patterns(&self, text: &str) -> bool {
let patterns = PATTERNS.get_or_init(CompiledPatterns::new);
patterns.github_pat.is_match(text) ||
patterns.github_app.is_match(text) ||
patterns.github_oauth.is_match(text) ||
patterns.aws_access_key.is_match(text) ||
patterns.jwt.is_match(text)
patterns.github_pat.is_match(text)
|| patterns.github_app.is_match(text)
|| patterns.github_oauth.is_match(text)
|| patterns.aws_access_key.is_match(text)
|| patterns.jwt.is_match(text)
}
/// Get the number of secrets being tracked

View File

@@ -1,4 +1,6 @@
use crate::{validation::validate_secret_value, SecretError, SecretProvider, SecretResult, SecretValue};
use crate::{
validation::validate_secret_value, SecretError, SecretProvider, SecretResult, SecretValue,
};
use async_trait::async_trait;
use std::collections::HashMap;
@@ -21,7 +23,6 @@ impl Default for EnvironmentProvider {
}
impl EnvironmentProvider {
/// Get the full environment variable name
fn get_env_name(&self, name: &str) -> String {
match &self.prefix {

View File

@@ -1,4 +1,6 @@
use crate::{validation::validate_secret_value, SecretError, SecretProvider, SecretResult, SecretValue};
use crate::{
validation::validate_secret_value, SecretError, SecretProvider, SecretResult, SecretValue,
};
use async_trait::async_trait;
use serde_json::Value;
use std::collections::HashMap;

View File

@@ -82,8 +82,6 @@ impl RateLimiter {
}
}
/// Check if a request should be allowed for the given key
pub async fn check_rate_limit(&self, key: &str) -> SecretResult<()> {
if !self.config.enabled {

View File

@@ -176,7 +176,10 @@ mod tests {
let manager = SecretManager::default().await.unwrap();
let mut substitution = SecretSubstitution::new(&manager);
let input = format!("Token: ${{{{ secrets.{} }}}}, API: ${{{{ secrets.{} }}}}", github_token_name, api_key_name);
let input = format!(
"Token: ${{{{ secrets.{} }}}}, API: ${{{{ secrets.{} }}}}",
github_token_name, api_key_name
);
let result = substitution.substitute(&input).await.unwrap();
assert_eq!(result, "Token: ghp_test_token, API: secret_api_key");

View File

@@ -37,7 +37,8 @@ pub fn validate_secret_name(name: &str) -> SecretResult<()> {
if !SECRET_NAME_PATTERN.is_match(name) {
return Err(SecretError::InvalidSecretName {
reason: "Secret name can only contain letters, numbers, underscores, hyphens, and dots".to_string(),
reason: "Secret name can only contain letters, numbers, underscores, hyphens, and dots"
.to_string(),
});
}
@@ -56,8 +57,8 @@ pub fn validate_secret_name(name: &str) -> SecretResult<()> {
// Reserved names
let reserved_names = [
"CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
"CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8",
"COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
];
if reserved_names.contains(&name.to_uppercase().as_str()) {
@@ -99,12 +100,16 @@ pub fn validate_provider_name(name: &str) -> SecretResult<()> {
}
if name.len() > 64 {
return Err(SecretError::InvalidConfig(
format!("Provider name too long: {} characters (max: 64)", name.len()),
));
return Err(SecretError::InvalidConfig(format!(
"Provider name too long: {} characters (max: 64)",
name.len()
)));
}
if !name.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '-') {
if !name
.chars()
.all(|c| c.is_alphanumeric() || c == '_' || c == '-')
{
return Err(SecretError::InvalidConfig(
"Provider name can only contain letters, numbers, underscores, and hyphens".to_string(),
));
@@ -141,12 +146,12 @@ pub fn looks_like_secret(value: &str) -> bool {
// Check for common secret patterns
let secret_patterns = [
r"^[A-Za-z0-9+/=]{40,}$", // Base64-like
r"^[a-fA-F0-9]{32,}$", // Hex strings
r"^[A-Z0-9]{20,}$", // All caps alphanumeric
r"^sk_[a-zA-Z0-9_-]+$", // Stripe-like keys
r"^pk_[a-zA-Z0-9_-]+$", // Public keys
r"^rk_[a-zA-Z0-9_-]+$", // Restricted keys
r"^[A-Za-z0-9+/=]{40,}$", // Base64-like
r"^[a-fA-F0-9]{32,}$", // Hex strings
r"^[A-Z0-9]{20,}$", // All caps alphanumeric
r"^sk_[a-zA-Z0-9_-]+$", // Stripe-like keys
r"^pk_[a-zA-Z0-9_-]+$", // Public keys
r"^rk_[a-zA-Z0-9_-]+$", // Restricted keys
];
for pattern in &secret_patterns {
@@ -224,7 +229,9 @@ mod tests {
assert!(looks_like_secret("sk_test_abcdefghijklmnop1234567890"));
assert!(looks_like_secret("abcdefghijklmnopqrstuvwxyz123456"));
assert!(looks_like_secret("ABCDEF1234567890ABCDEF1234567890"));
assert!(looks_like_secret("YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkw"));
assert!(looks_like_secret(
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkw"
));
// Should not detect as secrets
assert!(!looks_like_secret("short"));

View File

@@ -60,8 +60,14 @@ async fn test_end_to_end_secret_workflow() {
// Test 1: Get secret from environment provider
let env_secret = manager.get_secret(&env_secret_name).await.unwrap();
assert_eq!(env_secret.value(), "ghp_1234567890abcdefghijklmnopqrstuvwxyz");
assert_eq!(env_secret.metadata.get("source"), Some(&"environment".to_string()));
assert_eq!(
env_secret.value(),
"ghp_1234567890abcdefghijklmnopqrstuvwxyz"
);
assert_eq!(
env_secret.metadata.get("source"),
Some(&"environment".to_string())
);
// Test 2: Get secret from file provider
let file_secret = manager
@@ -69,7 +75,10 @@ async fn test_end_to_end_secret_workflow() {
.await
.unwrap();
assert_eq!(file_secret.value(), "super_secret_db_pass_123");
assert_eq!(file_secret.metadata.get("source"), Some(&"file".to_string()));
assert_eq!(
file_secret.metadata.get("source"),
Some(&"file".to_string())
);
// Test 3: List secrets from file provider
let all_secrets = manager.list_all_secrets().await.unwrap();
@@ -152,8 +161,8 @@ async fn test_error_handling() {
/// Test rate limiting functionality
#[tokio::test]
async fn test_rate_limiting() {
use wrkflw_secrets::rate_limit::RateLimitConfig;
use std::time::Duration;
use wrkflw_secrets::rate_limit::RateLimitConfig;
// Create config with very low rate limit
let mut config = SecretConfig::default();
@@ -179,7 +188,10 @@ async fn test_rate_limiting() {
// Third request should fail due to rate limiting
let result3 = manager.get_secret(&test_secret_name).await;
assert!(result3.is_err());
assert!(result3.unwrap_err().to_string().contains("Rate limit exceeded"));
assert!(result3
.unwrap_err()
.to_string()
.contains("Rate limit exceeded"));
// Cleanup
std::env::remove_var(&test_secret_name);
@@ -308,7 +320,10 @@ async fn test_comprehensive_masking() {
for pattern in should_not_contain {
if pattern != "***" {
assert!(
!masked.contains(pattern) || pattern == "ghp_" || pattern == "AKIA" || pattern == "eyJ",
!masked.contains(pattern)
|| pattern == "ghp_"
|| pattern == "AKIA"
|| pattern == "eyJ",
"Masked text '{}' should not contain '{}' (or only partial patterns)",
masked,
pattern