github-agent 0.23.0__tar.gz → 0.24.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {github_agent-0.23.0 → github_agent-0.24.0}/PKG-INFO +3 -3
- {github_agent-0.23.0 → github_agent-0.24.0}/README.md +1 -1
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/agent_server.py +1 -1
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent.egg-info/PKG-INFO +3 -3
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent.egg-info/SOURCES.txt +1 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent.egg-info/requires.txt +1 -1
- {github_agent-0.23.0 → github_agent-0.24.0}/pyproject.toml +2 -2
- github_agent-0.24.0/scripts/security_sanitizer.py +160 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/tests/test_auth_client_edge_cases.py +5 -5
- {github_agent-0.23.0 → github_agent-0.24.0}/LICENSE +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/__init__.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/__main__.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/api/__init__.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/api/api_client_base.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/api/api_client_branches.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/api/api_client_commits.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/api/api_client_contents.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/api/api_client_issues.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/api/api_client_orgs.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/api/api_client_pulls.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/api/api_client_releases.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/api/api_client_repos.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/api/api_client_search.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/api/api_client_workflows.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/api_client.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/auth.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/github_input_models.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/github_response_models.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/mcp/__init__.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/mcp/mcp_action.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/mcp/mcp_branch.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/mcp/mcp_collaborator.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/mcp/mcp_commit.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/mcp/mcp_content.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/mcp/mcp_issue.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/mcp/mcp_org.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/mcp/mcp_pull.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/mcp/mcp_release.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/mcp/mcp_repo.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/mcp/mcp_search.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/mcp_config.json +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent/mcp_server.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent.egg-info/dependency_links.txt +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent.egg-info/entry_points.txt +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/github_agent.egg-info/top_level.txt +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/scripts/validate_a2a_agent.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/scripts/verify_api_integration.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/setup.cfg +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/tests/conftest.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/tests/test_api_client_endpoints.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/tests/test_api_validation.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/tests/test_concept_parity.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/tests/test_github_agent_api_brute_force_coverage.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/tests/test_github_agent_brute_force_coverage.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/tests/test_init_dynamics.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/tests/test_mcp_coverage.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/tests/test_model_fields.py +0 -0
- {github_agent-0.23.0 → github_agent-0.24.0}/tests/test_startup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: github-agent
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.24.0
|
|
4
4
|
Summary: GitHub Agent for MCP
|
|
5
5
|
Author-email: Audel Rouhi <knucklessg1@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
12
12
|
Requires-Python: <3.14,>=3.11
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
License-File: LICENSE
|
|
15
|
-
Requires-Dist: agent-utilities[agent,logfire]>=0.
|
|
15
|
+
Requires-Dist: agent-utilities[agent,logfire]>=0.38.0
|
|
16
16
|
Provides-Extra: test
|
|
17
17
|
Requires-Dist: pytest-xdist>=3.6.0; extra == "test"
|
|
18
18
|
Requires-Dist: pytest; extra == "test"
|
|
@@ -41,7 +41,7 @@ Dynamic: license-file
|
|
|
41
41
|

|
|
42
42
|

|
|
43
43
|
|
|
44
|
-
*Version: 0.
|
|
44
|
+
*Version: 0.24.0*
|
|
45
45
|
|
|
46
46
|
---
|
|
47
47
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: github-agent
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.24.0
|
|
4
4
|
Summary: GitHub Agent for MCP
|
|
5
5
|
Author-email: Audel Rouhi <knucklessg1@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
12
12
|
Requires-Python: <3.14,>=3.11
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
License-File: LICENSE
|
|
15
|
-
Requires-Dist: agent-utilities[agent,logfire]>=0.
|
|
15
|
+
Requires-Dist: agent-utilities[agent,logfire]>=0.38.0
|
|
16
16
|
Provides-Extra: test
|
|
17
17
|
Requires-Dist: pytest-xdist>=3.6.0; extra == "test"
|
|
18
18
|
Requires-Dist: pytest; extra == "test"
|
|
@@ -41,7 +41,7 @@ Dynamic: license-file
|
|
|
41
41
|

|
|
42
42
|

