Abhängigkeiten verwalten

Genau hier finden Angriffe auf die Software-Lieferkette statt. Das OpenSSF Secure Supply Chain Consumption Framework (S2C2F) bietet ein strukturiertes Reifegradmodell dafür, wie Unternehmen Open-Source-Software nutzen sollten.

Siehe auch

Für ein umfassenderes Bedrohungsmodell über alle Ökosysteme hinweg ist das CNCF Software Supply Chain Security Whitepaper eine gute Einführung.

Wählt eure Abhängigkeiten sorgfältig aus

Bevor ihr eine Abhängigkeit hinzufügt, solltet ihr prüfen, ob ihr diese überhaupt benötigt, denn jede Abhängigkeit vergrößert eure Angriffsfläche. Weniger oder kleinere Abhängigkeiten bedeuten weniger Angriffsmöglichkeiten. Wenn ihr eine Abhängigkeit hinzufügt, bewertet die Sicherheitslage mithilfe der OpenSSF-Scorecard, die Projekte bewertet hinsichtlich

  • Branch-Protection

  • signierte Releases

  • Tools zur Aktualisierung von Abhängigkeiten

  • Vulnerability Disclosure

Eine niedrige Punktzahl gibt euch Aufschluss darüber, wie viel Vertrauen ihr in ein Projekt mit eingeschränkter Sicherheitshygiene setzen solltet.

Schreibt die Abhängigkeiten fest

Warnung

Wenn ihr eine Bibliothek auf PyPI veröffentlicht, solltet ihr im dependencies-Abschnitt eurer pyproject.toml-Datei möglichst breite Versionsbereiche verwenden, um Konflikte zu vermeiden, wenn andere eure Bibliothek zusammen mit weiteren Bibliotheken installieren wollen. Die Hinweise in diesem Abschnitt gelten ausschließlich für die Bereitstellung von Anwendungen.

Denkt euch folgendes Szenario: uv add schreibt in eure pyproject.toml-Datei die ungefähre Version eurer Abhängigkeit, z. B. "MYDEP>=3.0.5". Wenn das Projekt neu aufgesetzt wird, kann uv sync dazu führen, dass MYDEP in der Version 3.0.6 installiert wird. So könnte unbemerkt eine bösartige Version heruntergeladen werden ohne dass auch nur eine einzige Zeile Code geändert wurde.

Eine festgelegte Version "MYDEP==3.0.5" ist besser, da wir zumindest keine neuere Version als die getestete im Projekt erhalten. Dennoch erhaltet ihr somit immer noch keine Integritätsprüfung: Sollte bei einem Angriff das Konto des Maintainer kompromittiert werden und ein neues, mit einer Hintertür versehenes Release für dieselbe Version, aber für eine andere Plattform veröffentlicht werden, könnte auch dieses unwissentlich installiert werden. Um dieses Angriffsszenario zu reduzieren, sind zukünftig auf PyPI nur noch Releases für eine Version innerhalb von 14 Tagen erlaubt.

Hash-Pinning ist sicherer – es erstellt einen kryptografischen Fingerabdruck der Paketdatei, der mit uv in der uv.lock-Datei festgeschrieben wird. Alternativ könnt ihr auch die --require-hashes-Option von pip verwenden. Ihr solltet jedoch nicht nur für eure Python-Abhängigkeiten Hash-Pinning verwenden, sondern z. B. auch für eure pre-commit Checks und GitHub Actions.

Hash-Pinning schützt jedoch nicht davor, ein schädliches Paket zum ersten Mal zu installieren; in diesem Fall würdet ihr nur den Hash des schädlichen Pakets festlegen. Daher solltet ihr das Hash-Pinning mit Schwachstellenscans und verzögerter Übernahme kombinieren.

Siehe auch

The lockfile

Aktualisiert die Abhängigkeiten automatisch

