Wichtigste Erkenntnisse
- Ein privates PyPI-Repository gibt Aufschluss darüber, woher die Pakete stammen, nicht aber darüber, ob ihre Installation sicher ist.
- Das Hinzufügen von „–extra-index-url“ anstelle der Verweisung von „–index-url“ auf einen Index öffnet erneut die Sicherheitslücke, die durch das private Repository eigentlich geschlossen werden sollte.
- Ein Mirror oder Cache stellt alles bereit, was von der Upstream-Quelle veröffentlicht wurde – einschließlich bösartiger Versionen.
- Um ein installierbares Paket mit einer verifizierten Quelle zu verknüpfen, ist ein expliziter Verifizierungsschritt erforderlich, keine Konfigurationsänderung.
Ein privates PyPI-Repository einzurichten, scheint eine überschaubare Aufgabe zu sein. Man startet DevPI oder Artifactory, trägt die interne URL in die Onboarding-Dokumentation ein und schließt das Ticket. Kein Entwickler zieht mehr direkt aus dem öffentlichen PyPI. Ein Prüfer erhält eine Antwort. Der Albtraum ist vorbei.
Allerdings spiegelt das private Repository lediglich das öffentliche PyPI wider. Die ursprüngliche Frage ist nun beantwortet: Das Caching funktioniert, es gibt jedoch eine Richtlinie, die als Engpass wirkt. Ob ein bestimmtes Paket vertrauenswürdig ist, wurde jedoch nicht geklärt. Das Thema wurde lediglich verlagert.
Das ist der Punkt, den man sich vor der Wahl einer Architektur genau ansehen sollte. Die vier gängigen Ansätze für ein privates PyPI-Repository unterscheiden sich erheblich hinsichtlich der Art der Überprüfung, und bei den einfachen Spiegel- und Proxy-Modellen fehlt die Überprüfung gänzlich.
Was ein privates PyPI-Repository tatsächlich leistet
Vier Probleme, und die sind echt.
Verfügbarkeit, sodass ein Ausfall des öffentlichen PyPI Ihre Bereitstellungen nicht mit in den Absturz reißt. Bandbreite und Geschwindigkeit, denn es ist verschwenderisch, dieselben Wheels tausendmal am Tag aus dem Internet abzurufen. Interne Paketverteilung, damit private Bibliotheken einen anderen Speicherort haben als eine in der Datei „requirements.txt“ versteckte Git-URL. Und eine Richtlinien-Kontrollpunkt, der für die Sicherheit tatsächlich entscheidend ist.
Was damit allein nicht gelöst wird, ist die Paketintegrität. Ein Pull-Through-Cache ruft Daten vom Upstream ab, speichert das Ergebnis und stellt es bereit. Wenn der Upstream Malware drei Stunden lang Malware bereitgestellt hätte, bewahrt der Cache diese Malware originalgetreu auf und stellt sie anschließend weiterhin allen Nutzern mit hervorragender Latenz zur Verfügung.
Ein privates PyPI-Repository verändert lediglich die Quelle Ihrer Pakete. Es ändert nichts daran, was diese Pakete sind. Das Repository selbst als Sicherheitsmaßnahme zu betrachten, ist ein häufiger architektonischer Fehler in diesem Bereich.
Die Anzahl bösartiger Pakete, die in öffentlichen Registern landen, ist Jahr für Jahr gestiegen, und ein Spiegel, der diesem Wachstum nachgeschaltet ist, übernimmt naturgemäß alles. Der Spiegelmechanismus enthält keinerlei Filter, der die Absicht der Pakete prüft.
Der Fehler mit der „extra-index-url“, der alles zunichte macht
pip verfügt über zwei Flags, die auf den ersten Blick austauschbar erscheinen. Das sind sie jedoch nicht, und der Unterschied ist leicht zu übersehen – bis zu dem Tag, an dem jemand diese Lücke ausnutzt.
# Ersetzt den Standard-Index. Es wird ausschließlich Ihr Repository herangezogen.
pip install –index-url https://pypi.internal.example.com/simple/ mypackage
# Fügt einen zweiten Index hinzu. pip durchsucht beide, ohne dass einer von beiden Vorrang hat.
pip install –extra-index-url https://pypi.internal.example.com/simple/ mypackage
–extra-index-url taucht in der Regel auf, weil –index-url allein zunächst zu einem Fehler geführt hat. Ein Paket wurde nicht gefunden, der Build schlug fehl, und durch Hinzufügen eines zweiten Indexes wurde der Fehler behoben.
Und hier ist der Grund, warum das wichtig ist: pip hat keine Vorstellung davon, aus welchem Index ein Paket stammen soll. Die eigene Dokumentation sagt dies ganz offen: Es gibt keine Prioritäten bei den Suchorten, daher werden alle überprüft, und die beste Übereinstimmung mit der Anforderung gewinnt – was in der Praxis die höchste kompatible Version bedeutet. Wenn ein Angreifer die Version 99.0.0 von „internal-billing-utils“ im öffentlichen PyPI veröffentlicht, wird pip diese ohne zu zögern Ihrer internen Version 2.4.1 vorziehen.
Das ist Abhängigkeitsverwirrung, und über „–extra-index-url“ gelangt sie ins System.
Überprüfen Sie, wie Ihre Build-Agenten tatsächlich konfiguriert sind, und nicht, was in der Dokumentation steht. Die Umgebungsvariablen `PIP_INDEX_URL` und `PIP_EXTRA_INDEX_URL` überschreiben stillschweigend die `pip.conf`, und ein von einem anderen Team geerbtes Basis-Image ist eine gängige Methode, wie sich solche Einstellungen unbemerkt einschleichen können.
python -m pip config debug
pip config get global.index-url
Mit dem Befehl „pip config debug“ lässt sich anzeigen, aus welcher Datei oder Variable die jeweilige Einstellung stammt – das geht in der Regel schneller als das Raten. Überprüfen Sie dabei gleich auch die Umgebungsvariablen für beide PIP-Variablen.
Die Lösung besteht darin, ausschließlich –index-url zu verwenden und Ihr privates Repository als einziges Repository festzulegen, das pip kennt, wobei sowohl interne als auch Upstream-Pakete darüber bereitgestellt werden. PEP 708 schlug Repository-Metadaten vor, die es Indizes ermöglichen würden, diese Beziehungen zu signalisieren und das Rätselraten beim Zusammenführen zu beseitigen; doch nach drei Jahren im vorläufigen Status wurde der Vorschlag 2026 abgelehnt, da die erforderlichen Implementierungen nie umgesetzt wurden. Die Regel des einzigen Indexes gilt bis heute.
Vier Möglichkeiten, ein privates PyPI-Repository zu betreiben
Die Optionen unterscheiden sich danach, wie sie mit Upstream-Paketen umgehen, und nicht danach, welcher Produktname auf der Verpackung steht.
| Vorgehensweise | Vorgelagerte Abwicklung | Überprüft Artefakte | Betriebsaufwand |
| Ganzkörperspiegel | Kopiert den gesamten Inhalt von PyPI lokal | Nein | Hoch, der Lagerbestand wächst kontinuierlich |
| Pull-Through-Proxy | Abrufe und Zwischenspeicherung nach Bedarf | Nein | Gering bis mäßig |
| Kuratierte Zulassungsliste | Nur genehmigte Pakete werden aufgelöst | Nur das, was ein Mensch überprüft hat | Umfangreiche, laufende Überprüfung |
| Verifizierte verwaltete Registrierungsstelle (z. B. SecureChain) | Vor dem Servieren abseihen und neu zubereiten | Ja | Gering, ausgelagert |
Vollständige Spiegelung mit Bandersnatch ist die herkömmliche Lösung und bei weitem die speicherintensivste. Laut der Statistikseite von PyPIbeläuft sich der gesamte Bestand an Release-Dateien auf PyPI zum Zeitpunkt der Erstellung dieses Artikels auf etwa 44 TB, und diese Zahl wächst weiter. Der Speicherplatz ist dabei noch der einfache Teil – die eigentlichen Kosten entstehen durch die Aufrechterhaltung einer einwandfreien Synchronisation bei dieser Datenmenge.
Pull-Through-Proxys sind die gängige Wahl. devpi, proxpi, Nexus Repository, Artifactory, AWS CodeArtifact, Azure Artifacts, Google Artifact Registry: unterschiedliche Produkte, aber im Hintergrund dieselbe Architektur. Die Proxy- und Cache-Funktion löst die Probleme der Verfügbarkeit und des Cachings gut, trägt jedoch in keiner Weise dazu bei, die Vertrauenswürdigkeit eines vorgelagerten Artefakts festzustellen, da dies nie ihre Aufgabe war.
Kuratierte Zulassungslisten gehören zu den wirksamsten Kontrollmechanismen, die ein Team intern einsetzen kann, ohne eine vollständige Verifizierungs-Pipeline aufbauen zu müssen. Sie sind aber auch diejenigen, die unbemerkt versagen, weil die Überprüfungswarteschlange für jemanden zur unbeliebtesten Aufgabe wird und Genehmigungen schließlich in großen Mengen erteilt werden, um eine Veröffentlichung freizugeben.
Verifizierte verwaltete Registries basieren eher auf Verifizierung als auf Verfügbarkeit. SecureChain für Python funktioniert auf diese Weise. Pakete werden aus dem Quellcode neu kompiliert und geprüft, bevor sie zur Installation bereitgestellt werden – sie werden nicht erst im Nachhinein zwischengespeichert und später gescannt.
Bevor Sie sich für einen Ansatz entscheiden, führen Sie den Befehl „pip download -r requirements.txt“ aus und zählen Sie die verschiedenen Pakete, die dabei angezeigt werden – einschließlich der transitiven Pakete. Ermitteln Sie die Größe einer kuratierten Zulassungsliste im Vergleich zu Ihren direkten Abhängigkeiten allein kann den Überprüfungsaufwand erheblich unterschätzen, da der Umfang vor allem im transitiven Baum liegt.
Spiegelung ist keine Verifizierung
Es gibt drei unterschiedliche Fragen, die ein Python-Paketierungskonzept beantworten kann, und es ist wichtig, diese voneinander zu trennen, da bei Produkten die Grenzen oft verschwimmen.
Ist dieses Artefakt unbeschädigt angekommen? Hashes geben darauf eine Antwort, und pip unterstützt dies hervorragend.
Stammt dieses Artefakt tatsächlich von dem Ort, von dem es angeblich stammt? Die Herkunftsangabe und die Signatur geben darauf teilweise Aufschluss, sofern die Herausgeber daran mitwirken.
Stimmt dieses Artefakt mit dem Quellcode überein, aus dem es angeblich erstellt wurde? In der Standard-Toolchain gibt es so gut wie keine Hinweise darauf.
Die dritte Frage dient dazu, manipulierte Räder zu erkennen. Ein Wheel ist ein Build-Ergebnis, und im Paketsystem gibt es keine Vorschrift, dass es mit dem Repository übereinstimmen muss, auf das das Paket verweist. Eine Kompromittierung des Betreuerkontos kann zu einem Wheel führen, dessen Hash-Wert konsistent ist, das sich problemlos installieren lässt und Code enthält, der im Repository noch nie aufgetaucht ist.
pip-compile –generate-hashes requirements.in
pip install –require-hashes -r requirements.txt
Aktivieren Sie den Hash-Prüfmodus, unabhängig davon, was Sie sonst noch tun. Dadurch wird jede Abhängigkeit – einschließlich transitiver Abhängigkeiten – zwingend mit einem Hash fixiert, was eine stillschweigende Ersetzung verhindert und Installationen wiederholbar macht. Es gibt jedoch keine Gewissheit darüber, ob das fixierte Artefakt jemals legitim war. Es wird einfach das festgehalten, was beim ersten Mal aufgelöst wurde.
Die dritte Frage zu klären bedeutet, eine vertrauenswürdige Verbindung zwischen Quellcode und installierbarem Artefakt herzustellen. In der Praxis bedeutet dies, den Code aus verifiziertem Quellcode in einer kontrollierten Umgebung neu zu kompilieren und diesen Build anstelle einer undurchsichtigen Upstream-Binärdatei zu installieren. Das ist gezielte technische Arbeit und keine Konfigurationseinstellung.
„Trusted Publishing“ und was „durabletask“ aufgedeckt hat
Trusted Publishing ersetzt langlebige PyPI-API-Token durch kurzlebige Anmeldedaten, die über OIDC für einen bestimmten CI-Workflow ausgestellt werden. Das generierte Token läuft innerhalb von 15 Minuten ab, sodass keine langlebigen Anmeldedaten in einem Secret-Speicher liegen bleiben, die gestohlen werden könnten.
Im „durabletask“-SDK von Microsoft war diese Funktion nicht aktiviert, als im Rahmen der TeamPCP-Kampagne am 19. Mai 2026 drei bösartige Versionen auf PyPI . Die Versionen 1.4.1 bis 1.4.3 wurden innerhalb eines Zeitfensters von 35 Minuten veröffentlicht und direkt auf PyPI hochgeladen – mithilfe eines gestohlenen, langfristig gültigen API-Tokens, das sich in den Geheimnissen von GitHub Actions des Projekts befand. Keine passenden Tags, kein ausgeführter Veröffentlichungs-Workflow, nichts in der Repository-Historie, was erklären könnte, wie die Releases dorthin gelangten. Genau diese Art von Token-Diebstahl soll durch „Trusted Publishing“ verhindert werden.
Telnyx und LiteLLM hatten diese Lektion bereits etwa acht Wochen zuvor auf die harte Tour gelernt. Bei beiden wurden im März 2026 bösartige Releases auf PyPI hochgeladen, ohne dass es in ihren Repositories entsprechende Tags gab, und beide stellten anschließend auf „Trusted Publishing“ um, wie aus dem Vorfallbericht von PyPI selbst hervorgeht. durabletask hatte dies noch nicht umgesetzt, als TeamPCP im Mai erneut zuschlug. Zum Zeitpunkt der Erstellung dieses Artikels, basierend auf der aktuellsten Version 1.9.0 vom 30. Juli 2026, zeigt PyPI die Dateien weiterhin als ohne „Trusted Publishing“ veröffentlicht an.
Diese Lücke ist der aufschlussreichere Teil der Geschichte – mehr noch als jeder einzelne Vorfall für sich genommen. Die Lösung dafür gibt es schon seit Jahren. Zwei viel beachtete Sicherheitsvorfälle veranlassten zwei Projekte dazu, sie einzuführen. Wenige Wochen später gelang es derselben Kampagne, ein drittes Projekt mithilfe weiterer gestohlener Veröffentlichungszugangsdaten zu kompromittieren.
„Trusted Publishing“ ist ein wirklich wirksames Mittel gegen den Diebstahl von Anmeldedaten, weist jedoch zwei Einschränkungen auf, die klar und deutlich erwähnt werden sollten. Es schützt die von Ihnen veröffentlichten Pakete, nicht jedoch die von Ihnen genutzten. Wenn Sie diese Funktion also aktivieren, hat dies keinerlei Auswirkungen auf die Abhängigkeiten, die sich bereits in Ihrem Baum befinden. Außerdem hängt es davon ab, dass jeder Maintainer in der Upstream-Kette dieselbe Entscheidung trifft – worauf Sie sich jedoch nicht verlassen können.
| Kontrolle | Schützt | Bietet keinen Schutz |
| Trusted Publishing | Von Ihnen veröffentlichte Pakete | Alles, was Sie installieren |
| Hash-Pinning | Gegen eine Ersetzung nach der ersten Auflösung | Gegen einen Kompromiss beim ursprünglichen Vorsatz |
| Privates Repository | Kontrollierte Paketquelle, Verfügbarkeit, Richtlinien | Integrität der Artefakte |
| Screening auf Registrierungsebene | Artefakte, bevor sie abgearbeitet werden | Code, den du selbst geschrieben hast |
Falls Ihre Organisation Pakete veröffentlicht, aktivieren Sie diese Woche bitte „Trusted Publishing“. Es handelt sich um eine kleine Konfigurationsänderung, die einen ganzen Angriffsweg unterbindet.
Sicherheitsmaßnahmen auf der Client-Seite
Die Repository-Architektur steht im Mittelpunkt der Aufmerksamkeit. Die Durchsetzung erfolgt jedoch tatsächlich auf der Ebene der Client-Konfiguration, und diese ist in der Regel inkonsistent.
In jeder Umgebung, in der pip ausgeführt wird, sollten vier Punkte überprüft werden. Erstens: eine einzige Index-URL in einer pip.conf auf Systemebene, ohne dass irgendwo eine zusätzliche Index-URL angegeben ist. Zweitens: PIP_INDEX_URL und PIP_EXTRA_INDEX_URL dürfen in Basis-Images und der CI-Konfiguration nicht gesetzt sein. Drittens: Der Hash-Prüfmodus muss in der CI aktiviert sein, auch wenn Entwickler ihn lokal überspringen. Viertens sollte der ausgehende Netzwerkverkehr von Build-Agenten eingeschränkt sein, damit ein falsch konfigurierter Client das öffentliche PyPI überhaupt nicht erreichen kann.
Letzteres ist es, was eine Richtlinie in eine Durchsetzungsmaßnahme verwandelt. Konfigurationsabweichungen treten auf, Basis-Images werden vererbt, und immer gibt es jemanden, der ein Flag setzt, um eine Bereitstellung freizugeben. Eine Egress-Richtlinie, die es Build-Agenten nur erlaubt, auf das genehmigte private Repository zuzugreifen, fängt all das ab, ohne auf die Disziplin einzelner Personen angewiesen zu sein. Das Blockieren eines einzelnen Hostnamens reicht nicht aus, da der Index und der Datei-Host, auf den er verweist, voneinander getrennt sind.
Die Wahl eines Ansatzes
Passen Sie den Ansatz an das an, was tatsächlich gelöst werden soll, und nicht daran, was sich am schnellsten umsetzen lässt.
Ein Pull-Through-Proxy ist die richtige Wahl, wenn es um Verfügbarkeit und Caching geht. Es handelt sich um eine gute Infrastruktur, die jedoch in dem Moment ihren Nutzen verliert, in dem sie als Sicherheitsgrenze behandelt wird. Eine kuratierte Zulassungsliste fügt einen Richtlinien-Engpass hinzu, auf den ein Audit hinweisen kann – vorausgesetzt, es wird darauf geachtet, dass die Überprüfungswarteschlange nicht zu einem reinen Abnickverfahren verkommt. Eine vollständige Spiegelung rechtfertigt ihre Speicherkosten außerhalb von Air-Gapped-Umgebungen nur selten. Keine der drei Methoden garantiert von Natur aus die Integrität der Artefakte, und eine kuratierte Zulassungsliste bietet nur so viel Sicherheit, wie ihr Überprüfungsprozess tatsächlich gewährleistet.
Wenn es wirklich um die Integrität der Artefakte geht – und nicht nur um die Verfügbarkeit oder einen Prüfpfad –, benötigen Sie eine Verifizierungsschicht, die Pakete aus vertrauenswürdigen Quellen neu erstellt und diese vor der Auflösung überprüft. Eine verifizierte, verwaltete Registry bietet dies, ohne dass Sie die Pipeline selbst aufbauen und betreiben müssen – was intern eher ein mehrquartaliges Entwicklungsprojekt als ein Sprint ist.
Wo SecureChain für Python zum Einsatz kommt
Kehren wir zu den drei Fragen von vorhin zurück: „intact“ stimmt – wie behauptet – mit seiner Quelle überein. Die meisten Konfigurationen privater Repositorys hören bereits bei der ersten Frage auf. SecureChain wurde entwickelt, um alle drei Fragen zu beantworten. Jedes Paket wird in gehärteten Umgebungen aus dem Quellcode neu kompiliert und geprüft, bevor es zur Installation bereitgestellt wird – zusammen mit SLSA-Level-3-Herkunftsnachweis, SBOM- und VEX-Daten sowie Patches, die auch dann noch bereitgestellt werden, wenn ein Betreuer das Projekt verlässt.
Wenn Sie sehen möchten, wie sich das im Vergleich zu Ihrem eigenen Abhängigkeitsbaum verhält, fragen Sie unsere Experten nach SecureChain für Python und dem richtigen Ansatz für Ihren Stack.
FAQ
Was ist ein privates PyPI-Repository?
Ein intern gehosteter Python-Paketindex, der Ihren Entwicklern und Build-Systemen Pakete bereitstellt, anstatt sie direkt auf das öffentliche PyPI zu verweisen. Er kann Upstream-Inhalte spiegeln, bei Bedarf als Proxy fungieren und zwischenspeichern, nur eine genehmigte Auswahl bereitstellen oder Pakete vor der Bereitstellung überprüfen.
Macht ein privates PyPI-Repository die Installation sicherer?
Nur, wenn es Pakete überprüft. Ein Mirror oder ein Pull-Through-Cache stellt alles bereit, was der Upstream veröffentlicht hat – einschließlich bösartiger Versionen. Der Sicherheitsvorteil ergibt sich aus einer zusätzlich eingebauten Überprüfung oder Kuratierung, nicht aus dem Repository selbst.
Was ist der Unterschied zwischen „index-url“ und „extra-index-url“?
–index-url ersetzt den Standardindex, sodass ausschließlich Ihr Repository abgefragt wird. –extra-index-url fügt einen weiteren Index hinzu, und pip durchsucht alle konfigurierten Indizes, ohne sie zu bewerten. Auf diese Weise kann ein bösartiges öffentliches Paket die Auflösung für sich entscheiden und Ihnen einen „Dependency Confusion“-Angriff zufügen.
Wie kann ich die Verwirrung bei den Abhängigkeiten mit pip vermeiden?
Verwenden Sie ausschließlich –index-url, stellen Sie interne und Upstream-Pakete über diesen einzigen Index bereit, entfernen Sie überall den Eintrag PIP_EXTRA_INDEX_URL und beschränken Sie den ausgehenden Datenverkehr der Build-Agenten so, dass nur das genehmigte Repository erreichbar ist.
Sollte ich einen vollständigen PyPI-Mirror betreiben?
Selten. Ein vollständiger Spiegel bedeutet, dass etwa 44 TB an Daten mitgeführt werden müssen – Tendenz steigend –, und bietet gegenüber einem Pull-Through-Proxy keinen Vorteil hinsichtlich der Verifizierung. Eine vollständige Spiegelung ist vor allem in Air-Gapped-Umgebungen sinnvoll.
Was ist „Trusted Publishing“ auf PyPI?
Ein OIDC-basierter Veröffentlichungsmechanismus, der kurzlebige Anmeldedaten für einen bestimmten CI-Workflow ausstellt, anstatt auf langlebige API-Token zurückzugreifen. Er schützt die von Ihnen veröffentlichten Pakete. Bei den von Ihnen installierten Paketen greift er nicht ein.