|
|
43
43
|
|
|
44
|
-
*Version: 0.
|
|
44
|
+
*Version: 0.24.0*
|
|
45
45
|
|
|
46
46
|
---
|
|
47
47
|
|
|
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "github-agent"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.24.0"
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
description = "GitHub Agent for MCP"
|
|
10
10
|
requires-python = ">=3.11, <3.14"
|
|
11
11
|
classifiers = [ "Development Status :: 5 - Production/Stable", "License :: Public Domain", "Environment :: Console", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3",]
|
|
12
|
-
dependencies = [ "agent-utilities[agent,logfire]>=0.
|
|
12
|
+
dependencies = [ "agent-utilities[agent,logfire]>=0.38.0",]
|
|
13
13
|
[[project.authors]]
|
|
14
14
|
name = "Audel Rouhi"
|
|
15
15
|
email = "knucklessg1@gmail.com"
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
import sys
|
|
5
|
+
import subprocess
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
# Config
|
|
9
|
+
ALLOWED_TXT_NAMES = {"requirements.txt", "requirements-dev.txt"}
|
|
10
|
+
TRANSIENT_PY_PATTERNS = [
|
|
11
|
+
re.compile(r"^test_.*\.py$"),
|
|
12
|
+
re.compile(r"^fix_.*\.py$"),
|
|
13
|
+
re.compile(r"^debug_.*\.py$"),
|
|
14
|
+
re.compile(r"^scratch_.*\.py$"),
|
|
15
|
+
re.compile(r"^temp_.*\.py$"),
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
SECRET_PATTERNS = [
|
|
19
|
+
("GitHub PAT", re.compile(r"ghp_[A-Za-z0-9_]{36,255}")),
|
|
20
|
+
("GitHub Fine-grained PAT", re.compile(r"github_pat_[A-Za-z0-9_]{82,255}")),
|
|
21
|
+
("GitLab PAT", re.compile(r"glpat-[A-Za-z0-9\-]{20,255}")),
|
|
22
|
+
("Generic Secret Assignment", re.compile(r"secret[A-Za-z0-9_]*\s*[:=]\s*['\"][A-Za-z0-9_\-\.\~\*]{16,255}['\"]", re.IGNORECASE)),
|
|
23
|
+
("Generic Token Assignment", re.compile(r"token\s*[:=]\s*['\"][A-Za-z0-9_\-\.\~\*]{16,255}['\"]", re.IGNORECASE))
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
EXCLUDED_DIRS = {".git", ".venv", "venv", "node_modules", "build", "dist", "__pycache__", ".tox", ".specify"}
|
|
27
|
+
EXCLUDED_EXTENSIONS = {
|
|
28
|
+
".png", ".jpg", ".jpeg", ".gif", ".webp", ".ico", ".pyc", ".db", ".kuzu",
|
|
29
|
+
".sqlite", ".sqlite3", ".zip", ".tar.gz", ".tgz", ".bz2", ".xz", ".pdf",
|
|
30
|
+
".bin", ".exe", ".dll", ".so", ".dylib", ".woff", ".woff2", ".eot", ".ttf",
|
|
31
|
+
".mp4", ".mp3", ".wav", ".lock", ".svg"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Placeholder / Mock indicators
|
|
35
|
+
PLACEHOLDER_SUBSTRINGS = [
|
|
36
|
+
"1234567890", "abcdef12345", "abc123youandme", "askdfalskdvjas", "your_", "YOUR_", "your-",
|
|
37
|
+
"dummy", "DUMMY", "example", "EXAMPLE", "mock", "MOCK", "test_token", "test_secret",
|
|
38
|
+
"glpat-askdfalskdvjas", "github_pat_12345", "glpat-abc123youandme", "github_pat_...",
|
|
39
|
+
"glpat-*************", "ghp_*************", "github_pat_*************", "token_*************",
|
|
40
|
+
"secret_*************", "glpat-abc", "ghp_abc", "github_pat_abc", "${env:"
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
def is_placeholder(match_str: str) -> bool:
|
|
44
|
+
match_lower = match_str.lower()
|
|
45
|
+
for placeholder in PLACEHOLDER_SUBSTRINGS:
|
|
46
|
+
if placeholder in match_lower:
|
|
47
|
+
return True
|
|
48
|
+
|
|
49
|
+
# Check if match is mostly asterisks or single repeated char
|
|
50
|
+
cleaned = match_str.replace("'", "").replace('"', "").strip()
|
|
51
|
+
if not cleaned:
|
|
52
|
+
return True
|
|
53
|
+
|
|
54
|
+
# Check if there are sequences of asterisks indicating masked values
|
|
55
|
+
if "*" in cleaned:
|
|
56
|
+
# e.g., glpat-*************
|
|
57
|
+
return True
|
|
58
|
+
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
def get_repo_files(repo_path: Path):
|
|
62
|
+
try:
|
|
63
|
+
result = subprocess.run(
|
|
64
|
+
["git", "ls-files", "--cached", "--others", "--exclude-standard"],
|
|
65
|
+
cwd=str(repo_path),
|
|
66
|
+
capture_output=True,
|
|
67
|
+
text=True,
|
|
68
|
+
check=True
|
|
69
|
+
)
|
|
70
|
+
files = []
|
|
71
|
+
for line in result.stdout.splitlines():
|
|
72
|
+
if line.strip():
|
|
73
|
+
# Avoid files inside excluded directories
|
|
74
|
+
parts = Path(line.strip()).parts
|
|
75
|
+
if not any(part in EXCLUDED_DIRS for part in parts):
|
|
76
|
+
files.append(repo_path / line.strip())
|
|
77
|
+
return files
|
|
78
|
+
except Exception as e:
|
|
79
|
+
# Fallback to manual recursive scan
|
|
80
|
+
files = []
|
|
81
|
+
for root, dirs, walk_files in os.walk(str(repo_path)):
|
|
82
|
+
dirs[:] = [d for d in dirs if d not in EXCLUDED_DIRS and not d.startswith('.')]
|
|
83
|
+
for file in walk_files:
|
|
84
|
+
files.append(Path(root) / file)
|
|
85
|
+
return files
|
|
86
|
+
|
|
87
|
+
def scan_repository(repo_path: Path):
|
|
88
|
+
violations = []
|
|
89
|
+
files_to_scan = get_repo_files(repo_path)
|
|
90
|
+
|
|
91
|
+
for file_path in files_to_scan:
|
|
92
|
+
if not file_path.is_file():
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
# 1. Check root level naming constraints
|
|
96
|
+
if file_path.parent == repo_path:
|
|
97
|
+
# Check txt files
|
|
98
|
+
if file_path.suffix == ".txt":
|
|
99
|
+
if file_path.name.lower() not in ALLOWED_TXT_NAMES:
|
|
100
|
+
violations.append(
|
|
101
|
+
f"Non-standard root-level text file detected: '{file_path.name}'. Only 'requirements.txt' and 'requirements-dev.txt' are allowed."
|
|
102
|
+
)
|
|
103
|
+
# Check transient py files
|
|
104
|
+
elif file_path.suffix == ".py":
|
|
105
|
+
for pattern in TRANSIENT_PY_PATTERNS:
|
|
106
|
+
if pattern.match(file_path.name):
|
|
107
|
+
violations.append(
|
|
108
|
+
f"Transient/temporary script detected in root: '{file_path.name}'. Please move it to a subfolder or delete it."
|
|
109
|
+
)
|
|
110
|
+
break
|
|
111
|
+
|
|
112
|
+
# 2. Check for secrets
|
|
113
|
+
if file_path.suffix.lower() in EXCLUDED_EXTENSIONS:
|
|
114
|
+
continue
|
|
115
|
+
|
|
116
|
+
if file_path.name == "security_sanitizer.py":
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
content = file_path.read_text(encoding="utf-8", errors="ignore")
|
|
121
|
+
lines = content.splitlines()
|
|
122
|
+
|
|
123
|
+
for idx, line in enumerate(lines, 1):
|
|
124
|
+
if any(bypass in line for bypass in ["# sanitizer:ignore", "# sanitizer-ignore", "# nosec"]):
|
|
125
|
+
continue
|
|
126
|
+
|
|
127
|
+
for label, pattern in SECRET_PATTERNS:
|
|
128
|
+
for match in pattern.findall(line):
|
|
129
|
+
match_str = match[0] if isinstance(match, tuple) else match
|
|
130
|
+
if not is_placeholder(match_str):
|
|
131
|
+
rel_path = file_path.relative_to(repo_path)
|
|
132
|
+
violations.append(
|
|
133
|
+
f"Potential unmasked secret ({label}) detected in {rel_path}:{idx}\n"
|
|
134
|
+
f" Line: {line.strip()}"
|
|
135
|
+
|
|
136
|
+
)
|
|
137
|
+
except Exception:
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
return violations
|
|
141
|
+
|
|
142
|
+
def main():
|
|
143
|
+
repo_path = Path.cwd()
|
|
144
|
+
|
|
145
|
+
print("🔒 Running Security and Garbage Sanitizer...")
|
|
146
|
+
violations = scan_repository(repo_path)
|
|
147
|
+
|
|
148
|
+
if violations:
|
|
149
|
+
print("\n❌ SECURITY AND GARBAGE VALIDATION FAILED!")
|
|
150
|
+
print("Please correct the following issues before committing:")
|
|
151
|
+
for idx, violation in enumerate(violations, 1):
|
|
152
|
+
print(f"\n[{idx}] {violation}")
|
|
153
|
+
print("\nNote: To bypass secret checks on specific lines, append '# sanitizer:ignore' to the end of the line.")
|
|
154
|
+
sys.exit(1)
|
|
155
|
+
|
|
156
|
+
print("✅ All checks passed! No root garbage or unmasked secrets detected.")
|
|
157
|
+
sys.exit(0)
|
|
158
|
+
|
|
159
|
+
if __name__ == "__main__":
|
|
160
|
+
main()
|
|
@@ -93,7 +93,7 @@ def test_auth_get_client_delegation_missing_token():
|
|
|
93
93
|
|
|
94
94
|
def test_auth_get_client_delegation_exchange_failure():
|
|
95
95
|
# delegation enabled, user token present, but token exchange fails
|
|
96
|
-
local.user_token = "some-subject-token"
|
|
96
|
+
local.user_token = "some-subject-token" # sanitizer:ignore
|
|
97
97
|
config = {
|
|
98
98
|
"enable_delegation": True,
|
|
99
99
|
"audience": "github-audience",
|
|
@@ -115,7 +115,7 @@ def test_auth_get_client_delegation_exchange_failure():
|
|
|
115
115
|
|
|
116
116
|
def test_auth_get_client_delegation_auth_error():
|
|
117
117
|
# delegation enabled, user token present, token exchange succeeds, but Api throws AuthError
|
|
118
|
-
local.user_token = "some-subject-token"
|
|
118
|
+
local.user_token = "some-subject-token" # sanitizer:ignore
|
|
119
119
|
config = {
|
|
120
120
|
"enable_delegation": True,
|
|
121
121
|
"audience": "github-audience",
|
|
@@ -142,7 +142,7 @@ def test_auth_get_client_delegation_auth_error():
|
|
|
142
142
|
|
|
143
143
|
def test_auth_get_client_delegation_success():
|
|
144
144
|
# delegation enabled, user token present, token exchange succeeds, Api succeeds
|
|
145
|
-
local.user_token = "some-subject-token"
|
|
145
|
+
local.user_token = "some-subject-token" # sanitizer:ignore
|
|
146
146
|
config = {
|
|
147
147
|
"enable_delegation": True,
|
|
148
148
|
"audience": "github-audience",
|
|
@@ -165,7 +165,7 @@ def test_auth_get_client_delegation_success():
|
|
|
165
165
|
assert client == mock_api_instance
|
|
166
166
|
mock_api_class.assert_called_with(
|
|
167
167
|
url="https://api.github.com",
|
|
168
|
-
token="exchanged-github-token",
|
|
168
|
+
token="exchanged-github-token", # sanitizer:ignore
|
|
169
169
|
verify=True,
|
|
170
170
|
)
|
|
171
171
|
|
|
@@ -185,7 +185,7 @@ def test_auth_get_client_default_config():
|
|
|
185
185
|
|
|
186
186
|
def test_auth_invalid_oauth_types():
|
|
187
187
|
# delegation enabled, user token present, but one of the parameters is not a string
|
|
188
|
-
local.user_token = "some-subject-token"
|
|
188
|
+
local.user_token = "some-subject-token" # sanitizer:ignore
|
|
189
189
|
config = {
|
|
190
190
|
"enable_delegation": True,
|
|
191
191
|
"audience": 12345, # Invalid type (must be string)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{github_agent-0.23.0 → github_agent-0.24.0}/tests/test_github_agent_api_brute_force_coverage.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|