Skip to content

Commit b013707

Browse files
committed
Resolve dependabot alerts #3: refactor git clone implementation.
1 parent b63bab2 commit b013707

File tree

5 files changed

+117
-24
lines changed

5 files changed

+117
-24
lines changed

CHANGELOG.md

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<!--
22
## [Unreleased]
3-
### ➕ Added
4-
### 🛠 Changed
3+
### 🚀 New Features and Enhancements
4+
### 🛠 Changes
55
### ⚙️ Deprecated
66
### 🗑 Removed
77
### 🐛 Bug Fixes
@@ -14,6 +14,39 @@ All notable changes to the *readme-ai* project will be documented in this file.
1414

1515
---
1616

17+
## [v0.0.7] - *2023-08-30*
18+
19+
⚠️ Release v0.0.7 addresses a security vulnerability cloning git repositories via the *GitPython* package on Windows systems. This vulnerability could allow arbitrary command execution if code is run from a directory containing a malicious `git.exe` or `git` executable.
20+
21+
### 🔐 Security Fixes
22+
#### *Arbitrary Command Execution Mitigation*
23+
24+
- Dependabot Alert [#3](https://github.com/eli64s/readme-ai/security/dependabot/3): GitPython untrusted search path on Windows systems leading to arbitrary code execution.
25+
- The previous git clone implementation sets the `env` argument to the path of the git executable in the current working directory. This poses a security risk as the code is susceptible to running arbitrary `git` commands from a malicious repository.
26+
```python
27+
git.Repo.clone_from(repo_path, temp_dir, depth=1)
28+
```
29+
- Updated the `env` argument to explicitly set the absolute path of the git executable. This ensures that the git executable used to clone the repository is the one thats installed in the system path, and not the one located in the current working directory.
30+
```python
31+
git.Repo.clone_from(repo_path, temp_dir, depth=1, env=git_exec_path)
32+
```
33+
### 🚀 New Features and Enhancements
34+
35+
#### *Code Modularity*
36+
37+
- Introduced three methods to help isolate the Git executable discovery and validation logic.
38+
- `find_git_executable()`: Determines the absolute path of the Git executable.
39+
- `validate_git_executable()`: Validates the found Git executable path.
40+
- `validate_file_permissions()`: Validates the file permissions of the cloned repository.
41+
42+
#### *File Permission Checks*
43+
44+
- For Unix systems, added checks to ensure the permissions of the cloned repository are set to `0o700`. This is a best practice for secure temporary directories and prevents unauthorized users from accessing the directory.
45+
46+
⚠️ These updates aim to mitigate the vulnerbility raised in Dependabot alert [#3](https://github.com/eli64s/readme-ai/security/dependabot/3). Users are advised to update *readme-ai* to the latest version, i.e ```pip install --upgrade readmeai```. Please be mindful of this vulnerability and use caution when cloning repositories from untrusted sources, especially for Windows users.
47+
48+
---
49+
1750
## [v0.0.6] - *2023-08-29*
1851

1952
### 🐛 Bug Fixes
@@ -26,7 +59,7 @@ All notable changes to the *readme-ai* project will be documented in this file.
2659

2760
## [v0.0.5] - *2023-07-31*
2861

29-
### ➕ Added
62+
### 🚀 New Features and Enhancements
3063

3164
- Add [.dockerignore](./.dockerignore) file to exclude unnecessary files from the Docker image.
3265

@@ -48,7 +81,7 @@ All notable changes to the *readme-ai* project will be documented in this file.
4881

4982
## [v0.0.4] - *2023-07-30*
5083

51-
### ➕ Added
84+
### 🚀 New Features and Enhancements
5285

5386
- Publish *readme-ai* CLI to PyPI under the module name [readmeai](https://pypi.org/project/readmeai/).
5487
- Refactored the codebase to use [Click](https://click.palletsprojects.com/en/8.1.x/), migrating from argparse.
@@ -75,7 +108,7 @@ All notable changes to the *readme-ai* project will be documented in this file.
75108

76109
## [v0.0.3] - *2023-06-29*
77110

78-
### ➕ Added
111+
### 🚀 New Features and Enhancements
79112

80113
- Add [pydantic](https://pydantic-docs.helpmanual.io/) to validate the user's repository and api key inputs.
81114
- Validation was moved from *main.py* to *conf.py*.
@@ -91,12 +124,12 @@ All notable changes to the *readme-ai* project will be documented in this file.
91124

92125
## [v0.0.2] - *2023-06-28*
93126

94-
### ➕ Added
127+
### 🚀 New Features and Enhancements
95128

96129
- Add [CHANGELOG.md](./CHANGELOG.md) to track changes to the project.
97130
- Add new directory [examples/video](./examples/video) to store mp4 videos to demonstrate the *readme-ai* tool.
98131

99-
### 🛠 Changed
132+
### 🛠 Changes
100133

101134
- Update [Makefile](./Makefile) and [setup.sh](./setup/setup.sh) to use *poetry* for dependency management.
102135

@@ -109,10 +142,10 @@ All notable changes to the *readme-ai* project will be documented in this file.
109142

110143
## [v0.0.1] - *2023-06-28*
111144

112-
### ➕ Added
145+
### 🚀 New Features and Enhancements
113146
- Initial release of *readme-ai* v0.0.1
114147

115-
### 🛠 Changed
148+
### 🛠 Changes
116149

117150
- Refine the markdown template structure to be more readable.
118151

poetry.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
44

55
[tool.poetry]
66
name = "readmeai"
7-
version = "0.3.1"
7+
version = "0.3.015"
88
description = "🚀 Generate awesome README.md files from the terminal, powered by OpenAI's GPT language model APIs 💫"
99
authors = ["Eli <0x.eli.64s@gmail.com>"]
1010
license = "MIT"

readmeai/preprocess.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,13 @@ def __init__(
4848
self.language_setup = language_setup
4949
self.encoding_name = config.api.encoding
5050

51-
def analyze(self, root_path: str, is_remote: bool = False) -> List[Dict]:
51+
def analyze(self, repo_path: str, is_remote: bool = False) -> List[Dict]:
5252
"""Analyzes a local or remote git repository."""
5353
with tempfile.TemporaryDirectory() as temp_dir:
5454
if is_remote:
55-
utils.clone_repository(root_path, temp_dir)
56-
root_path = temp_dir
57-
contents = self.generate_contents(root_path)
55+
utils.clone_repository(repo_path, temp_dir)
56+
repo_path = temp_dir
57+
contents = self.generate_contents(repo_path)
5858
contents = self.tokenize_content(contents)
5959
contents = self.process_language_mapping(contents)
6060
return contents

readmeai/utils.py

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,81 @@
11
"""Utility methods for the readme-ai application."""
22

3+
import os
4+
import platform
35
import re
46
from pathlib import Path
5-
from typing import List
7+
from typing import List, Optional
68

79
import git
810
from tiktoken import get_encoding
911

10-
from . import conf
12+
from . import conf, logger
1113

14+
logger = logger.Logger(__name__)
1215

13-
def clone_repository(url: str, repo_path: Path) -> None:
16+
17+
def clone_repository(repo_path: str, temp_dir: Path) -> None:
1418
"""Clone a repository to a temporary directory."""
19+
git_exec_path = find_git_executable()
20+
21+
validate_git_executable(git_exec_path)
22+
23+
env = os.environ.copy()
24+
env["GIT_PYTHON_GIT_EXECUTABLE"] = str(git_exec_path)
25+
1526
try:
16-
git.Repo.clone_from(url, repo_path, depth=1)
17-
except git.exc.GitCommandError as exc:
18-
raise ValueError(f"Error cloning repository: {exc}") from exc
27+
git.Repo.clone_from(repo_path, temp_dir, depth=1, env=env)
28+
logger.info(f"Successfully cloned {repo_path} to {temp_dir}.")
29+
30+
except git.GitCommandError as excinfo:
31+
raise ValueError(f"Git clone error: {excinfo}") from excinfo
32+
33+
except Exception as excinfo:
34+
raise (f"Error cloning git repository: {excinfo}")
35+
36+
validate_file_permissions(temp_dir)
37+
38+
39+
def find_git_executable() -> Optional[Path]:
40+
"""Find the path to the git executable, if available."""
41+
42+
git_exec_path = os.environ.get("GIT_PYTHON_GIT_EXECUTABLE")
43+
44+
if git_exec_path:
45+
return Path(git_exec_path)
46+
47+
# For Windows, set default known location for git executable
48+
if platform.system() == "Windows":
49+
default_windows_path = Path("C:\\Program Files\\Git\\cmd\\git.EXE")
50+
if default_windows_path.exists():
51+
return default_windows_path
52+
53+
# For other OS (including Linux), set executable by looking into PATH
54+
paths = os.environ["PATH"].split(os.pathsep)
55+
for path in paths:
56+
git_path = Path(path) / "git"
57+
if git_path.exists():
58+
return git_path
59+
60+
return None
61+
62+
63+
def validate_git_executable(git_exec_path: Optional[str]) -> None:
64+
"""Validate the path to the git executable."""
65+
if not git_exec_path or not Path(git_exec_path).exists():
66+
raise ValueError(f"Git executable not found at {git_exec_path}")
67+
68+
69+
def validate_file_permissions(temp_dir: Path) -> None:
70+
"""Validates file permissions of the cloned repository."""
71+
if platform.system() != "Windows":
72+
if isinstance(temp_dir, str):
73+
temp_dir = Path(temp_dir)
74+
permissions = temp_dir.stat().st_mode & 0o777
75+
if permissions != 0o700:
76+
raise ValueError(
77+
"Error: file permissions of cloned repository must be set to 0o700."
78+
)
1979

2080

2181
def get_github_file_link(file: str, user_repo_name: str) -> str:
@@ -32,7 +92,7 @@ def get_user_repository_name(url) -> str:
3292
username, reponame = match.groups()
3393
return f"{username}/{reponame}"
3494
else:
35-
return "Invalid remote git URL."
95+
raise ("Error: invalid remote repository URL.")
3696

3797

3898
def adjust_max_tokens(max_tokens: int, prompt: str, target: str = "Hello!") -> int:

0 commit comments

Comments
 (0)