feat(git): add size limits for git operations

- Added MAX_CHERRY_PICK_PATCH_BYTES limit of 100MB for cherry-pick operations
- Added MAX_ACTION_CONTENT_BYTES limit of 100MB for commit action content
- Added MAX_COMMIT_MESSAGE_BYTES limit of 10MB for commit messages
- Added MAX_CHECK_REVISIONS limit of 10,000 for revision checks
- Added MAX_REBASE_COMMITS limit of 10,000 for rebase operations
- Added MAX_REBASE_PATCH_BYTES limit of 100MB for rebase patches
- Added MAX_RESOLUTION_CONTENT_BYTES limit of 100MB for merge conflict resolutions
- Added MAX_REVERT_PATCH_BYTES limit of 100MB for revert operations
- Return InvalidArgument error when size limits are exceeded with descriptive messages
This commit is contained in:
zhenyi
2026-06-12 12:59:47 +08:00
parent 934858bebf
commit 293102e5f2
6 changed files with 78 additions and 0 deletions
+8
View File
@@ -3,6 +3,8 @@ use crate::commit::create_commit::command_ok;
use crate::error::{GitError, GitResult};
use crate::pb::{CherryPickCommitRequest, CreateCommitResponse, GetCommitRequest};
const MAX_CHERRY_PICK_PATCH_BYTES: usize = 100 * 1024 * 1024;
impl GitBare {
pub fn cherry_pick_commit(
&self,
@@ -77,6 +79,12 @@ impl GitBare {
.unchecked()
.run()?;
let patch_data = command_ok(diff)?;
if patch_data.len() > MAX_CHERRY_PICK_PATCH_BYTES {
return Err(GitError::InvalidArgument(format!(
"cherry-pick patch too large ({} bytes, max {MAX_CHERRY_PICK_PATCH_BYTES})",
patch_data.len()
)));
}
let apply = duct::cmd(
"git",
+24
View File
@@ -23,6 +23,14 @@ impl GitBare {
}
}
const MAX_ACTIONS_PER_COMMIT: usize = 10_000;
if request.actions.len() > MAX_ACTIONS_PER_COMMIT {
return Err(GitError::InvalidArgument(format!(
"too many commit actions ({} > max {MAX_ACTIONS_PER_COMMIT})",
request.actions.len()
)));
}
let repo = self.gix_repo()?;
let branch = request.branch.clone();
tracing::debug!(
@@ -160,6 +168,14 @@ impl GitBare {
index_path: &str,
action: &crate::pb::CreateCommitAction,
) -> GitResult<()> {
const MAX_ACTION_CONTENT_BYTES: usize = 100 * 1024 * 1024;
if action.content.len() > MAX_ACTION_CONTENT_BYTES {
return Err(GitError::InvalidArgument(format!(
"action content too large ({} bytes, max {MAX_ACTION_CONTENT_BYTES})",
action.content.len()
)));
}
// Validate file paths to prevent command injection / traversal
if !action.file_path.is_empty() {
crate::sanitize::validate_file_path(&action.file_path)?;
@@ -325,6 +341,14 @@ impl GitBare {
author: Option<&crate::pb::Signature>,
committer: Option<&crate::pb::Signature>,
) -> GitResult<String> {
const MAX_COMMIT_MESSAGE_BYTES: usize = 10 * 1024 * 1024;
if message.len() > MAX_COMMIT_MESSAGE_BYTES {
return Err(GitError::InvalidArgument(format!(
"commit message too large ({} bytes, max {MAX_COMMIT_MESSAGE_BYTES})",
message.len()
)));
}
let mut args = vec![
"--git-dir".to_string(),
self.bare_dir.to_string_lossy().into_owned(),
+7
View File
@@ -83,6 +83,13 @@ impl GitBare {
&self,
request: CheckObjectsExistRequest,
) -> GitResult<CheckObjectsExistResponse> {
const MAX_CHECK_REVISIONS: usize = 10_000;
if request.revisions.len() > MAX_CHECK_REVISIONS {
return Err(crate::error::GitError::InvalidArgument(format!(
"too many revisions (max {MAX_CHECK_REVISIONS})"
)));
}
let repo = self.gix_repo()?;
let mut revisions = Vec::new();
+8
View File
@@ -3,6 +3,8 @@ use crate::commit::create_commit::command_ok;
use crate::error::{GitError, GitResult};
use crate::pb::{CreateCommitResponse, GetCommitRequest, RevertCommitRequest};
const MAX_REVERT_PATCH_BYTES: usize = 100 * 1024 * 1024;
impl GitBare {
pub fn revert_commit(&self, request: RevertCommitRequest) -> GitResult<CreateCommitResponse> {
let target_branch = request.branch.clone();
@@ -79,6 +81,12 @@ impl GitBare {
.unchecked()
.run()?;
let patch_data = command_ok(diff)?;
if patch_data.len() > MAX_REVERT_PATCH_BYTES {
return Err(GitError::InvalidArgument(format!(
"revert patch too large ({} bytes, max {MAX_REVERT_PATCH_BYTES})",
patch_data.len()
)));
}
let apply = duct::cmd(
"git",