Abhängigkeiten sollten regelmäßig aktualisiert werden, um Schwachstellen zu vermeiden, Inkompatibilitäten zwischen Abhängigkeiten einzuschränken und komplexe Upgrades zu vermeiden, wenn von einer zu alten Version aktualisiert wird. Eine Vielzahl von Werkzeugen kann dabei helfen, auf dem neuesten Stand zu bleiben.

Veraltete Abhängigkeiten machen ein Projekt anfällig für Angriffe auf bekannte Schwachstellen. Daher sollte die Aktualisierung von Abhängigkeiten automatisiert werden, indem nach veralteten Anforderungen gesucht wird und diese ggf. aktualisiert werden. Mit prek könnt ihr regelmäßig eure uv.lock-Datei aktualisieren:

.pre-commit-config.yaml
- repo: https://github.com/astral-sh/uv-pre-commit
  rev: 6a280ba12b7901e47757c868c8c13c6a624c9ecb # 0.11.7
  hooks:
    - id: uv-lock
      args: ["--exclude-newer = 'P3D'", "--quiet"]
--exclude-newer

Dependency Cooldown, das Pakete ausschließt, die erst seit einigen Tagen, mit P3D erst seit drei Tagen, auf PyPI veröffentlicht sind. Dies gibt den PyPI-Administrator*innen die Möglichkeit, in dieser Zeit auf Malware zu reagieren.

Siehe auch

Alternativ könnt ihr euch auch von ../envs/uv/renovate> unterstützen lassen.

Schwachstellen-Scans

Dependency Pinning verhindert unbefugte Änderungen – doch was passiert, wenn ihr eine Version gestgeschrieben habt, die eine bekannte Sicherheitslücke aufweist? Forschende entdecken immer wieder neue CVEs in Paketen. Ein Paket, das gestern noch problemlos war, könnte heute schon eine kritische Sicherheitslücke aufweisen. Offene Sicherheitslücken in euren Abhängigkeiten können leicht ausgenutzt werden, und sie sollten daher so schnell wie möglich geschlossen werden. Hierfür könnt ihr uv audit verwenden und überprüfen, ob euer Projekt bekannte Sicherheitslücken in den Abhängigkeiten aufweist:

$ uv audit
warning: `uv audit` is experimental and may change without warning. Pass `--preview-features audit-command` to disable this warning.
Resolved 115 packages in 16ms
Found 12 known vulnerabilities and no adverse project statuses in 114 packages

Vulnerabilities:

idna 3.12 has 1 known vulnerability:
- GHSA-65pc-fj4g-8rjx: Internationalized Domain Names in Applications (IDNA): Specially crafted inputs to idna.encode() can bypass CVE-2024-3651 fix
  Fixed in: 3.15
  Advisory information: https://github.com/kjd/idna/security/advisories/GHSA-65pc-fj4g-8rjx

uv add, uv sync usw. können nun bei jedem Synchronisierungsvorgang nach zuvor identifizierter Malware suchen. Diese Funktion ist standardmäßig nicht aktiviert, sie kann jedoch mit UV_MALWARE_CHECK=1 in der Shell einfach ermöglicht werden.

Wenn in einer Abhängigkeit eine Schwachstelle gefunden wird, solltet ihr auf eine nicht-anfällige Version aktualisieren; wenn kein Update verfügbar ist, solltet ihr überlegen, die Abhängigkeit zu entfernen.

Wenn ihr glaubt, dass die Sicherheitslücke euer Projekt nicht betrifft, kann für uv audit in der pyproject.toml-Datei Ausnahmen definiert werden, z.B.:

pyproject.toml
[tool.uv.audit]
ignore = ["PYSEC-2022-43017", "GHSA-5239-wwwm-4pmq"]

oder besser:

pyproject.toml
[tool.uv.audit]
ignore-until-fixed = ["PYSEC-2022-43017"]

Ihr könnt die Schwachstellenanalyse mit uv-audit auch in eure prek-Checks übernehmen:

- repo: https://github.com/astral-sh/uv-pre-commit
  rev: d9fca3320346514799461a80b0753eb45d707d46 # 0.11.28
  hooks:
  - id: uv-audit
    files: ^(uv\.lock|pyproject\.toml)$

