refactor(bare): enhance security and performance optimizations
- Remove unnecessary sorting in advertise_refs for deterministic output - Add path traversal detection and validation in bare_dir construction - Implement symlink resolution checks to prevent security vulnerabilities - Refactor cache system with CRC validation and improved metrics - Integrate repo-specific cache invalidation using indexed keys - Add comprehensive unit tests for commit operations and diff functionality - Move configuration constants to centralized config module - Optimize string operations in disk cache random value generation - Enhance license detection algorithm with cleaner matching logic - Streamline argument processing in various git operations - Update dependencies including crc32fast and flate2 for performance - Add signal handling capability to tokio runtime configuration
This commit is contained in:
+40
-4
@@ -159,8 +159,29 @@ fn run_single_script(script_path: &Path, stdin_data: &[u8], timeout: Duration) -
|
||||
timeout_secs = timeout.as_secs(),
|
||||
"hook script timed out, killing"
|
||||
);
|
||||
let _ = c.kill();
|
||||
let _ = c.wait();
|
||||
if let Err(e) = c.kill() {
|
||||
tracing::error!(
|
||||
script = %script_path.display(),
|
||||
error = %e,
|
||||
"failed to kill timed-out hook"
|
||||
);
|
||||
}
|
||||
match c.wait() {
|
||||
Ok(status) => {
|
||||
tracing::debug!(
|
||||
script = %script_path.display(),
|
||||
exit_code = ?status.code(),
|
||||
"killed hook process reaped"
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
script = %script_path.display(),
|
||||
error = %e,
|
||||
"failed to reap killed hook"
|
||||
);
|
||||
}
|
||||
}
|
||||
HookResult::rejected(format!(
|
||||
"hook script timed out after {}s: {}",
|
||||
timeout.as_secs(),
|
||||
@@ -168,8 +189,23 @@ fn run_single_script(script_path: &Path, stdin_data: &[u8], timeout: Duration) -
|
||||
))
|
||||
}
|
||||
Err(e) => {
|
||||
let _ = c.kill();
|
||||
let _ = c.wait();
|
||||
tracing::error!(
|
||||
script = %script_path.display(),
|
||||
error = %e,
|
||||
"hook script wait error"
|
||||
);
|
||||
if let Err(kill_err) = c.kill() {
|
||||
tracing::error!(
|
||||
error = %kill_err,
|
||||
"failed to kill hook after wait error"
|
||||
);
|
||||
}
|
||||
if let Err(wait_err) = c.wait() {
|
||||
tracing::error!(
|
||||
error = %wait_err,
|
||||
"failed to reap hook after wait error"
|
||||
);
|
||||
}
|
||||
HookResult::rejected(format!("hook script wait error: {e}"))
|
||||
}
|
||||
}
|
||||
|
||||
+32
-12
@@ -25,7 +25,6 @@ const FORBIDDEN_PATTERNS: &[&str] = &[
|
||||
"init 6",
|
||||
"poweroff",
|
||||
"halt",
|
||||
// Additional patterns to catch encoding/obfuscation attempts
|
||||
"eval ", // eval can execute arbitrary strings
|
||||
"exec ", // exec can replace process
|
||||
"$(", // command substitution
|
||||
@@ -55,8 +54,21 @@ const DANGEROUS_PREFIXES: &[&str] = &[
|
||||
"rm -rf *", // rm -rf with wildcard
|
||||
];
|
||||
|
||||
/// Maximum hook script size (64KB).
|
||||
const MAX_HOOK_SIZE: usize = 65536;
|
||||
/// Pairs of commands that indicate data exfiltration or code execution.
|
||||
const DANGEROUS_COMMAND_PAIRS: &[(&str, &str)] = &[
|
||||
("curl", "bash"),
|
||||
("curl", "sh"),
|
||||
("wget", "bash"),
|
||||
("wget", "sh"),
|
||||
("nc", "-e"),
|
||||
("ncat", "-e"),
|
||||
("python", "-c"),
|
||||
("perl", "-e"),
|
||||
("ruby", "-e"),
|
||||
("node", "-e"),
|
||||
];
|
||||
|
||||
use crate::config::MAX_HOOK_SCRIPT_SIZE;
|
||||
|
||||
/// Validate a custom hook script content for safety.
|
||||
pub fn validate_hook_content(content: &str) -> GitResult<()> {
|
||||
@@ -65,10 +77,10 @@ pub fn validate_hook_content(content: &str) -> GitResult<()> {
|
||||
"hook content cannot be empty".into(),
|
||||
));
|
||||
}
|
||||
if content.len() > MAX_HOOK_SIZE {
|
||||
if content.len() > MAX_HOOK_SCRIPT_SIZE {
|
||||
return Err(GitError::InvalidArgument(format!(
|
||||
"hook content too large (max {} bytes): {} bytes",
|
||||
MAX_HOOK_SIZE,
|
||||
MAX_HOOK_SCRIPT_SIZE,
|
||||
content.len()
|
||||
)));
|
||||
}
|
||||
@@ -78,7 +90,6 @@ pub fn validate_hook_content(content: &str) -> GitResult<()> {
|
||||
));
|
||||
}
|
||||
|
||||
// Check for forbidden patterns (case-insensitive where appropriate)
|
||||
let content_lower = content.to_lowercase();
|
||||
for pattern in FORBIDDEN_PATTERNS {
|
||||
if content_lower.contains(&pattern.to_lowercase()) {
|
||||
@@ -88,7 +99,6 @@ pub fn validate_hook_content(content: &str) -> GitResult<()> {
|
||||
}
|
||||
}
|
||||
|
||||
// Check for dangerous prefixes (exact case)
|
||||
for prefix in DANGEROUS_PREFIXES {
|
||||
if content.contains(prefix) {
|
||||
return Err(GitError::InvalidArgument(format!(
|
||||
@@ -97,15 +107,28 @@ pub fn validate_hook_content(content: &str) -> GitResult<()> {
|
||||
}
|
||||
}
|
||||
|
||||
// Check for obfuscation techniques
|
||||
check_obfuscation_attempts(content)?;
|
||||
|
||||
check_dangerous_pairs(content)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check for dangerous command pairs that indicate data exfiltration or code execution.
|
||||
fn check_dangerous_pairs(content: &str) -> GitResult<()> {
|
||||
let content_lower = content.to_lowercase();
|
||||
for &(cmd1, cmd2) in DANGEROUS_COMMAND_PAIRS {
|
||||
if content_lower.contains(cmd1) && content_lower.contains(cmd2) {
|
||||
return Err(GitError::InvalidArgument(format!(
|
||||
"hook contains dangerous command combination: '{cmd1}' + '{cmd2}' (possible data exfiltration)"
|
||||
)));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check for common obfuscation attempts.
|
||||
fn check_obfuscation_attempts(content: &str) -> GitResult<()> {
|
||||
// Check for excessive use of special characters that might indicate obfuscation
|
||||
let special_char_count = content
|
||||
.chars()
|
||||
.filter(|c| {
|
||||
@@ -117,14 +140,12 @@ fn check_obfuscation_attempts(content: &str) -> GitResult<()> {
|
||||
.count();
|
||||
let total_chars = content.chars().count();
|
||||
|
||||
// If more than 30% of content is special characters, it's suspicious
|
||||
if total_chars > 0 && (special_char_count * 100 / total_chars) > 30 {
|
||||
return Err(GitError::InvalidArgument(
|
||||
"hook content appears obfuscated (too many special characters)".into(),
|
||||
));
|
||||
}
|
||||
|
||||
// Check for hex encoding attempts (e.g., \x41\x42)
|
||||
if content.contains("\\x") {
|
||||
let hex_count = content.matches("\\x").count();
|
||||
if hex_count > 5 {
|
||||
@@ -134,7 +155,6 @@ fn check_obfuscation_attempts(content: &str) -> GitResult<()> {
|
||||
}
|
||||
}
|
||||
|
||||
// Check for unicode escape sequences
|
||||
if content.contains("\\u") {
|
||||
let unicode_count = content.matches("\\u").count();
|
||||
if unicode_count > 5 {
|
||||
|
||||
Reference in New Issue
Block a user