mirror of
https://github.com/github/spec-kit.git
synced 2026-02-03 14:33:36 +00:00
Strengthens APM url module parsing as per PR review
This commit is contained in:
@@ -122,27 +122,37 @@ class DependencyReference:
|
|||||||
# Already a full URL - parse directly
|
# Already a full URL - parse directly
|
||||||
parsed_url = urllib.parse.urlparse(repo_url)
|
parsed_url = urllib.parse.urlparse(repo_url)
|
||||||
else:
|
else:
|
||||||
# For any other format, try both github.com/ prefix and user/repo formats
|
# Safely construct GitHub URL from various input formats
|
||||||
# Let urllib.parse handle the validation instead of unsafe string operations
|
if repo_url.startswith("github.com/"):
|
||||||
candidate_urls = [
|
# Remove github.com/ prefix to get user/repo part
|
||||||
urllib.parse.urljoin("https://", repo_url), # Handles github.com/user/repo format
|
user_repo = repo_url[len("github.com/"):]
|
||||||
urllib.parse.urljoin("https://github.com/", repo_url) # Handles user/repo format
|
else:
|
||||||
]
|
# For any input that contains a domain-like pattern, reject it unless it's github.com
|
||||||
|
if "." in repo_url.split("/")[0] and not repo_url.startswith("github.com/"):
|
||||||
|
raise ValueError(f"Only GitHub repositories are supported. Use 'user/repo' or 'github.com/user/repo' format")
|
||||||
|
|
||||||
|
# Assume it's in user/repo format
|
||||||
|
user_repo = repo_url
|
||||||
|
|
||||||
parsed_url = None
|
# Validate format before URL construction (security critical)
|
||||||
for candidate in candidate_urls:
|
if not user_repo or "/" not in user_repo:
|
||||||
try:
|
|
||||||
test_parsed = urllib.parse.urlparse(candidate)
|
|
||||||
if (test_parsed.netloc == "github.com" and
|
|
||||||
test_parsed.path.strip("/") and
|
|
||||||
len(test_parsed.path.strip("/").split("/")) >= 2):
|
|
||||||
parsed_url = test_parsed
|
|
||||||
break
|
|
||||||
except Exception:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if parsed_url is None:
|
|
||||||
raise ValueError(f"Invalid repository format: {repo_url}. Expected 'user/repo' or 'github.com/user/repo'")
|
raise ValueError(f"Invalid repository format: {repo_url}. Expected 'user/repo' or 'github.com/user/repo'")
|
||||||
|
|
||||||
|
parts = user_repo.split("/")
|
||||||
|
if len(parts) < 2 or not parts[0] or not parts[1]:
|
||||||
|
raise ValueError(f"Invalid repository format: {repo_url}. Expected 'user/repo' or 'github.com/user/repo'")
|
||||||
|
|
||||||
|
user, repo = parts[0], parts[1]
|
||||||
|
|
||||||
|
# Security: validate characters to prevent injection
|
||||||
|
if not re.match(r'^[a-zA-Z0-9._-]+$', user):
|
||||||
|
raise ValueError(f"Invalid user name: {user}")
|
||||||
|
if not re.match(r'^[a-zA-Z0-9._-]+$', repo.rstrip('.git')):
|
||||||
|
raise ValueError(f"Invalid repository name: {repo}")
|
||||||
|
|
||||||
|
# Safely construct URL - this is now secure
|
||||||
|
github_url = urllib.parse.urljoin("https://github.com/", f"{user}/{repo}")
|
||||||
|
parsed_url = urllib.parse.urlparse(github_url)
|
||||||
|
|
||||||
# SECURITY: Validate that this is actually a GitHub URL with exact hostname match
|
# SECURITY: Validate that this is actually a GitHub URL with exact hostname match
|
||||||
if parsed_url.netloc != "github.com":
|
if parsed_url.netloc != "github.com":
|
||||||
|
|||||||
Reference in New Issue
Block a user