Skip to content

Commit 453ab5b

Browse files
committed
restrict git commands to read-only operations
1 parent 899ea3e commit 453ab5b

3 files changed

Lines changed: 193 additions & 0 deletions

File tree

.sofosrc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ src/
9898
- Output size limit: 50MB
9999
- No sudo, no file modifications, no directory changes
100100
- Read-only operations only
101+
- Git operations restricted: blocks push/pull/fetch/clone and all modifications
102+
- Allows safe git commands: status, log, diff, show, branch, remote -v, grep, blame
101103

102104
## Code Conventions
103105

@@ -143,6 +145,8 @@ src/
143145
- No directory changes (`cd`, `pushd`, `popd`)
144146
- No background execution (`&`)
145147
- Read-only operations only
148+
- Git command restrictions: blocks push, pull, fetch, clone, commit, add, and all modifications
149+
- Allows safe git operations: status, log, diff, show, branch (list), remote -v, grep, blame, stash list/show
146150

147151
3. **File Size Limits**
148152
- Read operations: 10MB limit

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,16 @@ Bash execution is restricted to read-only operations:
138138
- ❌ Cannot modify files (`rm`, `mv`, `cp`, `chmod`, `mkdir`, `touch`)
139139
- ❌ Cannot change directories or use output redirection
140140

141+
Git commands are restricted to read-only operations:
142+
143+
- ✅ Can view history and status (`git status`, `git log`, `git diff`, `git show`)
144+
- ✅ Can list branches and remotes (`git branch`, `git remote -v`)
145+
- ✅ Can search and blame (`git grep`, `git blame`)
146+
- ❌ Cannot push, pull, fetch, or clone (network operations)
147+
- ❌ Cannot commit, add, or modify files (`git commit`, `git add`, `git reset --hard`)
148+
- ❌ Cannot change branches or stash (`git checkout -b`, `git stash`, `git switch`)
149+
- ❌ Cannot configure remotes (`git remote add`, `git remote set-url`)
150+
141151
**Best Practice:** Run `sofos` from your project directory and use git to track changes.
142152

143153
## Development

src/tools/bashexec.rs

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,10 @@ impl BashExecutor {
120120
return false;
121121
}
122122

123+
if !self.is_safe_git_command(&command_lower) {
124+
return false;
125+
}
126+
123127
let forbidden_commands = [
124128
"rm",
125129
"mv",
@@ -183,6 +187,78 @@ impl BashExecutor {
183187

184188
true
185189
}
190+
191+
fn is_safe_git_command(&self, command: &str) -> bool {
192+
if !command.starts_with("git ") && !command.contains(" git ") && !command.contains(";git ")
193+
&& !command.contains("&&git ") && !command.contains("||git ") && !command.contains("|git ") {
194+
return true;
195+
}
196+
197+
// Allow safe git stash read-only operations
198+
if command.contains("git stash list") || command.contains("git stash show") {
199+
return true;
200+
}
201+
202+
// Dangerous git operations that are completely blocked
203+
let dangerous_git_ops = [
204+
"git push",
205+
"git pull",
206+
"git fetch",
207+
"git clone",
208+
"git clean",
209+
"git reset --hard",
210+
"git reset --mixed",
211+
"git checkout -f",
212+
"git checkout -b",
213+
"git checkout --",
214+
"git branch -d",
215+
"git branch -D",
216+
"git branch -m",
217+
"git branch -M",
218+
"git remote add",
219+
"git remote set-url",
220+
"git remote remove",
221+
"git remote rm",
222+
"git submodule",
223+
"git filter-branch",
224+
"git gc",
225+
"git prune",
226+
"git update-ref",
227+
"git send-email",
228+
"git apply",
229+
"git am",
230+
"git cherry-pick",
231+
"git revert",
232+
"git commit",
233+
"git merge",
234+
"git rebase",
235+
"git tag -d",
236+
"git stash", // Blocks all stash operations except list/show (checked above)
237+
"git init",
238+
"git add",
239+
"git rm",
240+
"git mv",
241+
"git restore",
242+
"git switch",
243+
];
244+
245+
for dangerous_op in &dangerous_git_ops {
246+
if command.starts_with(dangerous_op)
247+
|| command.contains(&format!(" {}", dangerous_op))
248+
|| command.contains(&format!(";{}", dangerous_op))
249+
|| command.contains(&format!("&&{}", dangerous_op))
250+
|| command.contains(&format!("||{}", dangerous_op))
251+
|| command.contains(&format!("|{}", dangerous_op))
252+
{
253+
return false;
254+
}
255+
}
256+
257+
// Allow safe read-only git commands:
258+
// git status, git log, git diff, git show, git branch (list only),
259+
// git remote -v (view only), git config --list, git ls-files, etc.
260+
true
261+
}
186262
}
187263

