Skip to content

Harden Artifactory zip extraction#1948

Merged
danielmeppiel merged 7 commits into
microsoft:mainfrom
fallintoplace:fix/artifactory-safe-zip-extraction
Jul 4, 2026
Merged

Harden Artifactory zip extraction#1948
danielmeppiel merged 7 commits into
microsoft:mainfrom
fallintoplace:fix/artifactory-safe-zip-extraction

Conversation

@fallintoplace

@fallintoplace fallintoplace commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Summary

  • route Artifactory archive extraction through the shared safe ZIP helper
  • preserve root-prefix stripping while rejecting unsafe entries fail-closed
  • add regression coverage for traversal and uncompressed-size limits
  • update the hermetic Artifactory e2e stub to mirror production extraction

Why

Single-file Artifactory ZIP downloads still used ZipFile.extractall(), which bypassed the shared archive guards. A crafted archive could use path traversal to write outside the target directory.

Validation

  • uv run --extra dev pytest tests/unit/utils/test_archive.py tests/unit/deps/test_download_strategies_phase3.py tests/unit/deps/test_download_strategies_selection.py tests/unit/test_artifactory_support.py -q
  • uv run --extra dev pytest tests/integration/test_artifactory_install_e2e.py -q
  • uv run --extra dev ruff check src/apm_cli/utils/archive.py src/apm_cli/deps/download_strategies.py tests/integration/test_artifactory_install_e2e.py tests/unit/deps/test_download_strategies_phase3.py tests/unit/deps/test_download_strategies_selection.py tests/unit/test_artifactory_support.py

apm-spec-waiver: Internal archive extraction security hardening does not extend the OpenAPM package specification.

Copilot AI review requested due to automatic review settings June 28, 2026 20:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 Artifactory VCS ZIP extraction by routing both multi-entry and single-file archives through the shared safe_extract_zip() helper, preserving root-prefix stripping while failing closed on unsafe entries (e.g., path traversal) and enforcing the shared uncompressed-size limits.

Changes:

  • Added a member_name_transform hook to safe_extract_zip() to support root-prefix stripping while keeping centralized ZIP safety checks.
  • Refactored Artifactory archive extraction in DownloadDelegate to use the shared safe extractor and surface unsafe-archive errors consistently.
  • Added/updated unit + hermetic integration coverage for traversal rejection and uncompressed-size limit enforcement.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/apm_cli/utils/archive.py Extends safe_extract_zip() with a member-name transform hook so callers can strip prefixes without bypassing safety checks.
src/apm_cli/deps/download_strategies.py Introduces _extract_artifactory_zip() and routes Artifactory ZIP extraction through safe_extract_zip() with fail-closed handling for unsafe members.
tests/unit/test_artifactory_support.py Updates Artifactory edge-case tests to assert traversal is rejected and adds a regression for uncompressed-size limits.
tests/unit/deps/test_download_strategies_selection.py Adds unit coverage for single-file traversal rejection and uncompressed-size limit failures in Artifactory downloads.
tests/unit/deps/test_download_strategies_phase3.py Mirrors the selection test coverage for the phase3 suite (same new Artifactory ZIP safety regressions).
tests/integration/test_artifactory_install_e2e.py Updates the hermetic Artifactory stub extraction logic to use safe_extract_zip() and match production behavior.
Comments suppressed due to low confidence (1)

src/apm_cli/utils/archive.py:201

  • safe_extract_zip gained member_name_transform, but the docstring does not describe what the hook does or that the returned extracted list now contains transformed names. This makes the new API harder to use correctly.
    """Safely stream-extract *zf* under *dest_root* with zip-bomb limits.

    The uncompressed-size limit is enforced against bytes actually read from
    each entry, not against attacker-controlled ZipInfo.file_size metadata.
    """

Comment thread src/apm_cli/deps/download_strategies.py Outdated
headers["Authorization"] = f"Bearer {self._host.artifactory_token}"
return headers

def _extract_artifactory_zip(self, zf, target_path: Path, url: str) -> None:
Comment on lines +1271 to +1272
target = self.temp_dir / "pkg"
monkeypatch.setitem(safe_extract_zip.__kwdefaults__, "max_uncompressed", 1024)
Comment on lines +670 to +671
d, _ = self._delegate([resp])
monkeypatch.setitem(safe_extract_zip.__kwdefaults__, "max_uncompressed", 1024)
Comment on lines +667 to +668
d, _ = self._delegate([resp])
monkeypatch.setitem(safe_extract_zip.__kwdefaults__, "max_uncompressed", 1024)
@danielmeppiel danielmeppiel requested a review from nadav-y June 28, 2026 21:09
@danielmeppiel danielmeppiel added the panel-review Trigger the apm-review-panel gh-aw workflow label Jun 30, 2026
danielmeppiel added a commit to fallintoplace/apm that referenced this pull request Jul 4, 2026
Adds the required integration regression for malicious Artifactory ZIP traversal through the real downloader path, records the security change in the changelog, and preserves response-close failures in debug diagnostics. Addresses shepherd panel follow-ups for PR microsoft#1948.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the required integration regression for malicious Artifactory ZIP traversal through the real downloader path, records the security change in the changelog, and preserves response-close failures in debug diagnostics. Addresses shepherd panel follow-ups for PR microsoft#1948.

apm-spec-waiver: Internal archive extraction security hardening does not extend the OpenAPM package specification.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@danielmeppiel danielmeppiel force-pushed the fix/artifactory-safe-zip-extraction branch from 72cf249 to 40c1704 Compare July 4, 2026 20:47
danielmeppiel and others added 3 commits July 4, 2026 22:49
Empty commit to retrigger upstream pull_request checks after the maintainer force-with-lease update. No code changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Documents that the malicious-zip integration regression uses the production DownloadDelegate against local HTTP, keeping the test's coverage tier clear for reviewers.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@danielmeppiel