Sicherheitsprüfungen sollten automatisiert durchgeführt werden. Hierzu könnt ihr uv audit z.B . in einer GitHub Action verwenden:

name: Security Scan
jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
      - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
      - run: uv audit

oder in einer GitLb CI/CD-Pipeline:

security-scan:
  image: ghcr.io/astral-sh/uv:python3.14
  script:
    - uv audit

Alternativ zu uv audit könnt ihr hierfür auch osv oder pip-audit verwenden.

Vermeidet Abhängigkeitskonflikte

Abhängigkeitskonflikte können durch die Art und Weise entstehen, wie Paketmanager Namen auflösen, wenn sowohl öffentliche als auch private Paketverzeichnisse verwendet werden. Ein bösartiges Paket, das auf PyPI veröffentlicht wurde und denselben Namen wie euer internes Paket trägt, kann vom Build-System stattdessen installiert werden. Bei pip funktioniert der Angriff wie folgt:

  1. Beim Aufruf von python -m pip install --extra-index-url https://EXAPMPLE.COM/simple MYPACKAGE o. ä. würdet ihr vermutlich erwarten, dass MYPACKAGE von eurem Index https://EXAPMPLE.COM/simple geladen würde.

  2. pip schaut jeddoch in allen Indexen nach und wählt die höchste Version aus.

  3. Liegt also auf PyPI eine höhere Version von MYPACKAGE mit bösartigem Code, wird diese installiert.

Mit --index-url für einen einzelnen Index könnt ihr dieses Problem umgehen. Dabei geht pip davon aus, dass euer interner Index als Proxy für den öffentlichen PyPI fungiert; falls er jedoch nur interne Pakete hostet, könnt ihr ihn zunächst als PyPI-Proxy konfigurieren:

pip.conf
[install]
index-url = https://EXAPMPLE.COM/simple
trusted-host = EXAPMPLE.COM

SBOMs können dabei helfen, potenzielle Namenskonflikte aufzudecken, indem sie eine Bestandsliste zur Überprüfung bereitstellen; es handelt sich dabei jedoch um nachträgliche Kontrollmaßnahmen – sie zeigen euch also erst im Nachhinein, was ihr installiert habt.

uv verwendet hingegen üblicherweise die first-index-Strategie, nimmtalso den erstgenannten Index, in dem ein Paket gefunden wird. Dadurch werden die oben beschriebenen Abhängigkeitskonflikte vermieden:

pyoroject.toml
1[[tool.uv.index]]
2name = "internal"
3url = "https://EXAPMPLE.COM/simple"
4explicit = true
5
6[tool.uv.sources]
7mypackage = { index = "internal" }
Zeile 4:

Diesen Index wird nur für explizit angeheftete Pakete verwendet.


Im folgenden schauen wir uns nun an, wie die Abhängigkeiten in unseren Python-Projekten abgesichert werden kann. Dabei orientieren wir uns an der OpenSSF Scorecard. Alternativ könnt ihr euch auch an ISO/IEC 5230/OpenChain orientieren.

In einem früheren Abschnitt haben wir schon einige Hinweise gegeben, wie die Veröffentlichung von Python-Paketen auf PyPI abgesichert werden kann:

Siehe auch

Für ein umfassenderes Bedrohungsmodell über alle Ökosysteme hinweg ist das CNCF Software Supply Chain Security Whitepaper eine gute Einführung.

Nun wollen wir uns anschauen, wie Python-Projekte weiter abgesichert werden können. Dabei orientieren wir uns an der OpenSSF Scorecard. Alternativ könnt ihr euch auch an ISO/IEC 5230/OpenChain orientieren.

Wartung

Werden die Abhängigkeiten noch gewartet?

Risiko: Hoch

