Problem
ProjectIn.base_catalog_url is an unvalidated str that flows straight into git.Repo.clone_from() — an SSRF / arbitrary-protocol sink.
Evidence
app/schemas/v1/projects.py:16 — base_catalog_url: str | None = None (contrast SourceIn.base_url which correctly uses HttpUrl)
app/routes/v1/projects/compose.py:56 — git.Repo.clone_from(project.base_catalog_url, td)
Impact (High)
A crafted value (file:///..., ssh://, git@host:..., internal URLs) is executed by GitPython/libgit2 on the server during compose. Reachable via POST /v1/projects + compose. SourceRepo.owner/.repo have a related unbounded-string concatenation risk (app/core/models.py:41-42).
Suggested fix
Validate base_catalog_url as HttpUrl (http/https only) at the schema level; constrain SourceRepo.owner/.repo to a safe character class.
Problem
ProjectIn.base_catalog_urlis an unvalidatedstrthat flows straight intogit.Repo.clone_from()— an SSRF / arbitrary-protocol sink.Evidence
app/schemas/v1/projects.py:16—base_catalog_url: str | None = None(contrastSourceIn.base_urlwhich correctly usesHttpUrl)app/routes/v1/projects/compose.py:56—git.Repo.clone_from(project.base_catalog_url, td)Impact (High)
A crafted value (
file:///...,ssh://,git@host:..., internal URLs) is executed by GitPython/libgit2 on the server during compose. Reachable viaPOST /v1/projects+ compose.SourceRepo.owner/.repohave a related unbounded-string concatenation risk (app/core/models.py:41-42).Suggested fix
Validate
base_catalog_urlasHttpUrl(http/https only) at the schema level; constrainSourceRepo.owner/.repoto a safe character class.