feat: allow relative target paths for git mounts

- Remove absolute path requirement from target_path validation
- Resolve relative paths against working_directory at instance startup
- Fall back to /home/user if no working_directory is configured
- Update frontend to allow relative target paths
- Update spec to document relative path support
- Update tests to allow relative paths and test path traversal rejection
This commit is contained in:
Alex Blank
2026-05-27 15:01:16 +02:00
parent 8a58c61278
commit 33d08faf70
5 changed files with 15 additions and 10 deletions
-2
View File
@@ -82,8 +82,6 @@ class GitMountItem(BaseModel):
@field_validator("target_path")
@classmethod
def validate_target_path(cls, v: str) -> str:
if not v.startswith("/"):
raise ValueError("target_path must be absolute (start with /)")
if ".." in v:
raise ValueError("target_path cannot contain path traversal (..)")
return v
+10 -2
View File
@@ -61,6 +61,7 @@ async def _resolve_git_mounts(
session: AsyncSession,
resolved: ResolvedProfile,
instance_dir: str | None = None,
working_directory: str | None = None,
) -> list[dict]:
"""Convert git mounts from resolved profile to Docker volume mounts.
@@ -74,7 +75,7 @@ async def _resolve_git_mounts(
# Process all git mounts concurrently
tasks = []
for git_mount in resolved.git_mounts:
tasks.append(_resolve_single_git_mount(session, git_mount, instance_dir))
tasks.append(_resolve_single_git_mount(session, git_mount, instance_dir, working_directory))
results = await asyncio.gather(*tasks, return_exceptions=True)
@@ -93,6 +94,7 @@ async def _resolve_single_git_mount(
session: AsyncSession,
git_mount: dict,
instance_dir: str | None = None,
working_directory: str | None = None,
) -> list[dict]:
"""Resolve a single git mount to volume mount entries.
@@ -108,6 +110,12 @@ async def _resolve_single_git_mount(
logger.warning("Invalid git mount skipped: missing remote_url or target_path")
return []
# Resolve relative target paths against working directory
if target_path and not target_path.startswith("/"):
base = working_directory or "/home/user"
target_path = os.path.join(base, target_path)
logger.info("Resolved relative target path to %s", target_path)
if not instance_dir:
logger.warning("Git mount skipped: no instance_dir provided for cloning")
return []
@@ -918,7 +926,7 @@ async def start_instance(
# Profile mounts are added to extra volumes
extra_volumes.extend(profile_mounts)
# Git repository mounts are resolved and added
git_mount_volumes = await _resolve_git_mounts(session, resolved, instance_dir)
git_mount_volumes = await _resolve_git_mounts(session, resolved, instance_dir, working_directory)
extra_volumes.extend(git_mount_volumes)
# Profile runtime hints override tool config values
if profile_hints.get("start_command"):