Dies weist auf möglicherweise ungepatchte Sicherheitslücken hin. Daher sollte regelmäßig überprüft werden, ob ein Projekt archiviert wurde. Umgekehrt wird bei der OSSF-Scorecard davon ausgegangen, dass bei mindestens einem Commit in der Woche über 90 Tage hinweg das Projekt sehr aktiv gewartet wird. Ein Mangel an aktiver Wartung ist jedoch nicht unbedingt immer ein Problem: insbesondere kleinere Dienstprogramme müssen normalerweise nicht oder nur sehr selten gewartet werden. Fehlende aktive Wartung weist euch also nur darauf hin, dass ihr die Situation genauer untersuchen solltet.

Ihr könnt euch die Aktivitäten eines Projekts auch mit Badges anzeigen lassen, z.B.:

Jährliche Commit-Aktivität Monatliche Commit-Aktivität Wöchentliche Commit-Aktivität

Gibt es ein Sicherheitskonzept für das Projekt?

Risiko: Mittel

Idealerweise sollte mit dem Projekt eine SECURITY-Datei o.ä. veröffentlicht worden sein. Diese Datei sollte Informationen enthalten,

  • wie eine Sicherheitslücke gemeldet werden kann ohne dass sie öffentlich sichtbar wird,

  • über den Ablauf und den Zeitplan für die Offenlegung der Schwachstelle,

  • zu Links, z.B. URLs und E-Mails, unter denen Unterstützung angefragt werden kann.

Enthält das Projekt eine verwendbare Lizenz?

Risiko: Niedrig

Eine Lizenz weist darauf hin, wie der Quellcode verwendet werden darf oder nicht. Das Fehlen einer Lizenz erschwert jede Art von Sicherheitsüberprüfung oder Audit und stellt ein rechtliches Risiko für die potenzielle Nutzung dar.

OpenSSF-Scorecard verwendet die GitHub License API für auf GitHub gehostete Projekte, ansonsten eine eigene Heuristik, um eine veröffentlichte Lizenzdatei zu erkennen. Dateien in einem LICENSES-Verzeichnis sollten mit ihrem SPDX-Lizenzbezeichner benannt werden, gefolgt von einer entsprechenden Dateierweiterung, wie in der REUSE-Spezifikation beschrieben.

OpenSSF Best Practices Badge

Risiko: Niedrig

Mit dem OpenSSF Best Practices Badge Programm könnt ihr euch auch ein entsprechendes Badge holen.

Kontinuierliches Testen

Werden im Projekt CI-Tests durchgeführt?

Risiko: Niedrig

Bevor Code in Pull- oder Merge-Requests zusammengeführt wird, sollten Tests durchgeführt werden, die dabei helfen, Fehler frühzeitig zu erkennen und die Anzahl der Schwachstellen in einem Projekt zu reduzieren.

Verwendet das Projekt Fuzzing-Tools?

Risiko: Mittel

Fuzzing oder Fuzz-Testing übergibt unerwartete oder zufällige Daten an euer Programm, um Fehler zu entdecken. Regelmäßiges Fuzzing ist wichtig, um Schwachstellen aufzuspüren, die von anderen ausgenutzt werden können, zumal auch bei einem Angriff Fuzzing genutzt werden kann, um dieselben Schwachstellen zu finden.

  • Verwendet euer Projekt Fuzzing?

  • Ist der Name des Repository in der OSS-Fuzz-Projektliste enthalten?

  • Wird ClusterFuzzLite im Repository eingesetzt?

  • Sind benutzerdefinierte sprachenspezifische Fuzzing-Funktionen im Repository vorhanden, z.B. mit atheris?

Verwendet euer Projekt Werkzeuge zur statischen Codeanalyse?

Risiko: Mittel

Statische Testverfahren testen den Quellcode, bevor die Anwendung ausgeführt wird. Dies kann verhindern, dass bekannte Fehlerklassen versehentlich in die Codebasis eingeführt werden.

Mit Bandit, das ihr mit Ruff verwenden könnt lassen sich u. a. folgende Schwachstellen überprüfen:

Regel

Beschreibung

S105

fest codierte Geheimnisse

S301

Pickle und andere unsichere Deserialisierung

S307

Verwendung von eval() mit nicht vertrauenswürdigen Eingaben

