Registers builder endpoints if web is enabled and multipart is installed.
(app: FastAPI, web: bool, agents_dir: str)
| 97 | |
| 98 | |
| 99 | def _register_builder_endpoints(app: FastAPI, web: bool, agents_dir: str): |
| 100 | """Registers builder endpoints if web is enabled and multipart is installed.""" |
| 101 | if not web: |
| 102 | return |
| 103 | try: |
| 104 | import multipart # noqa: F401 |
| 105 | except ImportError: |
| 106 | logger.warning( |
| 107 | "python-multipart not installed. Builder UI endpoints will not be" |
| 108 | " available." |
| 109 | ) |
| 110 | return |
| 111 | |
| 112 | import shutil |
| 113 | |
| 114 | import yaml |
| 115 | |
| 116 | agents_base_path = (Path.cwd() / agents_dir).resolve() |
| 117 | |
| 118 | def _get_app_root(app_name: str) -> Path: |
| 119 | if app_name in ("", ".", ".."): |
| 120 | raise ValueError(f"Invalid app name: {app_name!r}") |
| 121 | if Path(app_name).name != app_name or "\\" in app_name: |
| 122 | raise ValueError(f"Invalid app name: {app_name!r}") |
| 123 | app_root = (agents_base_path / app_name).resolve() |
| 124 | if not app_root.is_relative_to(agents_base_path): |
| 125 | raise ValueError(f"Invalid app name: {app_name!r}") |
| 126 | return app_root |
| 127 | |
| 128 | def _normalize_relative_path(path: str) -> str: |
| 129 | return path.replace("\\", "/").lstrip("/") |
| 130 | |
| 131 | def _has_parent_reference(path: str) -> bool: |
| 132 | return any(part == ".." for part in path.split("/")) |
| 133 | |
| 134 | _ALLOWED_EXTENSIONS = frozenset({".yaml", ".yml"}) |
| 135 | |
| 136 | _BLOCKED_YAML_KEYS = frozenset({"args"}) |
| 137 | |
| 138 | def _check_yaml_for_blocked_keys(content: bytes, filename: str) -> None: |
| 139 | try: |
| 140 | docs = list(yaml.safe_load_all(content)) |
| 141 | except yaml.YAMLError as exc: |
| 142 | raise ValueError(f"Invalid YAML in {filename!r}: {exc}") from exc |
| 143 | |
| 144 | def _walk(node: Any) -> None: |
| 145 | if isinstance(node, dict): |
| 146 | for key, value in node.items(): |
| 147 | if key in _BLOCKED_YAML_KEYS: |
| 148 | raise ValueError( |
| 149 | f"Blocked key {key!r} found in {filename!r}. " |
| 150 | f"The '{key}' field is not allowed in builder uploads " |
| 151 | "because it can execute arbitrary code." |
| 152 | ) |
| 153 | _walk(value) |
| 154 | elif isinstance(node, list): |
| 155 | for item in node: |
| 156 | _walk(item) |
no test coverage detected