Copy link
Copy Markdown
Collaborator

APM Review Panel: ship_now

The Artifactory ZIP hardening now routes extraction through the shared archive safety chokepoint, has an end-to-end integration regression trap, and is green on fresh CI at 1e3da9759.

cc @fallintoplace @sergio-sisternes-epam @nadav-y -- a fresh advisory pass is ready for your review.

The final panel pass weighs this as ready for maintainer review. The in-scope security surface is covered: traversal, zip-bomb, and real Artifactory install/extract behavior now fail closed through safe_extract_zip. The earlier follow-ups were folded: close failures now get debug visibility, the transform return semantics are documented, the changelog records the security hardening, and the integration suite empirically proves the malicious archive is rejected.

Aligned with: secure-by-default installs; regression traps on user-visible package manager behavior; pragmatic OSS maintenance.

Panel summary

Persona B R N Takeaway
Python Architect 0 0 0 The final shape keeps Artifactory extraction behind the shared archive utility without adding a parallel extraction policy.
CLI Logging Expert 0 0 0 No user-facing CLI output changes; debug-only close diagnostics are appropriately quiet.
DevX UX Expert 0 0 0 Install failure remains fail-closed and localized to archive validation errors.
Supply Chain Security Expert 0 0 0 Zip-slip and unsafe extraction risk are closed on the Artifactory path; no remaining in-scope security reservations.
OSS Growth Hacker 0 0 0 Security hardening is recorded in the changelog with no extra user burden.
Doc Writer 0 0 0 Changelog and docstring updates are accurate and concise.
Test Coverage Expert 0 0 0 The required integration regression trap exists and mutation-proves the guard.
Performance Expert 0 0 0 No new hot-path concern from the folded changes.

B = blocking-severity findings, R = recommended, N = nits. Counts are signal strength, not gates. The maintainer ships.

Recommendation

Ship now from the advisory panel lens. The remaining note about buffering Artifactory archives is broader performance work and is not part of this ZIP safety fix.

Reservations carried from strategic-alignment

  • Supply-chain-security: zip-slip fully closed through safe_extract_zip; close-failure debug logging and returned-name semantics were verified/addressed by 40c170496.
  • Test-coverage: regression traps are present; the new integration test proves the real Artifactory install/extract path rejects traversal archives.

Folded in this run

  • (panel) Added integration coverage for malicious Artifactory ZIP rejection through the real downloader/extract path -- resolved in 40c170496.
  • (panel) Added mutation-break proof for the extraction guard -- resolved in 40c170496.
  • (panel) Added debug logging for response close failures instead of silently suppressing them -- resolved in 40c170496.
  • (panel) Documented member_name_transform and returned extracted-name semantics -- resolved in 40c170496.
  • (panel) Added the security changelog entry -- resolved in 40c170496.
  • (copilot) Clarified the safe extraction transform docstring signal -- resolved in 40c170496.

Copilot signals reviewed

  • src/apm_cli/utils/archive.py -- LEGIT: low-risk doc clarity request for member_name_transform; folded in 40c170496.

Deferred (out-of-scope follow-ups)

These items were surfaced by the panel but cross the stated scope of this PR. Each one suggests a separate follow-up.

  • (panel) Avoid buffering Artifactory archives before ZIP validation -- scope boundary: changes download/cache performance architecture beyond the ZIP traversal hardening in this PR.

Regression-trap evidence (mutation-break gate)

  • tests/integration/test_artifactory_install_e2e.py::test_malicious_zip_traversal_rejected_through_real_downloader -- deleted _zip_member_target(...) guard enforcement in safe_extract_zip; test FAILED as expected; guard restored.

Lint contract

uv run --extra dev ruff check src/ tests/ and
uv run --extra dev ruff format --check src/ tests/ both passed. Full local lint mirror also passed: ruff check, ruff format --check, pylint R0801, and scripts/lint-auth-signals.sh.

CI

Fresh GitHub checks are green on 1e3da97592c52a594e7b62dfd1e3ab52d7a2df66: CI, Merge Gate, Spec conformance, NOTICE Drift Check, CodeQL, and CLA all pass. Local integration evidence: uv run --extra dev pytest tests/integration -q -> 10114 passed, 242 skipped, 16 deselected, 2 xfailed in 270.33s (after 1 CI fix iteration).

Mergeability status

Captured from gh pr view 1948 --json mergeable,mergeStateStatus,statusCheckRollup immediately after the last push of this run.

PR head SHA CEO stance iters folds defers Copilot rounds CI mergeable mergeStateStatus notes
#1948 1e3da97 ship_now 2 6 1 1 green MERGEABLE BLOCKED pending required review

Convergence

2 outer iteration(s); 1 Copilot round(s). Final panel stance: ship_now.

Ready for maintainer review.


Full per-persona findings

Python Architect

No findings.

CLI Logging Expert

No findings.

DevX UX Expert

No findings.

Supply Chain Security Expert

No findings.

OSS Growth Hacker

No findings.

Auth Expert -- inactive

No authentication or token-resolution surface changed.

Doc Writer

No findings.

Test Coverage Expert

No findings.

Performance Expert

  • [recommended] Artifactory archive buffering remains broader performance work outside this PR's safety scope.
    The PR is about closing unsafe ZIP extraction; changing transport/cache architecture should be handled separately.

This panel is advisory. Re-apply the panel-review label after addressing feedback to re-run.

@danielmeppiel danielmeppiel removed the panel-review Trigger the apm-review-panel gh-aw workflow label Jul 4, 2026
@danielmeppiel danielmeppiel merged commit 5596551 into microsoft:main Jul 4, 2026
14 checks passed
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