PEP 708 – Erweiterung der Repository-API zur Abmilderung von Dependency Confusion-Angriffen
- Autor:
- Donald Stufft <donald at stufft.io>
- PEP-Delegate:
- Paul Moore <p.f.moore at gmail.com>
- Discussions-To:
- Discourse thread
- Status:
- Vorläufig
- Typ:
- Standards Track
- Thema:
- Packaging
- Erstellt:
- 20-Feb-2023
- Post-History:
- 01-Feb-2023, 23-Feb-2023
- Resolution:
- Discourse-Nachricht
Inhaltsverzeichnis
- Vorläufige Annahme
- Zusammenfassung
- Motivation
- Begründung
- Spezifikation
- Empfehlungen
- Wie kommuniziert man das?
- Abgelehnte Ideen
- Implizites Zulassen von Spiegelungen, wenn die Dateilisten identisch sind
- Bereitstellung eines Mechanismus zur Reihenfolge der Repositories
- Verlassen auf Repository-Proxys
- Verlassen nur auf Hash-Prüfung
- Verlangen, dass alle Projekte im „Standard“-Repository existieren
- Umbenennung in global eindeutige Namen
- Nur Empfehlung, dass Installer explizite Konfiguration anbieten
- Scopes à la npm
- Definition und Standardisierung der „Expliziten Konfiguration“
- Danksagungen
- Urheberrecht
Vorläufige Annahme
Diese PEP wurde **vorläufig angenommen**, mit den folgenden erforderlichen Bedingungen, bevor die PEP finalisiert wird.
- Eine Implementierung der PEP in PyPI (Warehouse), einschließlich aller notwendigen Benutzeroberflächenelemente, damit Projektbesitzer die Tracking-Daten festlegen können.
- Eine Implementierung der PEP in mindestens einem anderen Repository als PyPI, da man das Zusammenführen von Indizes ohne mindestens zwei Indizes nicht wirklich testen kann.
- Eine Implementierung der PEP in pip, die die beabsichtigte Semantik unterstützt und verwendet werden kann, um zu demonstrieren, dass die erwarteten Sicherheitsvorteile erzielt werden. Diese Implementierung muss zunächst „standardmäßig deaktiviert“ sein, was bedeutet, dass Benutzer sie explizit aktivieren müssen. Idealerweise sollten wir explizite positive Berichte von Benutzern (sowohl Projektbesitzern als auch Projektbenutzern) sammeln, die die neue Funktion erfolgreich ausprobiert haben, anstatt nur anzunehmen, dass „keine Neuigkeiten gute Nachrichten sind“.
Zusammenfassung
Dependency Confusion-Angriffe, bei denen ein bösartiges Paket anstelle des erwarteten Pakets installiert wird, sind eine zunehmend häufige Bedrohung der Lieferkette. Die meisten solchen Angriffe auf Python-Abhängigkeiten, einschließlich des jüngsten PyTorch-Vorfalls, treten bei mehreren Paket-Repositories auf, wobei eine von einem Repository erwartete Abhängigkeit (z. B. ein benutzerdefinierter Index) von einem anderen (z. B. PyPI) installiert wird.
Um dieses Problem zu lösen, schlägt diese PEP eine Erweiterung der Simple Repository API vor, die es Repository-Betreibern ermöglicht, anzugeben, dass ein auf ihrem Repository gefundenes Projekt ein Projekt auf anderen Repositories „verfolgt“ und Projekten ermöglicht, ihre Namensräume über mehrere Repositories hinweg zu erweitern.
Diese Funktionen werden es Installern ermöglichen, zu bestimmen, wann ein Projekt, das aus einer bestimmten Mischung von Repositories verfügbar gemacht wird, erwartet und zugelassen werden sollte und wann nicht, und die Installation mit einem Fehler stoppen, um den Benutzer zu schützen.
Motivation
Es gibt eine langjährige Klasse von Angriffen, die als „Dependency Confusion“-Angriffe bezeichnet werden, die grob gesagt darauf hinauslaufen, dass ein einzelner Benutzer erwartet, Paket A zu erhalten, aber stattdessen B erhalten hat. In Python geschieht dies fast immer aufgrund der Konfiguration mehrerer Repositories (möglicherweise einschließlich des Standard-PyPI), wobei erwartet wurde, dass Paket A aus Repository X stammt, aber jemand in der Lage ist, Paket B unter demselben Namen in Repository Y zu veröffentlichen.
Dependency Confusion-Angriffe waren schon lange möglich, aber sie haben kürzlich mit öffentlichen Beispielen für erfolgreich durchgeführte Angriffe für Aufsehen gesorgt.
Ein spezifisches Beispiel hierfür ist der jüngste Fall, in dem das PyTorch-Projekt ein internes Paket namens torchtriton hatte, das nur von seinen Repositories unter https://download.pytorch.org/ installiert werden sollte, aber dieses Repository war für die Verwendung in Verbindung mit PyPI konzipiert und der Name torchtriton wurde auf PyPI nicht beansprucht, was es einem Angreifer ermöglichte, diesen Namen zu verwenden und eine bösartige Version zu veröffentlichen.
Es gibt heute eine Reihe von Möglichkeiten, diese Angriffe zu mildern, aber sie erfordern alle, dass der Endbenutzer sich mehr Mühe gibt, sich selbst zu schützen, anstatt standardmäßig geschützt zu sein. Das bedeutet, dass für die große Mehrheit der Benutzer wahrscheinlich weiterhin anfällig sein werden, selbst wenn sie sich dieser Arten von Angriffen letztendlich bewusst sind.
Letztendlich ergeben sich die Ursachen für diese Angriffe aus der Tatsache, dass es keinen global eindeutigen Namensraum gibt, aus dem alle Python-Paketnamen stammen. Stattdessen ist jedes Repository sein eigener, separater Namensraum, und wenn ein Installer einen „abstrakten“ Namen wie spam zur Installation erhält, muss er diesen implizit in einen „konkreten“ Namen wie pypi.org:spam oder example.com:spam umwandeln. Derzeit ist das Standardverhalten von Python-Installationstools, diese mehreren Namensräume implizit zu einem zusammenzufassen, der die Dateien aus allen Namensräumen enthält.
Diese Annahme, dass das Zusammenführen der Namensräume erwartet wurde, bedeutet, dass, wenn Pakete mit demselben Namen in verschiedenen Repositories von verschiedenen Parteien erstellt werden (wie im Fall torchtriton), Dependency Confusion-Angriffe möglich werden.
Dies ist besonders knifflig, da es keine „richtige“ Antwort gibt; es gibt legitime Anwendungsfälle sowohl dafür, zwei Repositories in einem Namensraum zusammenzuführen, als auch dafür, zwei Repositories als separate Namensräume zu behandeln. Dies bedeutet, dass ein Installer einen Mechanismus benötigt, um zu bestimmen, wann er die Namensräume mehrerer Repositories zusammenführen und wann nicht, anstatt eine pauschale Regel für immer zusammenführen oder nie zusammenführen.
Diese Funktionalität könnte direkt an den Endbenutzer weitergegeben werden, da letztendlich der Endbenutzer die Person ist, deren Erwartungen darüber, was aus welchem Repository installiert wird, tatsächlich wichtig sind. Durch die Erweiterung der Repository-Spezifikation, damit ein Repository angeben kann, wann es sicher ist, können wir jedoch einzelne Projekte und Repositories „standardmäßig funktionieren lassen“, auch wenn ihr Projekt natürlich mehrere separate Namensräume überspannt, während die Möglichkeit für einen Installer, standardmäßig sicher zu sein, erhalten bleibt.
Für sich allein löst diese PEP keine Dependency Confusion-Angriffe, aber sie liefert genügend Informationen, damit Installer diese verhindern können, ohne zu viele Kollateralschäden an ansonsten gültigen und sicheren Anwendungsfällen zu verursachen.
Begründung
Es gibt zwei breite Anwendungsfälle für die Zusammenführung von Namen über Repositories hinweg, die diese PEP ermöglichen soll.
Der erste Anwendungsfall ist, wenn ein Repository keine eigenen Namen definiert, sondern vielmehr Namen erweitert, die in anderen Repositories definiert sind. Dies geschieht häufig in Fällen, in denen ein Projekt von einem Repository in ein anderes gespiegelt wird (siehe Bandersnatch) oder wenn ein Repository ergänzende Artefakte für eine bestimmte Plattform bereitstellt (siehe Piwheels).
In diesem Fall wissen weder die Repositories noch die Projekte, die erweitert werden, möglicherweise, dass sie erweitert werden oder von wem, sodass dies nicht auf Informationen angewiesen sein kann, die nicht im „erweiternden“ Repository selbst vorhanden sind.
Der zweite Anwendungsfall ist, wenn das Projekt in einem „Haupt“-Repository veröffentlichen möchte, aber dann zusätzliche Repositories hat, die Binärdateien für zusätzliche Plattformen, GPUs, CPUs usw. bereitstellen. Derzeit sind Wheel-Tags nicht ausreichend in der Lage, diese Arten von binärer Kompatibilität auszudrücken, sodass Projekte, die sich darauf verlassen möchten, gezwungen sind, mehrere Repositories einzurichten und ihre Benutzer manuell zu konfigurieren, um die richtigen Binärdateien für ihre Plattform, GPU, CPU usw. zu erhalten.
Dieser Anwendungsfall ähnelt dem ersten, aber der wichtige Unterschied, der ihn zu einem eigenständigen Anwendungsfall macht, ist, wer die Informationen bereitstellt und wie sein Vertrauensniveau ist.
Wenn ein Benutzer ein bestimmtes Repository konfiguriert (oder sich auf das Standard verlässt), besteht keine Mehrdeutigkeit darüber, welches Repository er meint. Ein Repository wird durch eine URL identifiziert, und über das Domain-System sind URLs global eindeutige Identifikatoren. Dieser Mangel an Mehrdeutigkeit bedeutet, dass ein Installer davon ausgehen kann, dass der Repository-Betreiber vertrauenswürdig ist, und Metadaten, die er bereitstellt, ohne sie validieren zu müssen, vertrauen kann.
Auf der anderen Seite ist es, wenn ein Installer einen Namen in mehreren Repositories findet, unklar, welchem davon der Installer vertrauen sollte. Diese Mehrdeutigkeit bedeutet, dass ein Installer nicht davon ausgehen kann, dass der Projektbesitzer in beiden Repositories vertrauenswürdig ist, und validieren muss, dass es sich tatsächlich um dasselbe Projekt handelt und dass eines kein Dependency Confusion-Angriff ist.
Ohne eine Möglichkeit für den Installer, die Metadaten zwischen mehreren Repositories zu validieren, wären Projekte gezwungen, zu Repository-Betreibern zu werden, um diesen Anwendungsfall sicher zu unterstützen. Das wäre keine besonders falsche Entscheidung, aber es besteht die Gefahr, dass, wenn wir keine Möglichkeit bieten, dass Repositories Projektbesitzern erlauben, diese Beziehung sicher auszudrücken, sie dazu verleitet werden, die Metadaten des Repository-Betreibers stattdessen zu verwenden, was die ursprüngliche Unsicherheit wieder einführen würde.
Spezifikation
Diese Spezifikation definiert die Änderungen in Version 1.2 der einfachen Repository-API und fügt zwei neue Metadaten-Elemente hinzu: „Repository Tracks“ und „Alternate Locations“.
Repository „Tracks“ Metadaten
Um einem Repository zu ermöglichen, ein Projekt zu hosten, das ein Projekt hosten soll, das in anderen Repositories gehostet wird, erlaubt diese PEP dem erweiternden Repository, zu deklarieren, dass ein bestimmtes Projekt ein Projekt in einem oder mehreren anderen Repositories „verfolgt“, indem die URLs des Projekts und der Repositories hinzugefügt werden, die es erweitert.
Dies wird in JSON als Schlüssel meta.tracks und in HTML als Meta-Element namens pypi:tracks auf den projektspezifischen URLs ($root/$project/) verfügbar gemacht.
Es gibt einige wichtige Eigenschaften, die bei Verwendung dieser Metadaten **MÜSSEN** beibehalten werden.
- Es **MUSS** unter der Kontrolle der Repository-Betreiber selbst liegen, nicht eines einzelnen Publishers, der dieses Repository nutzt.
- „Repository-Betreiber“ kann auch jeden umfassen, der den gesamten Namensraum für ein bestimmtes Repository verwaltet, was in Situationen wie gehosteten Repository-Diensten der Fall sein kann, bei denen eine Entität die Software betreibt, aber eine andere den gesamten Namensraum dieses Repositorys besitzt/verwaltet.
- Alle URLs **MÜSSEN** dasselbe „Projekt“ darstellen wie das Projekt im erweiternden Repository.
- Das bedeutet nicht, dass sie dieselben Dateien bereitstellen müssen. Es ist gültig, wenn sie Binärdateien enthalten, die auf verschiedenen Plattformen kompiliert wurden, Kopien mit lokalen Patches usw. Dies ist bewusst vage gehalten, da es letztendlich von den Erwartungen der Benutzer an das Repository und seine Betreiber abhängt, was genau das „selbe“ Projekt ausmacht.
- Es **MUSS** auf die Repositories verweisen, die die Namensräume „besitzen“, nicht auf ein anderes Repository, das diesen Namensraum ebenfalls verfolgt.
- Es **MUSS** auf ein Projekt mit exakt demselben Namen (nach Normalisierung) verweisen.
- Es **MUSS** auf die tatsächlichen URLs für dieses Projekt verweisen, nicht auf die Basis-URL der erweiterten Repositories.
Es ist **NICHT** erforderlich, dass jeder Name in einem Repository dasselbe Repository verfolgt oder überhaupt ein Repository verfolgt. Gemischte Repositories, bei denen einige Namen ein Repository verfolgen und andere nicht, sind ausdrücklich erlaubt.
JSON
{
"meta": {
"api-version": "1.2",
"tracks": ["https://pypi.org/simple/holygrail/", "https://test.pypi.org/simple/holygrail/"]
},
"name": "holygrail",
"files": [
{
"filename": "holygrail-1.0.tar.gz",
"url": "https://example.com/files/holygrail-1.0.tar.gz",
"hashes": {"sha256": "...", "blake2b": "..."},
"requires-python": ">=3.7",
"yanked": "Had a vulnerability"
},
{
"filename": "holygrail-1.0-py3-none-any.whl",
"url": "https://example.com/files/holygrail-1.0-py3-none-any.whl",
"hashes": {"sha256": "...", "blake2b": "..."},
"requires-python": ">=3.7",
"dist-info-metadata": true
}
]
}
HTML
<!DOCTYPE html>
<html>
<head>
<meta name="pypi:repository-version" content="1.2">
<meta name="pypi:tracks" content="https://pypi.org/simple/holygrail/">
<meta name="pypi:tracks" content="https://test.pypi.org/simple/holygrail/">
</head>
<body>
<a href="https://example.com/files/holygrail-1.0.tar.gz#sha256=...">
<a href="https://example.com/files/holygrail-1.0-py3-none-any.whl#sha256=...">
</body>
</html>
„Alternate Locations“ Metadaten
Um einem Projekt zu ermöglichen, seinen Namensraum über mehrere Repositories hinweg zu erweitern, erlaubt diese PEP einem Projektbesitzer, eine Liste von „alternativen Speicherorten“ für sein Projekt zu deklarieren. Dies wird in JSON als Schlüssel alternate-locations und in HTML als Meta-Element namens pypi-alternate-locations verfügbar gemacht, das mehrmals verwendet werden kann.
Es gibt einige wichtige Eigenschaften, die bei Verwendung dieser Metadaten **BEACHTET WERDEN MÜSSEN**.
- Damit diese Metadaten vertrauenswürdig sind, **MUSS** eine Übereinstimmung zwischen allen Speicherorten, an denen das Projekt gefunden wird, darüber bestehen, was die alternativen Speicherorte sind.
- Bei der Verwendung alternativer Speicherorte **MÜSSEN** Clients implizit annehmen, dass die URL, von der die Antwort abgerufen wurde, in der Liste enthalten ist. Das bedeutet, wenn Sie von
https://pypi.org/simple/foo/abrufen und diesealternate-locationsMetadaten mit dem Wert["https://example.com/simple/foo/"]hat, dann **MÜSSEN** Sie es so behandeln, als hätte es den Wert["https://example.com/simple/foo/", "https://pypi.org/simple/foo/"]. - Die Reihenfolge der Elemente innerhalb des Arrays hat keine besondere Bedeutung.
Wenn ein Installer auf ein Projekt stößt, das die Metadaten für alternative Speicherorte verwendet, **SOLLTE** er davon ausgehen, dass alle genannten Repositories denselben Namensraum über mehrere Repositories hinweg erweitern.
Hinweis
Diese Metadaten für alternative Speicherorte sind projektspezifische Metadaten, keine artefaktbezogenen Metadaten, was bedeutet, dass sie nicht Teil der Kernmetadatenspezifikation sind, sondern dass jedes Repository eine Konfigurationsoption dafür bereitstellen muss (wenn es diese unterstützt).
JSON
{
"meta": {
"api-version": "1.2"
},
"name": "holygrail",
"alternate-locations": ["https://pypi.org/simple/holygrail/", "https://test.pypi.org/simple/holygrail/"],
"files": [
{
"filename": "holygrail-1.0.tar.gz",
"url": "https://example.com/files/holygrail-1.0.tar.gz",
"hashes": {"sha256": "...", "blake2b": "..."},
"requires-python": ">=3.7",
"yanked": "Had a vulnerability"
},
{
"filename": "holygrail-1.0-py3-none-any.whl",
"url": "https://example.com/files/holygrail-1.0-py3-none-any.whl",
"hashes": {"sha256": "...", "blake2b": "..."},
"requires-python": ">=3.7",
"dist-info-metadata": true
}
]
}
HTML
<!DOCTYPE html>
<html>
<head>
<meta name="pypi:repository-version" content="1.2">
<meta name="pypi:alternate-locations" content="https://pypi.org/simple/holygrail/">
<meta name="pypi:alternate-locations" content="https://test.pypi.org/simple/holygrail/">
</head>
<body>
<a href="https://example.com/files/holygrail-1.0.tar.gz#sha256=...">
<a href="https://example.com/files/holygrail-1.0-py3-none-any.whl#sha256=...">
</body>
</html>
Empfehlungen
Dieser Abschnitt ist nicht normativ; er liefert Empfehlungen für Installer, wie diese Metadaten interpretiert werden sollen, da diese PEP den besten Kompromiss zwischen dem Schutz von Benutzern standardmäßig und der Minimierung von Unterbrechungen bestehender Arbeitsabläufe bietet. Diese Empfehlungen sind nicht bindend, und Installer können sie ignorieren oder selektiv anwenden, wie es für ihre spezifischen Situationen sinnvoll ist.
Algorithmus zur Dateisuche
Hinweis
Dieser Algorithmus basiert darauf, wie pip derzeit Dateien entdeckt; andere Installer können ihn basierend auf ihren eigenen Suchverfahren anpassen.
Derzeit sieht der „Standard“-Algorithmus zur Dateisuche ungefähr so aus:
- Generieren einer Liste aller Dateien aus allen konfigurierten Repositories.
- Filtern aller Dateien heraus, die nicht mit bekannten Hashes aus einer Sperrdatei oder einer Anforderungsdatei übereinstimmen.
- Filtern aller Dateien heraus, die nicht mit der aktuellen Plattform, Python-Version usw. übereinstimmen.
- Übergabe dieser Dateiliste an den Resolver, der versuchen wird, die „beste“ Übereinstimmung aus diesen Dateien zu ermitteln, unabhängig davon, aus welchem Repository sie stammt.
Es wird empfohlen, dass Installer ihren Algorithmus zur Dateisuche ändern, um die neuen Metadaten zu berücksichtigen, und stattdessen Folgendes tun:
- Generieren einer Liste aller Dateien aus allen konfigurierten Repositories.
- Filtern aller Dateien heraus, die nicht mit bekannten Hashes aus einer Sperrdatei oder einer Anforderungsdatei übereinstimmen.
- Wenn der Endbenutzer dem Installer explizit mitgeteilt hat, das Projekt aus bestimmten Repositories abzurufen, filtern Sie alle anderen Repositories heraus und springen Sie zu 5.
- Prüfen Sie, ob die entdeckten Dateien mehrere Repositories umfassen; wenn ja, bestimmen Sie, ob entweder „Tracks“ oder „Alternate Locations“ Metadaten das sichere Zusammenführen *ALLER* Repositories, aus denen Dateien entdeckt wurden, zulassen. Wenn diese Metadaten dies **NICHT** zulassen, generieren Sie einen Fehler, andernfalls fahren Sie fort.
- **Hinweis:** Dies gilt nur für *entfernte* Repositories; Repositories, die sich auf dem lokalen Dateisystem befinden, **SOLLTEN** immer implizit erlaubt sein, mit jedem entfernten Repository zusammengeführt zu werden.
- Filtern aller Dateien heraus, die nicht mit der aktuellen Plattform, Python-Version usw. übereinstimmen.
- Übergabe dieser Dateiliste an den Resolver, der versuchen wird, die „beste“ Übereinstimmung aus diesen Dateien zu ermitteln, unabhängig davon, aus welchem Repository sie stammt.
Dies ist etwas subtil, aber die wichtigsten Punkte in der Empfehlung sind:
- Benutzer, die Sperrdateien oder Anforderungsdateien verwenden, die spezifische Hashes von „gültigen“ Artefakten enthalten, werden aufgrund dieser Hashes als geschützt angenommen, da der Rest dieser Empfehlungen während der Hash-Generierung angewendet würde. Daher filtern wir unbekannte Hashes im Voraus heraus.
- Wenn der Benutzer dem Installer explizit mitgeteilt hat, dass er ein Projekt aus bestimmten Repositories abrufen möchte, gibt es keinen Grund, dies in Frage zu stellen, und wir gehen davon aus, dass er sichergestellt hat, dass diese Namensräume zusammengeführt werden dürfen.
- Wenn das betreffende Projekt nur aus einem einzigen Repository stammt, besteht keine Gefahr von Dependency Confusion, daher gibt es keinen Grund, etwas anderes zu tun, als es zuzulassen.
- Wir prüfen die Metadaten in dieser PEP, bevor wir nach Plattform, Python-Version usw. filtern, da wir keine Fehler wünschen, die nur auf bestimmten Plattformen, Python-Versionen usw. auftreten.
- Wenn nichts uns sagt, dass das Zusammenführen der Namensräume sicher ist, weigern wir uns, dies implizit anzunehmen, und generieren stattdessen einen Fehler.
- Andernfalls führen wir die Namensräume zusammen und fahren fort.
Dieser Algorithmus stellt sicher, dass ein Installer niemals annimmt, dass zwei unterschiedliche Namensräume zu einem zusammengeführt werden können, was für alle praktischen Zwecke die Möglichkeit jeglicher Art von Dependency Confusion-Angriffen eliminiert und gleichzeitig die Macht über den gesamten Stack auf sichere Weise gibt, damit Personen explizit deklarieren können, wann diese unterschiedlichen Namensräume tatsächlich ein logischer Namensraum sind, der sicher zusammengeführt werden kann.
Der obige Algorithmus ist weitgehend ein konzeptionelles Modell. In der Realität kann der Algorithmus etwas anders sein, um datenschutzfreundlicher und schneller zu sein, oder sogar angepasst werden, um besser zu einem bestimmten Installer zu passen.
Explizite Konfiguration für Endbenutzer
Diese PEP vermeidet es, einen spezifischen Mechanismus vorzuschreiben oder zu empfehlen, mit dem ein Installer einem Endbenutzer erlaubt, genau zu konfigurieren, aus welchen Repositories er ein bestimmtes Paket installieren möchte. Sie empfiehlt jedoch, dass Installer *einen* Mechanismus bereitstellen, damit Endbenutzer diese Konfiguration vornehmen können, da Benutzer andernfalls in eine DoS-Situation geraten können, wie im Fall von torchtriton, bei denen sie völlig lahmgelegt sind, es sei denn, sie lösen die Namensraumkollision extern (lassen den Namen auf einem Repository entfernen, richten ein persönliches Repository ein, das das Zusammenführen übernimmt usw.).
Diese Konfiguration ermöglicht es Endbenutzern auch, sich proaktiv während einer wahrscheinlich langen Übergangszeit zu sichern, bis das Standardverhalten sicher ist.
Wie kommuniziert man das?
Hinweis
Dieses Beispiel ist spezifisch für pip und nimmt Besonderheiten an, wie pip diese PEP implementieren wird. Es ist als Beispiel dafür enthalten, wie wir diese Änderung kommunizieren können, und soll pip oder andere Installer nicht einschränken, wie sie diese implementieren. Dies könnte letztendlich die tatsächliche Grundlage für die Kommunikation sein, und falls ja, muss es auf Genauigkeit und Klarheit überprüft werden.
Dieser Abschnitt sollte gelesen werden, als ob er ein vollständiger „Beitrag“ wäre, um diese Änderung zu kommunizieren, die für einen Blogbeitrag, eine E-Mail oder einen Discourse-Beitrag verwendet werden könnte.
Es gibt eine langjährige Klasse von Angriffen, die als „Dependency Confusion“-Angriffe bezeichnet werden, die grob gesagt darauf hinauslaufen, dass eine Einzelperson erwartet, Paket A zu erhalten, aber stattdessen B erhalten hat. In Python geschieht dies fast immer aufgrund der Tatsache, dass der Endbenutzer mehrere Repositories konfiguriert hat, wobei erwartet wurde, dass Paket A aus Repository X stammt, aber jemand in der Lage ist, Paket B unter demselben Namen wie Paket A in Repository Y zu veröffentlichen.
Es gibt heute eine Reihe von Möglichkeiten, diese Angriffe zu mildern, aber sie erfordern alle, dass der Endbenutzer sich explizit darum bemüht, sich selbst zu schützen, anstatt dass es inhärent sicher ist.
Um die Benutzer von pip zu sichern und sie vor diesen Arten von Angriffen zu schützen, werden wir ändern, wie pip Pakete zur Installation entdeckt.
Was ändert sich?
Wenn pip feststellt, dass dasselbe Projekt aus mehreren entfernten Repositories verfügbar ist, wird es standardmäßig einen Fehler generieren und sich weigern fortzufahren, anstatt eine Vermutung anzustellen, aus welchem Repository es korrekt zu installieren war.
Projekte, die nativ in mehreren Repositories veröffentlichen, erhalten die Möglichkeit, ihre Repositories sicher zu „verknüpfen“, damit pip bei Verwendung dieser Repositories zusammen nicht fehlschlägt.
Endbenutzern von pip wird die Möglichkeit gegeben, explizit ein oder mehrere Repositories zu definieren, die für ein bestimmtes Projekt gültig sind, was dazu führt, dass pip nur diese Repositories für dieses Projekt berücksichtigt und die Generierung eines Fehlers vermeidet.
Siehe TBD für weitere Informationen.
Wer ist betroffen?
Benutzer, die aus mehreren entfernten (d. h. nicht auf dem lokalen Dateisystem vorhandenen) Repositories installieren, können davon betroffen sein, dass pip anstelle einer erfolgreichen Installation fehlschlägt, wenn
- Sie installieren ein Projekt, bei dem derselbe „Name“ von mehreren entfernten Repositories bereitgestellt wird.
- Der Projektname, der aus mehreren entfernten Repositories verfügbar ist, hat keinen der definierten Mechanismen zur Verknüpfung dieser Repositories verwendet.
- Der aufrufende Benutzer von pip hat keinen der definierten Mechanismen verwendet, um explizit zu steuern, welche Repositories für ein bestimmtes Projekt gültig sind.
Benutzer, die keine mehreren entfernten Repositories verwenden, sind überhaupt nicht betroffen, einschließlich Benutzern, die nur ein einziges entferntes Repository plus einen lokalen „Wheelhouse“ verwenden.
Was muss ich tun?
Als pip-Benutzer?
Wenn Sie nur ein einziges entferntes Repository verwenden, müssen Sie nichts tun.
Wenn Sie mehrere entfernte Repositories verwenden, können Sie das neue Verhalten aktivieren, indem Sie --use-feature=TBD zu Ihrer pip-Aufrufung hinzufügen, um zu sehen, ob einige Ihrer Abhängigkeiten aus mehreren entfernten Repositories bezogen werden. Wenn dies der Fall ist, sollten Sie sie überprüfen, um zu ermitteln, warum dies der Fall ist und welche beste Korrekturmaßnahme für Sie am besten geeignet ist.
Sobald dieses Verhalten zum Standard wird, können Sie es vorübergehend deaktivieren, indem Sie --use-deprecated=TBD zu Ihrer pip-Aufrufung hinzufügen.
Wenn Sie Projekte verwenden, die nicht auf einem öffentlichen Repository gehostet werden, aber das öffentliche Repository als Fallback beibehalten, sollten Sie erwägen, pip mit einer Repository-Datei zu konfigurieren, um explizit anzugeben, woher diese Abhängigkeit stammen soll, um zu verhindern, dass die Registrierung dieses Namens in einem öffentlichen Repository dazu führt, dass pip für Sie fehlschlägt.
Als Projektbesitzer?
Wenn Sie Ihr Projekt nur in einem einzigen Repository veröffentlichen, müssen Sie nichts tun.
Wenn Sie Ihr Projekt in mehreren Repositories veröffentlichen, die gleichzeitig verwendet werden sollen, konfigurieren Sie alle Repositories so, dass sie die Metadaten des alternativen Repositories bereitstellen, um Unterbrechungen für Ihre Endbenutzer zu verhindern.
Wenn Sie Ihr Projekt in einem einzigen Repository veröffentlichen, es aber häufig in Verbindung mit anderen Repositories verwendet wird, sollten Sie erwägen, Ihre Namen präventiv bei diesen Repositories zu registrieren, um zu verhindern, dass eine dritte Partei Ihre pip install Aufrufe zum Scheitern bringt. Dies ist möglicherweise nicht verfügbar, wenn Ihr Projektname zu generisch ist oder wenn die Repositories Richtlinien haben, die eine defensive Namensreservierung verhindern.
Als Repository-Betreiber?
Sie müssen entscheiden, wie Ihr Repository von Ihren Endbenutzern genutzt werden soll und wie Sie möchten, dass sie es nutzen.
Für private Repositories, die private Projekte hosten, wird empfohlen, die öffentlichen Projekte, von denen Ihre Benutzer abhängig sind, in Ihr eigenes Repository zu spiegeln, wobei darauf zu achten ist, dass kein öffentliches Projekt mit einem privaten Projekt zusammengeführt wird, und Ihren Benutzern mitzuteilen, die Option --index-url zu verwenden, um nur Ihr Repository zu nutzen.
Für öffentliche Repositories, die öffentliche Projekte hosten, sollten Sie den Mechanismus für alternative Repositories implementieren und den Besitzern dieser Projekte die Möglichkeit geben, die Liste der Repositories zu konfigurieren, aus denen ihr Projekt verfügbar ist, wenn es aus mehr als einem Repository verfügbar ist.
Für öffentliche Repositories, die ein anderes Repository „verfolgen“, aber ergänzende Artefakte wie für eine bestimmte Plattform gebaute Wheels bereitstellen, sollten Sie die „Tracks“-Metadaten für Ihr Repository implementieren. Diese Informationen **DÜRFEN NICHT** von Endbenutzern gesetzt werden, die Projekte in Ihrem Repository veröffentlichen. Siehe TBD für weitere Informationen.
Abgelehnte Ideen
Hinweis: Einige davon sind etwas spezifisch für pip, aber jede Lösung, die für pip nicht funktioniert, ist keine besonders nützliche Lösung.
Implizites Zulassen von Spiegelungen, wenn die Dateilisten identisch sind
Wenn jedes Repository exakt dieselbe Liste von Dateien zurückgibt, ist es sicher, diese Repositories als denselben Namensraum zu betrachten und sie implizit zusammenzuführen. Dies könnte bedeuten, dass Spiegelungen automatisch ohne jegliche Arbeit von Benutzer- oder Repository-Betreiberseite zugelassen würden.
Leider hat dies zwei Mängel, die es unerwünscht machen:
- Es löst nur den Fall von Spiegelungen, die exakte Kopien voneinander sind, aber nicht von Repositories, die andere „verfolgen“, was letztendlich eine allgemeinere Lösung ist.
- Selbst im Fall exakter Spiegelungen werden mehrere sich gegenseitig spiegelnde Repositories in einem verteilten System nicht immer vollständig konsistent sein, was effektiv ein eventual konsistentes System darstellt. Dies bedeutet, dass Repositories, die sich auf diese implizite Heuristik verlassen, sporadische Fehler aufgrund von Abweichungen zwischen dem Quell-Repository und den gespiegelten Repositories aufweisen würden.
Bereitstellung eines Mechanismus zur Reihenfolge der Repositories
Die Bereitstellung eines Mechanismus zur Reihenfolge der Repositories und das anschließende Kurzschließen des Suchalgorithmus, wenn das erste Repository gefunden wird, das Dateien für dieses Projekt bereitstellt, ist eine weitere praktikable Lösung, die sicher ist, wenn die Reihenfolge korrekt angegeben wird.
Dies wurde jedoch aus einer Reihe von Gründen abgelehnt:
- Wir haben über 15 Jahre damit verbracht, Benutzern beizubringen, dass die Reihenfolge der angegebenen Repositories nicht aussagekräftig ist und sie eine undefinierte Reihenfolge haben. Es wäre schwierig, davon abzurücken und zu sagen, dass die Reihenfolge jetzt wichtig ist.
- Benutzer können die Reihenfolge, in der sie ihre Repositories angeben, innerhalb eines einzelnen Ortes leicht neu anordnen, aber beim Laden von Repositories aus mehreren Orten (Umgebungsvariable, Konfigurationsdatei, Anforderungsdatei, CLI-Argumente) ist die Reihenfolge in pip fest codiert. Obwohl es eine deterministische und dokumentierte Reihenfolge wäre, gibt es keinen Grund anzunehmen, dass es sich um die Reihenfolge handelt, in der der Benutzer seine Repositories definieren möchte, was ihn zwingt, die Konfiguration von pip so zu verzerren, dass die implizite Reihenfolge die richtige ist.
- Das Obige kann durch die Bereitstellung einer Möglichkeit zur expliziten Angabe der Reihenfolge, anstatt der impliziten Verwendung der Reihenfolge, in der sie definiert wurden, gemildert werden; dies bedeutet jedoch dann, dass die Schutzmaßnahmen nicht bereitgestellt werden, es sei denn, der Benutzer nimmt eine explizite Konfiguration vor.
- Die Reihenfolge geht davon aus, dass ein Repository **immer** einem anderen Repository vorgezogen wird, ohne Möglichkeit, dies projektweise zu entscheiden.
- Das Verlassen auf die Reihenfolge ist subtil; wenn ich mir eine Reihenfolge von Repositories ansehe, habe ich keine Möglichkeit zu wissen oder im Voraus sicherzustellen, welche Namen von welchen Repositories stammen werden. Ich kann nur in diesem Moment wissen, welche Namen von welchen Repositories bereitgestellt werden.
- Das Verlassen auf die Reihenfolge ist fragil. Es gibt keinen Grund anzunehmen, dass zwei unterschiedliche Repositories keine zufälligen Namenskollisionen haben werden – was passiert, wenn ich eine Bibliothek aus einem Repository niedrigerer Priorität verwende und ein Repository höherer Priorität zufällig eine kollidierende Namensgebung beginnt?
- In Fällen, in denen die Reihenfolge falsch ist, geschieht dies stillschweigend, ohne Feedback an den Benutzer. Dies geschieht absichtlich, da es nicht tatsächlich weiß, was falsch oder richtig ist, es hofft nur, dass die Reihenfolge das Richtige bewirkt, und wenn dies der Fall ist, sind die Benutzer ohne Unterbrechungen geschützt. Wenn es jedoch falsch ist, bleiben die Benutzer mit einem sehr verwirrenden Verhalten von pip zurück, wo es einfach stillschweigend die falsche Sache installiert.
Es gibt eine Variante dieser Idee, die im Wesentlichen besagt, dass es die Natur der offenen Registrierung von PyPI ist, die die wirklichen Probleme verursacht. Wenn wir also alle Repositories außer dem „Standard“-Repository gleichrangig behandeln und dann das Standard-Repository als niedrigere Priorität behandeln, werden wir die Dinge reparieren.
Das ist wahr, insofern als es die Dinge verbessert, aber es hat viele der gleichen Probleme wie die allgemeine Reihenfolge-Idee (wenn auch nicht alle).
Es geht auch davon aus, dass PyPI, oder welches Repository auch immer als „Standard“ konfiguriert ist, das einzige Repository mit offener Registrierung von Namen ist. Projekte wie Piwheels existieren jedoch, die Benutzer erwartungsgemäß zusätzlich zu PyPI verwenden und die ebenfalls eine offene Registrierung von Namen haben, da sie alles verfolgen, was auf PyPI registriert ist.
Verlassen auf Repository-Proxys
Eine mögliche Lösung ist, dass statt des Installers, der dies lösen muss, stattdessen auf Repository-Proxys zurückgegriffen wird, die mehrere Repositories sicher intelligent zusammenführen können. Dies könnte eine bessere Erfahrung für Leute mit komplexen Bedürfnissen bieten, da sie Konfigurationen und Funktionen haben können, die auf den Problembereich zugeschnitten sind.
Das wurde jedoch aus folgenden Gründen abgelehnt:
- Es erfordert, dass Benutzer sich für deren Verwendung entscheiden, es sei denn, wir entfernen auch die Einrichtungen, um mehr als ein Repository in Installern zu haben, um Benutzer zu zwingen, einen Repository-Proxy zu verwenden, wenn sie mehrere Repositories benötigen.
- Das Entfernen der Möglichkeit, mehr als ein Repository zu konfigurieren, wurde abgelehnt, da dies für Endbenutzer zu störend wäre.
- Ein Benutzer benötigt möglicherweise unterschiedliche Ergebnisse beim Zusammenführen mehrerer Repositories in unterschiedlichen Kontexten oder muss verschiedene, sich gegenseitig ausschließende Repositories zusammenführen. Das bedeutet, dass er für jede eindeutige Optionskombination tatsächlich mehrere Repository-Proxys einrichten muss.
- Dies erfordert, dass Benutzer Infrastruktur pflegen oder dass Installer Funktionen hinzugefügt werden, um automatisch ein Repository für jede Ausführung zu starten.
- Dies ändert nicht die Notwendigkeit, eine Lösung für diese Probleme zu haben, sondern verlagert lediglich die Implementierungsverantwortung von den Installern auf einen Repository-Proxy. In beiden Fällen benötigen wir jedoch immer noch etwas, das herausfindet, wie diese unterschiedlichen Namespaces zusammengeführt werden.
- Letztendlich möchten die meisten Benutzer keinen Repository-Proxy einrichten, nur um sicher mit mehreren Repositories interagieren zu können.
Verlassen nur auf Hash-Prüfung
Eine weitere mögliche Lösung ist die Nutzung der Hash-Überprüfung, da Benutzer bei aktivierter Hash-Überprüfung kein unerwartetes Artefakt erhalten; es spielt keine Rolle, ob die Namespaces falsch zusammengeführt wurden oder nicht.
Dies ist sicherlich eine Lösung; leider leidet sie auch unter Problemen, die sie unbrauchbar machen
- Es erfordert, dass Benutzer sich dafür entscheiden, sodass Benutzer standardmäßig weiterhin ungeschützt sind.
- Es erfordert, dass Benutzer viel Arbeit investieren, um ihre Hashes zu verwalten, was die meisten Benutzer wahrscheinlich nicht tun wollen.
- Es ist schwierig und umständlich, den Schutz zu erhalten, wenn Benutzer keine
requirements.txtDatei als Quelle ihrer Abhängigkeiten verwenden (dies betrifft Build-Zeit-Abhängigkeiten und Abhängigkeiten, die über die Befehlszeile bereitgestellt werden). - Es löst das Problem nur bedingt, da es die Verantwortung für das Problem auf das System verlagert, das die Hashes generiert, die der Installer verwenden würde. Wenn dieses System keine manuell Hashes validierende Person ist, was unwahrscheinlich ist, dann haben wir die Frage nach der Zusammenführung dieser Namespaces auf das Tool verlagert, das die Wartung der Hashes implementiert.
Verlangen, dass alle Projekte im „Standard“-Repository existieren
Eine weitere Idee ist, den Umfang von --extra-index-url so einzuschränken, dass seine einzige unterstützte Verwendung darin besteht, auf ergänzende Repositories zum Standard-Repository zu verweisen, was effektiv bedeutet, dass das Standard-Repository den Namespace definiert und jedes zusätzliche Repository es nur mit zusätzlichen Paketen erweitert.
Die Implementierung hierfür wäre grob gesagt, zu verlangen, dass das Projekt MUSS im Standard-Repository registriert sein, damit zusätzliche Repositories funktionieren.
Dies funktioniert, wenn man den Umfang auf diese Weise erfolgreich einschränkt, wurde aber letztendlich abgelehnt, weil
- Benutzer werden diesen reduzierten Umfang wahrscheinlich nicht verstehen oder akzeptieren und somit wahrscheinlich versuchen, ihn weiterhin in der nun nicht mehr unterstützten Weise zu verwenden.
- Dies wird dadurch erschwert, dass Benutzer, die den ausgeschlossenen Workflow haben, mit dem nun eingeschränkten Umfang keine andere Wahl haben, als einen Repository-Proxy einzurichten, was Infrastruktur und Aufwand erfordert, die sie zuvor nicht betreiben mussten.
- Es wird davon ausgegangen, dass nur weil ein Name in einem "zusätzlichen" Repository derselbe ist wie im Standard-Repository, es sich um dasselbe Projekt handelt. Wenn wir von Grund auf in einem brandneuen Ökosystem starten würden, könnten wir diese Annahme vielleicht von Anfang an treffen und sie beibehalten, aber es wird unglaublich schwierig sein, das Ökosystem zu dieser Änderung zu bewegen.
- Dies ist ein grundlegendes Problem dieses Ansatzes; das zugrunde liegende Problem, das zu Dependency Confusion führt, ist, dass wir unterschiedliche Namespaces nehmen und sie in einen einzigen flachen Namespace einbetten. Dieser Ansatz erklärt im Wesentlichen nur "OK" und versucht, ihn zu mildern, indem er von allen verlangt, ihre Namen zu registrieren.
- Aufgrund der oben genannten Annahme wird in Fällen, in denen ein Name in einem zusätzlichen Repository versehentlich mit dem Standard-Repository kollidiert, dieser für diese Benutzer scheinbar funktionieren, aber sie befinden sich dann heimlich in einem Zustand der Dependency Confusion.
- Dies wird dadurch verschlimmert, dass die Person, der der Name gehört, der dies ermöglicht, sich der Rolle, die sie für diesen Benutzer spielt, nicht bewusst ist und möglicherweise ihr Projekt löscht oder es an jemand anderen übergibt, was es möglicherweise einem böswilligen Benutzer ermöglicht, es zu übernehmen.
- Benutzer werden wahrscheinlich versuchen, in einen funktionierenden Zustand zurückzukehren, indem sie ihre Namen in ihrem Standard-Repository als defensives Name Squatting registrieren. Ihre Fähigkeit, dies zu tun, hängt von den spezifischen Richtlinien ihres Standard-Repositorys ab, ob jemand bereits diesen Namen hat, ob er zu generisch ist usw. Im besten Fall führt dies zu unnötigen Platzhalterprojekten, die keinen anderen Zweck erfüllen, als die interne Verwendung eines Namens zu sichern.
Umbenennung in global eindeutige Namen
Der Hauptgrund für dieses Problem ist, dass wir keine global eindeutigen Namen haben, sondern lokal eindeutige Namen, die unter mehreren Namespaces existieren, die wir versuchen, in einen einzigen flachen Namespace zusammenzuführen. Wenn wir stattdessen einen Weg finden könnten, global eindeutige Namen zu haben, könnten wir das gesamte Problem umgehen.
Diese Idee wurde abgelehnt, weil
- Das Erzeugen von global eindeutigen, aber sicheren Namen, die auch für Menschen verständlich sind, ist eine fast unmögliche Aufgabe, ohne auf eine Art zentralisierte Datenbank zurückzugreifen. Soweit ich weiß, greifen die einzigen Systeme, die dies geschafft haben, auf das Domain-System zurück und verweisen auf Pakete über URLs mit Domains usw.
- Selbst wenn wir einen Mechanismus für global eindeutige Namen finden, ist unsere Fähigkeit, diesen in unser jahrzehntealtes System zu integrieren, praktisch null, ohne alles niederzubrennen und von vorne zu beginnen. Das Beste, was wir wahrscheinlich tun könnten, wäre zu erklären, dass alle nicht global eindeutigen Namen implizit Namen auf der PyPI-Domain sind und alle mit einem Nicht-PyPI-Paket ihr Paket umbenennen müssen.
- Dies würde so viele Kernannahmen und grundlegende Teile unseres aktuellen Systems auf den Kopf stellen, dass es schwer ist, überhaupt zu wissen, wo man anfangen soll, sie aufzulisten.
Nur Empfehlung, dass Installer explizite Konfiguration anbieten
Eine Idee, die aufkam, ist, im Wesentlichen die explizite Konfiguration zu implementieren und nichts anderes zu ändern. Der spezifische Vorschlag für eine Mapping-Richtlinie hat die Option zur expliziten Konfiguration inspiriert und eine Datei erstellt, die etwa so aussah
{
"repositories": {
"PyTorch": ["https://download.pytorch.org/whl/nightly"],
"PyPI": ["https://pypi.org/simple"]
},
"mapping": [
{
"paths": ["torch*"],
"repositories": ["PyTorch"],
"terminating": true
},
{
"paths": ["*"],
"repositories": ["PyPI"]
}
]
}
Die Empfehlung zur expliziten Konfiguration überlässt die Entscheidung über die Implementierung jedem Installer, was ihm erlaubt zu wählen, was für seine Benutzer am besten funktioniert.
Letztendlich wurde die Implementierung nur einer Art expliziter Konfiguration abgelehnt, weil sie naturgemäß opt-in ist und daher durchschnittliche Benutzer, die am wenigsten in der Lage sind, das Problem mit den vorhandenen Werkzeugen zu lösen, nicht schützt; durch Hinzufügen zusätzlicher Schutzmaßnahmen neben der expliziten Konfiguration können wir alle Benutzer standardmäßig schützen.
Darüber hinaus bedeutet die alleinige Abhängigkeit von expliziter Konfiguration auch, dass jeder Endbenutzer dasselbe Problem immer wieder lösen muss, selbst in Fällen von Spiegelungen von PyPI, Piwheels, PyTorch usw. In jedem einzelnen Fall müssen sie dort sitzen und Entscheidungen treffen (oder ein Beispiel finden, dem sie folgen können), um sicher zu sein. Das Hinzufügen zusätzlicher Funktionen ermöglicht es uns, diese Schutzmaßnahmen zu zentralisieren, wo wir können, während wir fortgeschrittenen Endbenutzern immer noch die Möglichkeit geben, ihr eigenes Schicksal vollständig zu kontrollieren.
Scopes à la npm
Es wurde vorgeschlagen, dass Scopes, ähnlich wie npm sie implementiert hat, dies letztendlich lösen könnten. Letztendlich ändern Scopes nichts an diesem Problem. Soweit ich weiß, sind Scopes in npm nicht global eindeutig, sondern an eine bestimmte Registry gebunden, genau wie unscope Namen. Was Scopes jedoch ermöglichen, ist ein offensichtlicher Mechanismus zum Gruppieren zusammengehöriger Projekte und die Fähigkeit eines Benutzers oder einer Organisation auf npm.org, einen gesamten Scope zu beanspruchen, was die explizite Konfiguration erheblich erleichtert, da Sie sicher sein können, dass ein ganzer kleiner Teil des Namespaces Ihnen gehört, und Sie können leicht eine Regel schreiben, die einen gesamten Scope einer bestimmten nicht-öffentlichen Registry zuweist.
Leider ist dies im Grunde eine einfachere Version der Idee, nur die explizite Konfiguration zu verwenden, was in npm in Ordnung ist, da es für Menschen nicht besonders üblich ist, ihre eigenen Registries zu verwenden, aber in Python ermutigen wir Sie genau dazu.
Definition und Standardisierung der „Expliziten Konfiguration“
Dieses PEP empfiehlt Installern, einen Mechanismus zur expliziten Konfiguration zu haben, von welchem Repository ein bestimmtes Projekt stammt, definiert jedoch nicht, wie dieser Mechanismus aussieht. Wir lassen dies absichtlich undefiniert, da es eng mit der Benutzererfahrung jedes einzelnen Installers verknüpft ist und wir jedem einzelnen Installer die Möglichkeit geben möchten, diese Konfiguration nach eigenem Ermessen für seine spezifischen Anwendungsfälle darzustellen.
Darüber hinaus schienen andere Installer, als die Idee aufkam, diesen Mechanismus zu definieren, kein besonderes Interesse daran zu haben, dass dieser Mechanismus für sie definiert wird, was darauf hindeutet, dass sie bereit waren, dies als Teil ihrer Benutzererfahrung zu behandeln.
Schließlich verdient dieser Mechanismus, wenn wir uns entscheiden würden, ihn zu definieren, ein eigenes PEP, anstatt ihn als Teil der Änderungen an der Repository-API in diesem PEP zu verankern, und er kann ein zukünftiges PEP sein, wenn wir uns letztendlich entscheiden, diesen Weg der Standardisierung zu beschreiten.
Danksagungen
Vielen Dank an Trishank Kuppusamy, der die Diskussion angestoßen hat, die mit seinem Vorschlag zu diesem PEP führte.
Vielen Dank an Paul Moore, Pradyun Gedam, Steve Dower und Trishank Kuppusamy für das frühe Feedback und die Diskussion der Ideen in diesem PEP.
Vielen Dank an Jelle Zijlstra, C.A.M. Gerlach, Hugo van Kemenade und Stefano Rivera für die redaktionelle Bearbeitung und Verbesserung der Struktur und Qualität dieses PEP.
Urheberrecht
Dieses Dokument wird in die Public Domain oder unter die CC0-1.0-Universal-Lizenz gestellt, je nachdem, welche Lizenz permissiver ist.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0708.rst
Zuletzt geändert: 2025-02-01 08:55:40 GMT