Skip to content

Harden extension add --from flow and align --dev/--from semantics#2640

Draft
Copilot wants to merge 3 commits into
mainfrom
copilot/fix-comments-in-review-thread
Draft

Harden extension add --from flow and align --dev/--from semantics#2640
Copilot wants to merge 3 commits into
mainfrom
copilot/fix-comments-in-review-thread

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 20, 2026

This PR addresses all actionable comments in review thread pullrequestreview-4301684579 for PR #2582. The goal is to keep the path-traversal hardening while removing regressions in CLI behavior and URL-install handling.

  • CLI option semantics

    • Rejects specify extension add ... --dev --from ... as mutually exclusive instead of letting --from side effects run on a code path where --dev takes precedence.
  • URL install path hardening

    • Removes pathname-based ZIP staging for --from installs.
    • Downloads the archive into memory and installs from bytes, avoiding cache-path creation/cleanup races and path-based TOCTOU windows in this flow.
  • Extension manager API refinement

    • Adds ExtensionManager.install_from_zip_bytes(...).
    • Refactors install_from_zip(path) into a thin file-read wrapper delegating to the bytes-based implementation.
    • Keeps ZIP extraction safeguards and adds clearer doc/error handling around ZIP file reads.
  • Focused regression coverage

    • Adds tests for:
      • --dev + --from rejection.
      • --from using in-memory ZIP bytes install path.
# before: path-based staging + install
with _open_url(from_url, timeout=60) as response:
    zip_data = response.read()
zip_path.write_bytes(zip_data)
manifest = manager.install_from_zip(zip_path, speckit_version, priority=priority)

# after: in-memory install
with _open_url(from_url, timeout=60) as response:
    zip_bytes = response.read()
manifest = manager.install_from_zip_bytes(zip_bytes, speckit_version, priority=priority)

Copilot AI requested review from Copilot and removed request for Copilot May 20, 2026 10:47
Copilot AI requested review from Copilot and removed request for Copilot May 20, 2026 10:51
Copilot AI requested review from Copilot and removed request for Copilot May 20, 2026 10:55
Copilot AI changed the title [WIP] Fix code based on review comments Harden extension add --from flow and align --dev/--from semantics May 20, 2026
Copilot finished work on behalf of mnriem May 20, 2026 10:56
Copilot AI requested a review from mnriem May 20, 2026 10:56
@mnriem mnriem requested a review from Copilot May 21, 2026 01:26
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the specify extension add --from <url> installation path while aligning CLI option semantics so --dev and --from are treated as mutually exclusive, and refactors ExtensionManager to support ZIP installs from in-memory bytes.

Changes:

  • Reject specify extension add ... --dev --from ... as an invalid flag combination.
  • Add ExtensionManager.install_from_zip_bytes(...) and route --from URL installs through the bytes-based path (no temp ZIP staging file).
  • Add targeted CLI tests covering the --dev+--from rejection and ensuring --from installs call the bytes-based install API.
Show a summary per file
File Description
tests/test_extensions.py Adds CLI regression tests for --dev/--from exclusivity and for using the in-memory ZIP install path.
src/specify_cli/extensions.py Introduces install_from_zip_bytes(...) and refactors install_from_zip(...) to delegate via bytes.
src/specify_cli/__init__.py Enforces --dev/--from mutual exclusion and switches URL installs to call install_from_zip_bytes(...).

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 3/3 changed files
  • Comments generated: 2

Comment on lines +1228 to +1232
try:
with zip_path.open("rb") as zip_file:
return self.install_from_zip_bytes(zip_file.read(), speckit_version, priority=priority)
except OSError as e:
raise ValidationError(f"Failed to read ZIP file {zip_path}: {e}") from e
Comment on lines 3649 to +3655
from specify_cli.authentication.http import open_url as _open_url

with _open_url(from_url, timeout=60) as response:
zip_data = response.read()
zip_path.write_bytes(zip_data)
zip_bytes = response.read()

# Install from downloaded ZIP
manifest = manager.install_from_zip(zip_path, speckit_version, priority=priority)
manifest = manager.install_from_zip_bytes(zip_bytes, speckit_version, priority=priority)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants