fix(drone): external projects can resolve AIPass branches (#618)

resolve_branch() validated branch path containment against the primary
registry root even when the branch was found via the AIPASS_HOME fallback,
so external projects (Vera, Daemon) were blocked from calling @api and any
other AIPass branch with 'path escapes project root'.

Add get_branch_with_registry() (non-breaking sibling to get_branch_by_name)
that returns the branch plus the registry it was found in. resolve_branch()
now validates containment against that registry's root. Security preserved:
each branch stays contained within its own declaring registry; genuine
escapes still blocked. 4 new cross-project resolver tests, 58 resolver
tests pass, drone suite 702 pass, seedgo @drone 99%.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
AIOSAI
2026-06-02 16:25:46 -07:00
co-authored by Claude Opus 4.8
parent cc449c4a18
commit bedf58e7b5
4 changed files with 138 additions and 4 deletions
+5 -4
View File
@@ -25,7 +25,7 @@ from aipass.drone.apps.handlers.registry_handler import (
load_registry,
get_all_branches,
get_branch_by_name,
get_registry_path,
get_branch_with_registry,
_validate_branch_path,
)
@@ -156,13 +156,14 @@ def resolve_branch(symbolic_name: str) -> str:
raise BranchNotFoundError(f"Branch name must use @ prefix: '@{symbolic_name}' (got '{symbolic_name}')")
name = normalize_branch_name(symbolic_name).lower()
branch = get_branch_by_name(name)
result = get_branch_with_registry(name)
if branch is None:
if result is None:
raise BranchNotFoundError(f"Branch '{symbolic_name}' not found in registry")
branch, source_registry = result
branch_path = Path(branch["path"])
project_root = get_registry_path().parent
project_root = source_registry.parent
if not branch_path.is_absolute():
branch_path = project_root / branch_path
if not _validate_branch_path(branch_path, project_root, name):