88import subprocess
99from typing import Dict , List , Optional
1010from pathlib import Path
11+ from urllib .parse import urlparse
1112import git
1213
1314
1415class ComposeStackManager :
1516 """Manage Docker Compose stacks from Git repositories."""
16-
17+
1718 def __init__ (self , stacks_dir : str = "/opt/stacks" ):
1819 self .stacks_dir = Path (stacks_dir )
1920 self .stacks_dir .mkdir (parents = True , exist_ok = True )
21+
22+ def _validate_repo_url (self , url : str ) -> bool :
23+ """Validate repository URL is from allowed sources.
24+
25+ Args:
26+ url: Git repository URL to validate
27+
28+ Returns:
29+ True if URL is allowed, False otherwise
30+ """
31+ # Get allowed hosts from environment, default to common git providers
32+ allowed_hosts_str = os .getenv (
33+ "SYSTEMMANAGER_ALLOWED_GIT_HOSTS" ,
34+ "github.com,gitlab.com,bitbucket.org"
35+ )
36+ allowed_hosts = [h .strip () for h in allowed_hosts_str .split ("," ) if h .strip ()]
37+
38+ try :
39+ parsed = urlparse (url )
40+
41+ # Only allow https and git protocols
42+ if parsed .scheme not in ['https' , 'git' ]:
43+ return False
44+
45+ # Extract hostname (handle git@host:repo format)
46+ if '@' in parsed .netloc :
47+ host = parsed .netloc .split ('@' )[- 1 ].split (':' )[0 ]
48+ else :
49+ host = parsed .netloc .split (':' )[0 ]
50+
51+ # Check if host is in allowed list
52+ return any (host == allowed or host .endswith ('.' + allowed ) for allowed in allowed_hosts )
53+ except Exception :
54+ return False
2055
2156 async def deploy_stack (
2257 self ,
@@ -39,8 +74,16 @@ async def deploy_stack(
3974 Deployment status and details
4075 """
4176 stack_path = self .stacks_dir / stack_name
42-
77+
4378 try :
79+ # Validate repository URL
80+ if not self ._validate_repo_url (repo_url ):
81+ return {
82+ "success" : False ,
83+ "error" : f"Repository URL not allowed: { repo_url } . Configure SYSTEMMANAGER_ALLOWED_GIT_HOSTS to allow this host." ,
84+ "stack" : stack_name
85+ }
86+
4487 # Clone or pull repository
4588 if stack_path .exists ():
4689 repo = git .Repo (stack_path )
0 commit comments