Skip to content

Route Python packages through Azure Artifacts#8576

Merged
StellaHuang95 merged 3 commits into
mainfrom
cfsIssue
Jul 14, 2026
Merged

Route Python packages through Azure Artifacts#8576
StellaHuang95 merged 3 commits into
mainfrom
cfsIssue

Conversation

@StellaHuang95

@StellaHuang95 StellaHuang95 commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Route all pipeline Python package downloads through the team-owned DevDiv/Pylance_PublicPackages Azure Artifacts feed to resolve the SFI-ES4.2.4 public PyPI violation.

  • authenticate the build and Glass test jobs with PipAuthenticate@1
  • replace direct pypi.org JSON and files.pythonhosted.org requests with the authenticated PEP 503 feed
  • preserve the existing merged-wheel installation for debugpy and etwtrace
  • reject extra/public indexes and public PyPI redirects
  • validate wheel names and verify SHA-256 hashes when supplied
  • support both Azure Artifacts hostname formats emitted by local tooling and hosted agents

Validation

  • PTVS-Build run 26195.3 succeeded
  • debugpy 1.8.21 and etwtrace 0.1b9 installed through Pylance_PublicPackages
  • product, tests, installer, bootstrapper, signing, SBoM, security scans, and artifact publication succeeded
  • CodeQL and SonarCloud passed

@StellaHuang95

Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
1 pipeline(s) were filtered out due to trigger conditions.

cache_path = os.path.join(cache_dir, filename)
except Exception:
cache_path = None
os.makedirs(cache_dir, exist_ok=True)
@StellaHuang95

Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
1 pipeline(s) were filtered out due to trigger conditions.

PipAuthenticate@1 on 1ES hosted agents emits PIP_INDEX_URL using the
legacy `<org>.pkgs.visualstudio.com` host, while local runs and other
agents use the canonical `pkgs.dev.azure.com` host. Both are the same
Azure Artifacts service under different DNS names and both are already
in the redirect denylist for public PyPI, so accept either as a valid
index host and validate every subsequent request against the same rule.

Verified end-to-end against both hosts and confirmed a bogus host is
still rejected.

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

Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
1 pipeline(s) were filtered out due to trigger conditions.

@StellaHuang95

Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
1 pipeline(s) were filtered out due to trigger conditions.

@StellaHuang95

Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
1 pipeline(s) were filtered out due to trigger conditions.

@StellaHuang95 StellaHuang95 reopened this Jul 14, 2026
@StellaHuang95 StellaHuang95 changed the title fix cfs issue Route Python packages through Azure Artifacts Jul 14, 2026
@StellaHuang95 StellaHuang95 marked this pull request as ready for review July 14, 2026 17:50
@StellaHuang95 StellaHuang95 requested a review from a team as a code owner July 14, 2026 17:50
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

1 similar comment
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@rchiodo

rchiodo commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

🔒 Automated review in progress — @rchiodo is auto-reviewing this PR.

@heejaechang

Copy link
Copy Markdown

🔒 Automated review in progress — @heejaechang is auto-reviewing this PR.

@StellaHuang95

Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@sonarqubecloud

Copy link
Copy Markdown

f.write(data)

fragment = urllib.parse.parse_qs(urllib.parse.urlsplit(url).fragment)
expected_hashes = fragment.get("sha256")

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.

The optional wheel cache lost all of its error handling. Previously os.makedirs, the cache read, and the cache write were each wrapped in try/except, so a broken or opt-in PTVS_PYPI_WHEEL_CACHE_DIR (read-only mount, permission error, full disk, concurrent job) could never fail the install. Now any of those conditions will hard-fail the entire build, turning an optional accelerator into a load-bearing dependency. Restore best-effort semantics (degrade to a direct download on cache failure), or explicitly document that the cache is mandatory-when-configured.

with open(cache_path, "wb") as f:
f.write(data)

fragment = urllib.parse.parse_qs(urllib.parse.urlsplit(url).fragment)

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.

The cache write is non-atomic and happens before SHA-256 verification. A truncated/corrupt download gets persisted to cache_path before the hash check runs, so every subsequent run reads the poisoned file, re-verifies, and fails again — a persistent build break until the cache is manually purged. With the shared-cache parallelism this feed change enables, a partial f.write() can also be read by another job. Verify the SHA-256 on the downloaded bytes before writing, and write via a temp file + os.replace atomic rename.

wheel_name, version, _, _ = parse_wheel_filename(filename)
if canonicalize_name(wheel_name) != expected_name:
continue

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.

parse_wheel_filename can raise InvalidWheelFilename (or InvalidVersion on a non-PEP440 version), and neither is caught in get_package_data. A single malformed .whl-suffixed link on the feed's simple-index page for the requested package would abort all builds — a hard-failure the old JSON path didn't have. Wrap the call in try/except (InvalidWheelFilename, InvalidVersion): continue to skip unparseable links.



class _SfiRedirectHandler(url_lib.HTTPRedirectHandler):
# Azure Artifacts responds with a 303 pointing at a SAS-signed blob URL;

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.

The _SfiRedirectHandler comment claims it refuses "any redirect that leaves Microsoft-owned infrastructure," but the implementation only raises for the 5-host FORBIDDEN_HOSTS deny-list — a redirect to any other non-forbidden host would still be followed. (The Basic credential is correctly not resent, so no secret leaks.) Update the comment to describe the actual deny-list behavior so a future maintainer doesn't 'tighten' it based on the misleading description and break legitimate *.blob.core.windows.net redirects.

@rchiodo

rchiodo commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Solid, security-focused change that meets the SFI goal. A few robustness edge cases around the optional wheel cache and feed-link parsing are worth tightening before merge, but none are blocking given the passing pipeline validation.

@rchiodo rchiodo 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.

Approved via Review Center.

@heejaechang heejaechang left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approved via Review Center.

@StellaHuang95 StellaHuang95 merged commit f03ffe2 into main Jul 14, 2026
11 checks passed
@StellaHuang95 StellaHuang95 deleted the cfsIssue branch July 14, 2026 19:12
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.

4 participants