Create Relay Bot MVP
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
from pathlib import Path, PurePosixPath
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
SECRET_NAMES = {".env", ".env.local", "id_rsa", "credentials.json", "secrets.yml", "secrets.yaml"}
|
||||
SKIPPED_PARTS = {".git", "node_modules", ".venv", "venv", "build", "dist", "__pycache__"}
|
||||
SECRET_SUFFIXES = {".pem", ".key", ".p12", ".pfx"}
|
||||
|
||||
|
||||
def safe_repository_path(root: Path, requested: str) -> Path:
|
||||
if not requested or "\x00" in requested:
|
||||
raise ValueError("Invalid path")
|
||||
candidate = (root / requested).resolve()
|
||||
if root.resolve() not in candidate.parents and candidate != root.resolve():
|
||||
raise ValueError("Path is outside the repository")
|
||||
if any(part in SKIPPED_PARTS for part in candidate.relative_to(root.resolve()).parts):
|
||||
raise ValueError("Excluded path")
|
||||
if candidate.name.lower() in SECRET_NAMES or candidate.suffix.lower() in SECRET_SUFFIXES:
|
||||
raise ValueError("Sensitive file")
|
||||
return candidate
|
||||
|
||||
|
||||
def validate_repository_url(value: str) -> str:
|
||||
parsed = urlsplit(value.strip())
|
||||
if parsed.scheme != "https" or not parsed.netloc or parsed.username or parsed.password:
|
||||
raise ValueError("Нужна HTTPS-ссылка на Git-репозиторий без токена в URL")
|
||||
if parsed.query or parsed.fragment:
|
||||
raise ValueError("Ссылка на репозиторий не должна содержать параметры или fragment")
|
||||
path = parsed.path.rstrip("/")
|
||||
if path.endswith(".git"):
|
||||
path = path[:-4]
|
||||
if not path or path == "/":
|
||||
raise ValueError("Неполная ссылка на репозиторий")
|
||||
return f"https://{parsed.netloc}{path}.git"
|
||||
|
||||
|
||||
def safe_repository_member(requested: str) -> PurePosixPath:
|
||||
path = PurePosixPath(requested)
|
||||
if not requested or path.is_absolute() or ".." in path.parts or "\x00" in requested:
|
||||
raise ValueError("Invalid repository path")
|
||||
if any(part in SKIPPED_PARTS for part in path.parts):
|
||||
raise ValueError("Excluded path")
|
||||
if path.name.lower() in SECRET_NAMES or path.suffix.lower() in SECRET_SUFFIXES:
|
||||
raise ValueError("Sensitive file")
|
||||
return path
|
||||
|
||||
|
||||
def allowed_repository_file(path: Path, max_bytes: int) -> bool:
|
||||
try:
|
||||
safe_repository_path(path.parent if path.is_absolute() else Path("."), path.name)
|
||||
return (
|
||||
path.is_file()
|
||||
and path.stat().st_size <= max_bytes
|
||||
and path.suffix.lower() not in SECRET_SUFFIXES
|
||||
)
|
||||
except (OSError, ValueError):
|
||||
return False
|
||||
Reference in New Issue
Block a user