S113

fehlende Zeitüberschreitungen

S324

schwache Kryptografie wie z. B. MD5-Kollisionen

S608

SQL-Injection über Zeichenfolgenformatierung

Bandit könnt ihr auch in Jupyter Notebooks, IDEs und prek integrieren.

Zudem könnt ihr Pysa für Taint-Analysen verwenden.

Für GitHub-Repositories könnt ihr alternativ auch CodeQL verwenden; s.a. codeql-action.

Risikobewertung des Quellcodes

Ist das Projekt frei von eingecheckten Binärdateien?

Risiko: Hoch

Generierte ausführbare Dateien im Quellcode-Repository (z.B. Java .class-Dateien, Python .pyc Dateien) erhöhen das Risiko, da sie schwer überprüft werden können, so dass sie veraltet oder böswillig manipuliert sein können. Diesen Problemen kann mit verifizierten, reproduzierbaren Builds begegnet werden, deren ausführbare Dateien jedoch nicht wieder im Quellcode-Repository landen sollten.

Ist der Entwicklungsprozess anfällig für das Einschleusen von bösartigem Code?

Risiko: Hoch

Mit geschützten Git-Zweigen können Regeln für die Übernahme von Änderungen in Standard- und Veröffentlichungszweige definiert werden, z.B. automatisierte statische Code-Analysen mit flake8, Pysa, Wily und Code-Reviews über sog. Merge-Requests.

Werden Code-Reviews durchgeführt?

Risiko: Hoch

Mit Code-Reviews lassen sich unbeabsichtigte Schwachstellen oder das mögliche Einschleusen von bösartigem Code erkennen. Ggf. können so Angriffe aufgespürt werden, bei denen das Konto eines Teammitglieds unterwandert wurde.

Wirken an dem Projekt Personen aus mehreren Organisationen mit?

Risiko: Niedrig

Dies wird als Indiz für eine geringere Anzahl von vertrauenswürdigen Code-Reviewers gewertet. Hierfür kann in den Profilen nach unterschiedlichen Einträgen im Feld Unternehmen gesucht werden. Wünschenswert sind mindestens drei verschiedene Unternehmen in den letzten 30 Commits, wobei jedes dieser Teammitglieder mindestens fünf Commits gemacht haben sollte.

Risikobewertung der Builds

Werden im Projekt Abhängigkeiten deklariert und festgeschrieben?

Risiko: Mittel

In eurem Projekt sollten Abhängigkeiten, die während des Build- und Release-Prozesses verwendet werden, festgeschrieben werden. Dabei sollte eine gepinnte Abhängigkeit explizit auf einen bestimmten Hash gesetzt sein und nicht nur auf eine veränderbare Version oder einen Versionsbereich.

Spack schreibt für die jeweilige Umgebung diese Hashes in spack.lock, uv in uv.lock-Datei fest.

Tipp

Üblicherweise verwalte ich diese Dateien jedoch nur bei Apps in Git. Bei Bibliotheken schränke ich üblicherweise lediglich den Versionsbereich der Abhängigkeiten in der pyproject.toml-Datei ein.

Für Apps können sich dadurch die folgenden Sicherheitsrisiken verringern:

  • Die Prüfung und Bereitstellung erfolgt mit derselben Software, was die Risiken beim Deployment verringert, die Fehlersuche vereinfacht und Reproduzierbarkeit ermöglicht.

  • Kompromittierte Abhängigkeiten untergraben nicht die Sicherheit des Projekts.

  • Substitutionsangriffe, also Angriffe, die auf die Verwechslung von Abhängigkeiten abzielen, kann so entgegengewirkt werden.

Das Festschreiben der Abhängigkeiten sollte jedoch Software-Updates nicht verhindern. Ihr könnt dieses Risiko verringern durch

  • automatisierte Werkzeuge, die euch benachrichtigen, wenn Abhängigkeiten in eurem Projekt veraltet sind

  • Anwendungen, die Abhängigkeiten festhalten, schnell aktualisieren.