MCPcopy
hub / github.com/github/spec-kit / install_from_zip

Method install_from_zip

src/specify_cli/extensions/__init__.py:1468–1531  ·  view source on GitHub ↗

Install extension from ZIP file. Args: zip_path: Path to extension ZIP file speckit_version: Current spec-kit version priority: Resolution priority (lower = higher precedence, default 10) force: If True and extension is already installed, remo

(
        self,
        zip_path: Path,
        speckit_version: str,
        priority: int = 10,
        force: bool = False,
    )

Source from the content-addressed store, hash-verified

1466 return manifest
1467
1468 def install_from_zip(
1469 self,
1470 zip_path: Path,
1471 speckit_version: str,
1472 priority: int = 10,
1473 force: bool = False,
1474 ) -> ExtensionManifest:
1475 """Install extension from ZIP file.
1476
1477 Args:
1478 zip_path: Path to extension ZIP file
1479 speckit_version: Current spec-kit version
1480 priority: Resolution priority (lower = higher precedence, default 10)
1481 force: If True and extension is already installed, remove it first
1482 before proceeding with installation
1483
1484 Returns:
1485 Installed extension manifest
1486
1487 Raises:
1488 ValidationError: If manifest is invalid or priority is invalid
1489 CompatibilityError: If extension is incompatible
1490 """
1491 # Validate priority early
1492 if priority < 1:
1493 raise ValidationError("Priority must be a positive integer (1 or higher)")
1494
1495 with tempfile.TemporaryDirectory() as tmpdir:
1496 temp_path = Path(tmpdir)
1497
1498 # Extract ZIP safely (prevent Zip Slip attack)
1499 with zipfile.ZipFile(zip_path, "r") as zf:
1500 # Validate all paths first before extracting anything
1501 temp_path_resolved = temp_path.resolve()
1502 for member in zf.namelist():
1503 member_path = (temp_path / member).resolve()
1504 # Use is_relative_to for safe path containment check
1505 try:
1506 member_path.relative_to(temp_path_resolved)
1507 except ValueError:
1508 raise ValidationError(
1509 f"Unsafe path in ZIP archive: {member} (potential path traversal)"
1510 )
1511 # Only extract after all paths are validated
1512 zf.extractall(temp_path)
1513
1514 # Find extension directory (may be nested)
1515 extension_dir = temp_path
1516 manifest_path = extension_dir / "extension.yml"
1517
1518 # Check if manifest is in a subdirectory
1519 if not manifest_path.exists():
1520 subdirs = [d for d in temp_path.iterdir() if d.is_dir()]
1521 if len(subdirs) == 1:
1522 extension_dir = subdirs[0]
1523 manifest_path = extension_dir / "extension.yml"
1524
1525 if not manifest_path.exists():

Callers 3

extension_addFunction · 0.95
extension_updateFunction · 0.95

Calls 3

ValidationErrorClass · 0.85
resolveMethod · 0.45

Tested by 1