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:
zhenyi
2026-06-12 15:04:12 +08:00
parent e386f44ee2
commit 10a4398e81
41 changed files with 1373 additions and 365 deletions
+60 -29
View File
@@ -32,9 +32,13 @@ pub fn validate_oid_hex(hex: &str) -> GitResult<()> {
if hex.is_empty() {
return Err(GitError::InvalidArgument("oid hex cannot be empty".into()));
}
if !(4..=64).contains(&hex.len()) {
if !(crate::config::MIN_OID_HEX_LENGTH..=crate::config::MAX_OID_HEX_LENGTH)
.contains(&hex.len())
{
return Err(GitError::InvalidArgument(format!(
"oid hex length must be 4..=64 chars: {}",
"oid hex length must be {}..={} chars: {}",
crate::config::MIN_OID_HEX_LENGTH,
crate::config::MAX_OID_HEX_LENGTH,
hex.len()
)));
}
@@ -75,9 +79,10 @@ pub fn validate_ref_name(name: &str) -> GitResult<()> {
"ref name contains forbidden character: {name}"
)));
}
if name.len() > 255 {
if name.len() > crate::config::MAX_REF_NAME_LENGTH {
return Err(GitError::InvalidArgument(format!(
"ref name too long (max 255 chars): {name}"
"ref name too long (max {} chars): {name}",
crate::config::MAX_REF_NAME_LENGTH
)));
}
Ok(())
@@ -91,35 +96,36 @@ pub fn validate_revision(rev: &str) -> GitResult<()> {
if rev.is_empty() {
return Err(GitError::InvalidArgument("revision cannot be empty".into()));
}
if rev.len() > 256 {
if rev.len() > crate::config::MAX_REVISION_LENGTH {
return Err(GitError::InvalidArgument(format!(
"revision too long (max 256 chars): {}",
"revision too long (max {} chars): {}",
crate::config::MAX_REVISION_LENGTH,
rev.len()
)));
}
if rev.chars().all(|c| c.is_ascii_hexdigit()) && rev.len() >= 4 && rev.len() <= 64 {
if rev.chars().all(|c| c.is_ascii_hexdigit())
&& rev.len() >= crate::config::MIN_OID_HEX_LENGTH
&& rev.len() <= crate::config::MAX_OID_HEX_LENGTH
{
return Ok(());
}
if rev == "HEAD" {
return Ok(());
}
// Allow ref:refs/heads/... (git internal format)
if let Some(rest) = rev.strip_prefix("ref:") {
return validate_ref_name(rest.trim());
}
const MAX_ANCESTRY_DEPTH: u32 = 10000;
if let Some(tilde_pos) = rev.rfind('~') {
let num_part = &rev[tilde_pos + 1..];
if !num_part.is_empty() && num_part.chars().all(|c| c.is_ascii_digit()) {
let depth: u32 = num_part
.parse()
.map_err(|_| GitError::InvalidArgument("invalid ~N syntax".into()))?;
if depth > MAX_ANCESTRY_DEPTH {
if depth > crate::config::MAX_ANCESTRY_DEPTH {
return Err(GitError::InvalidArgument(format!(
"~N depth too large: {} (max {})",
depth, MAX_ANCESTRY_DEPTH
depth, crate::config::MAX_ANCESTRY_DEPTH
)));
}
}
@@ -140,10 +146,10 @@ pub fn validate_revision(rev: &str) -> GitResult<()> {
let depth: u32 = num_part
.parse()
.map_err(|_| GitError::InvalidArgument("invalid ^N syntax".into()))?;
if depth > MAX_ANCESTRY_DEPTH {
if depth > crate::config::MAX_ANCESTRY_DEPTH {
return Err(GitError::InvalidArgument(format!(
"^N depth too large: {} (max {})",
depth, MAX_ANCESTRY_DEPTH
depth, crate::config::MAX_ANCESTRY_DEPTH
)));
}
}
@@ -204,9 +210,10 @@ pub fn validate_file_path(path: &str) -> GitResult<()> {
"file path cannot contain null byte: {path}"
)));
}
if path.len() > 4096 {
if path.len() > crate::config::MAX_FILE_PATH_LENGTH {
return Err(GitError::InvalidArgument(format!(
"file path too long (max 4096 chars): {path}"
"file path too long (max {} chars): {path}",
crate::config::MAX_FILE_PATH_LENGTH
)));
}
@@ -220,7 +227,6 @@ pub fn validate_file_path(path: &str) -> GitResult<()> {
)));
}
// Windows reserved names check
#[cfg(target_os = "windows")]
{
const RESERVED_NAMES: &[&str] = &[
@@ -307,10 +313,11 @@ pub fn validate_remote_url(url: &str) -> GitResult<()> {
"remote URL cannot be empty".into(),
));
}
if url.len() > 4096 {
return Err(GitError::InvalidArgument(
"remote URL too long (max 4096 chars)".into(),
));
if url.len() > crate::config::MAX_REMOTE_URL_LENGTH {
return Err(GitError::InvalidArgument(format!(
"remote URL too long (max {} chars)",
crate::config::MAX_REMOTE_URL_LENGTH
)));
}
if url.contains('\0') || url.contains('\n') || url.contains('\r') {
return Err(GitError::InvalidArgument(
@@ -343,14 +350,37 @@ pub fn validate_refspec(refspec: &str) -> GitResult<()> {
"refspec contains shell metacharacter: {refspec}"
)));
}
if refspec.len() > 1024 {
return Err(GitError::InvalidArgument(
"refspec too long (max 1024 chars)".into(),
));
if refspec.len() > crate::config::MAX_REFSPEC_LENGTH {
return Err(GitError::InvalidArgument(format!(
"refspec too long (max {} chars)",
crate::config::MAX_REFSPEC_LENGTH
)));
}
Ok(())
}
/// Sanitize git stderr output for logging to prevent leaking sensitive data
/// such as credentials in URLs, absolute filesystem paths, or email addresses.
pub fn sanitize_git_stderr(stderr: &str) -> String {
let mut s = stderr.to_string();
for scheme in &["https://", "http://", "git+ssh://", "ssh://"] {
while let Some(start) = s.find(scheme) {
let after_scheme = start + scheme.len();
if let Some(at_pos) = s[after_scheme..].find('@') {
let at_abs = after_scheme + at_pos;
let replacement = format!("{scheme}***:***@");
s.replace_range(start..=at_abs, &replacement);
} else {
break;
}
}
}
if let Some(homedir) = std::env::var_os("HOME").and_then(|v| v.into_string().ok()) {
s = s.replace(&homedir, "~");
}
s
}
/// Validate a storage-relative path (used in resolve_for_init and from_repository_header).
///
/// Must not contain path traversal, must be a simple relative path.
@@ -370,10 +400,11 @@ pub fn validate_relative_path(path: &str) -> GitResult<()> {
"relative_path cannot contain null byte".into(),
));
}
if path.len() > 4096 {
return Err(GitError::InvalidArgument(
"relative_path too long (max 4096 chars)".into(),
));
if path.len() > crate::config::MAX_RELATIVE_PATH_LENGTH {
return Err(GitError::InvalidArgument(format!(
"relative_path too long (max {} chars)",
crate::config::MAX_RELATIVE_PATH_LENGTH
)));
}
if path.contains("..") {
return Err(GitError::InvalidArgument(format!(