188264
#[cfg(test)]
@@ -273,4 +349,107 @@ mod tests {
273349
panic!("Expected ToolExecution error");
274350
}
275351
}
352+
353+
#[test]
354+
fn test_safe_git_commands() {
355+
let executor = BashExecutor::new(PathBuf::from(".")).unwrap();
356+
357+
// Safe read-only git commands
358+
assert!(executor.is_safe_command("git status"));
359+
assert!(executor.is_safe_command("git log"));
360+
assert!(executor.is_safe_command("git log --oneline"));
361+
assert!(executor.is_safe_command("git diff"));
362+
assert!(executor.is_safe_command("git diff HEAD~1"));
363+
assert!(executor.is_safe_command("git show"));
364+
assert!(executor.is_safe_command("git show HEAD"));
365+
assert!(executor.is_safe_command("git branch"));
366+
assert!(executor.is_safe_command("git branch -v"));
367+
assert!(executor.is_safe_command("git branch --list"));
368+
assert!(executor.is_safe_command("git remote -v"));
369+
assert!(executor.is_safe_command("git config --list"));
370+
assert!(executor.is_safe_command("git ls-files"));
371+
assert!(executor.is_safe_command("git ls-tree HEAD"));
372+
assert!(executor.is_safe_command("git blame file.txt"));
373+
assert!(executor.is_safe_command("git grep pattern"));
374+
assert!(executor.is_safe_command("git rev-parse HEAD"));
375+
assert!(executor.is_safe_command("git describe --tags"));
376+
assert!(executor.is_safe_command("git stash list"));
377+
assert!(executor.is_safe_command("git stash show"));
378+
assert!(executor.is_safe_command("git stash show stash@{0}"));
379+
}
380+
381+
#[test]
382+
fn test_dangerous_git_commands() {
383+
let executor = BashExecutor::new(PathBuf::from(".")).unwrap();
384+
385+
// Remote operations (data leakage risk)
386+
assert!(!executor.is_safe_command("git push"));
387+
assert!(!executor.is_safe_command("git push origin main"));
388+
assert!(!executor.is_safe_command("git push --force"));
389+
assert!(!executor.is_safe_command("git pull"));
390+
assert!(!executor.is_safe_command("git pull origin main"));
391+
assert!(!executor.is_safe_command("git fetch"));
392+
assert!(!executor.is_safe_command("git fetch origin"));
393+
assert!(!executor.is_safe_command("git clone https://example.com/repo.git"));
394+
395+
// Destructive local operations
396+
assert!(!executor.is_safe_command("git clean -fd"));
397+
assert!(!executor.is_safe_command("git reset --hard"));
398+
assert!(!executor.is_safe_command("git reset --hard HEAD~1"));
399+
assert!(!executor.is_safe_command("git checkout -f"));
400+
assert!(!executor.is_safe_command("git checkout -b newbranch"));
401+
assert!(!executor.is_safe_command("git branch -D branch-name"));
402+
assert!(!executor.is_safe_command("git branch -d branch-name"));
403+
assert!(!executor.is_safe_command("git filter-branch"));
404+
405+
// Modifications
406+
assert!(!executor.is_safe_command("git add ."));
407+
assert!(!executor.is_safe_command("git add file.txt"));
408+
assert!(!executor.is_safe_command("git commit -m 'message'"));
409+
assert!(!executor.is_safe_command("git commit --amend"));
410+
assert!(!executor.is_safe_command("git rm file.txt"));
411+
assert!(!executor.is_safe_command("git mv old.txt new.txt"));
412+
assert!(!executor.is_safe_command("git merge branch"));
413+
assert!(!executor.is_safe_command("git rebase main"));
414+
assert!(!executor.is_safe_command("git cherry-pick abc123"));
415+
assert!(!executor.is_safe_command("git revert abc123"));
416+
assert!(!executor.is_safe_command("git restore file.txt"));
417+
assert!(!executor.is_safe_command("git switch main"));
418+
419+
// Remote configuration changes
420+
assert!(!executor.is_safe_command("git remote add origin https://evil.com/repo.git"));
421+
assert!(!executor.is_safe_command("git remote set-url origin https://evil.com/repo.git"));
422+
assert!(!executor.is_safe_command("git remote remove origin"));
423+
424+
// Submodules (can fetch from remote)
425+
assert!(!executor.is_safe_command("git submodule update"));
426+
assert!(!executor.is_safe_command("git submodule init"));
427+
428+
// Stash operations (modify state)
429+
assert!(!executor.is_safe_command("git stash"));
430+
assert!(!executor.is_safe_command("git stash pop"));
431+
assert!(!executor.is_safe_command("git stash apply"));
432+
assert!(!executor.is_safe_command("git stash drop"));
433+
assert!(!executor.is_safe_command("git stash clear"));
434+
435+
// Init (creates repository)
436+
assert!(!executor.is_safe_command("git init"));
437+
assert!(!executor.is_safe_command("git init new-repo"));
438+
}
439+
440+
#[test]
441+
fn test_git_commands_in_chains() {
442+
let executor = BashExecutor::new(PathBuf::from(".")).unwrap();
443+
444+
// Safe commands in chains
445+
assert!(executor.is_safe_command("git status && git log"));
446+
assert!(executor.is_safe_command("git diff | grep pattern"));
447+
assert!(executor.is_safe_command("echo test; git status"));
448+
449+
// Dangerous commands in chains
450+
assert!(!executor.is_safe_command("git status && git push"));
451+
assert!(!executor.is_safe_command("git log | git commit -m 'test'"));
452+
assert!(!executor.is_safe_command("echo test; git add ."));
453+
assert!(!executor.is_safe_command("git status || git pull"));
454+
}
276455
}

0 commit comments

Comments
 (0)