Python-Abhängigkeiten absichern, bevor ein KI-Agent ein Paket installiert

8 Minuten Lesezeit English

Ein dunkler Arbeitsplatz mit einer Software-Abhängigkeitskette und einem isolierten verdächtigen Paket (KI-generiert)

Die Sicherheit der Software-Lieferkette ist kein Thema, mit dem man auf LinkedIn Applaus sammelt.

Genau deshalb wird es in vielen Teams übergangen.

In Python ist das gefährlich. Jedes Mal, wenn du ein Paket installierst und importierst, gibst du fremdem Code die Chance, auf deinem Rechner, in deiner CI-Umgebung oder im Produktions-Build zu laufen.

Das ist okay, wenn das Paket sauber ist.

Es wird hässlich, wenn das Paket kompromittiert wurde.

Die jüngsten Angriffe auf npm und PyPI folgen einem einfachen Muster. Ein Maintainer-Konto wird per Phishing übernommen. Ein CI-Token läuft aus. Ein Paket mit ähnlich klingendem Namen wird veröffentlicht. Eine kurzlebige bösartige Version landet in der Paketquelle. Dann installiert ein Entwickler, ein Build-Server oder ein KI-Agent dieses Paket.

Ab dort braucht der Angreifer keine Magie.

Er sucht nach Umgebungsvariablen, SSH-Schlüsseln, API-Tokens, Cloud-Zugängen, Tokens zum Veröffentlichen von Paketen und allem anderen, was der Prozess lesen kann. Im schlimmsten Fall verbreitet sich der Angriff über gestohlene Tokens weiter.

Das heißt nicht, dass du keine Abhängigkeiten mehr nutzen solltest.

Es heißt, dass du Abhängigkeiten nicht mehr wie etwas Kostenloses behandeln solltest.

Vor allem jetzt, wo KI-Agenten Pakete hinzufügen können, während du kaum hinschaust.

Diese Standards würde ich heute in jedes Python-Projekt legen.

Wenn du mit JavaScript oder TypeScript arbeitest, bleib kurz dabei. Am Ende gibt es einen eigenen Abschnitt zu npm.

1. Nutze pip nicht mehr direkt für Projektinstallationen

pip ist nicht schlecht.

Aber pip install irgendwas ist zu schnell ausgeführt, ohne dass genug Projektregeln zurückbleiben.

Für Anwendungsprojekte würde ich uv nutzen.

Nicht weil es gerade beliebt ist. Sondern weil du damit eine Projektdatei, ein Lockfile, schnelle Installationen und Einstellungen bekommst, die unsichere Abhängigkeitsänderungen deutlich erschweren.

Starte ein Projekt so:

uv init
uv add pydantic

Das erzeugt eine pyproject.toml und eine uv.lock.

Die pyproject.toml beschreibt, was dein Projekt will.

Die uv.lock hält fest, was wirklich aufgelöst wurde.

Beides zählt.

Eine requirements.txt kann in manchen Umgebungen weiter sinnvoll sein. Aber wenn dein Hauptsetup immer noch aus einer langen Paketliste und der Gewohnheit besteht, pip install -r requirements.txt auszuführen, fehlt dir ein einfacher Ort für bessere Sicherheitsregeln.

Du willst Abhängigkeitsregeln in der Versionskontrolle haben.

Nicht im Gedächtnis von irgendjemandem.

2. Pinne neue direkte Abhängigkeiten exakt

Viele Werkzeuge fügen Abhängigkeiten standardmäßig mit einer unteren Versionsgrenze hinzu.

Du fragst nach pydantic, und im Projekt steht dann etwa:

dependencies = [
  "pydantic>=2.12.0",
]

Das heißt, eine spätere Installation kann eine neuere Version nehmen.

Meistens ist das bequem.

Während eines Angriffs auf die Software-Lieferkette ist Bequemlichkeit nicht dein Freund.

Für Anwendungen bevorzuge ich exakte Grenzen bei direkten Abhängigkeiten. In uv fügst du das in deiner pyproject.toml hinzu:

[tool.uv]
add-bounds = "exact"

Wenn du danach ein Paket hinzufügst, schreibt uv die direkte Abhängigkeit mit einer exakten Version:

dependencies = [
  "pydantic==2.12.0",
]

Das ersetzt kein Lockfile. Indirekte Abhängigkeiten müssen weiterhin aufgelöst und festgeschrieben werden.

Aber es ändert den Standard von “nimm alles Neuere” zu “nimm die Version, die ich gewählt habe”.

Das ist für produktive Anwendungen der bessere Standard.

Aktualisierungen sollten bewusst passieren. Jemand sollte das Update ausführen, die Änderung prüfen, das Paket anschauen und das aktualisierte Lockfile einchecken.

Wenn dein KI-Agent still ein Paket auf eine neuere Version ziehen kann, ohne zu fragen, hast du keine Automatisierung.

Du hast unbeaufsichtigtes Vertrauen.

3. Baue ein Wartefenster für neue Versionen ein

Viele bösartige Paketversionen bleiben nicht lange online.

Das hilft dir aber nicht, wenn deine CI das Paket in der ersten Stunde installiert.

Deshalb mag ich ein Wartefenster für neue Veröffentlichungen.

In uv kannst du dem Resolver sagen, dass er Paketversionen ignorieren soll, die jünger als ein bestimmter Zeitraum sind:

[tool.uv]
add-bounds = "exact"
exclude-newer = "7 days"

Das genaue Fenster ist eine Teamentscheidung.

Sieben Tage sind für viele Anwendungsteams ein guter Start. Das gibt dem Ökosystem Zeit, offensichtliche bösartige Versionen zu finden, bevor dein Projekt sie auflösen darf.

Perfekt ist das nicht.

Ein Angreifer kann länger als eine Woche still bleiben. Ein wichtiger legitimer Patch kann auch jünger als sieben Tage sein. Du brauchst weiter Urteilskraft.

Aber die meisten Teams verpassen nicht jeden Morgen dringende Paketversionen.

Sie sind offen, weil jede frische Veröffentlichung standardmäßig in ihr Projekt kommen kann.

Ändere diesen Standard.

Und kopiere die Einstellung nicht nur, weil irgendjemand im Internet sie empfohlen hat. Teste sie in einem Wegwerfprojekt. Füge ein Paket hinzu. Setze das Wartefenster auf einen sehr großen Wert. Füge das Paket erneut hinzu. Schau, welche Version aufgelöst wird.

Du solltest verstehen, was eine Schutzmaßnahme tut, bevor du ihr vertraust.

4. Nutze locked sync in CI

Das Lockfile ist nur nützlich, wenn dein Installationsprozess es respektiert.

Diesen Befehl will ich in CI sehen:

uv sync --locked

Das Flag --locked sagt uv, dass das Lockfile während des Syncs nicht aktualisiert werden darf.

Wenn pyproject.toml und uv.lock nicht zusammenpassen, schlägt der Befehl fehl.

Genau das willst du.

Stell dir vor, ein KI-Agent fügt ein Paket zur pyproject.toml hinzu, aber das Lockfile ändert sich nicht. Oder jemand bearbeitet eine Abhängigkeit von Hand, ohne sauber neu aufzulösen. Ein normaler Sync kann diesen Fehler verdecken, indem er die Umgebung einfach aktualisiert.

Ein locked sync macht daraus einen sichtbaren Fehler.

Dieser Fehler ist nicht lästig.

Er zeigt, dass das System seinen Job macht.

Lokal nutze ich locked sync auch gern, wenn ich gerade keine Abhängigkeiten ändere. So fallen unbeabsichtigte Paketänderungen früh auf, bevor sie in CI überraschen.

5. Sag deinen KI-Agenten, dass sie keine Pakete ohne Rückfrage hinzufügen dürfen

Diesen Teil übersehen viele Teams.

Der Paketmanager ist nur eine Ebene. Dein KI-Agent ist eine weitere.

Agenten greifen schnell zu neuen Abhängigkeiten. Du fragst nach einer kleinen neuen Funktion, und plötzlich installieren sie einen neuen Parser, Formatter, HTTP-Helfer, CLI-Wrapper, Test-Plugin oder irgendein Paket, das in einem generierten Plan auftauchte.

Diese Gewohnheit war schon vorher unsauber.

Jetzt ist sie ein Sicherheitsproblem.

Lege eine Abhängigkeitsregel in deine AGENTS.md, CLAUDE.md oder die Anweisungsdatei, die dein Team nutzt:

## Dependencies

Do not add, remove, or upgrade Python dependencies without asking first.

Before proposing a new dependency:
- Check whether the standard library or an existing dependency can solve it.
- Verify the package name on PyPI.
- Prefer maintained packages with clear ownership and recent activity.
- Use `uv add`, never `pip install`, for project dependencies.
- Include the `pyproject.toml` and `uv.lock` changes in the same diff.

Diese eine Regel blockiert viel Unsinn.

Sie bringt das Team auch zu einer besseren Frage:

Verdient diese Abhängigkeit ihren Platz?

Manchmal lautet die Antwort ja. Kryptografie, HTTP-Clients, Datenbanktreiber oder ernsthafte Parsing-Logik selbst nachzubauen, ist eine schlechte Idee.

Manchmal lautet die Antwort nein. Wenn du nur einen Helfer mit 30 Zeilen aus einem Paket mit 4.000 Zeilen brauchst, übernimm die 30 Zeilen.

Abhängigkeiten sind nicht kostenlos. Sie bringen Code, Maintainer, Veröffentlichungsprozesse, indirekte Abhängigkeiten, Lizenzen, Schwachstellen und Lieferkettenrisiko mit.

Behandle das wie eine technische Entscheidung, nicht wie einen Reflex.

Wenn du npm, pnpm, Yarn oder Bun nutzt

uv ist nur für Python.

Das JavaScript- und TypeScript-Ökosystem braucht die gleiche Disziplin, nur mit anderen Befehlen.

Wenn du npm nutzt, committe deine package-lock.json und nutze in CI:

npm ci

Nutze in CI nicht npm install, außer du willst ausdrücklich, dass npm den Abhängigkeitsbaum aktualisiert.

npm ci braucht ein vorhandenes Lockfile. Wenn package.json und package-lock.json nicht zusammenpassen, schlägt der Befehl fehl, statt das Lockfile still zu ändern. Genau dieses Verhalten willst du.

Lege dann eine projektweite .npmrc an:

save-exact=true
min-release-age=7

Oder erstelle sie mit npm selbst:

npm config set save-exact true --location=project
npm config set min-release-age 7 --location=project

save-exact=true sorgt dafür, dass neue direkte Abhängigkeiten als exakte Versionen gespeichert werden, nicht als Caret-Ranges.

min-release-age=7 sagt aktuellem npm, dass es bei neuen Auflösungen keine Versionen nehmen soll, die vor weniger als sieben Tagen veröffentlicht wurden.

Wenn deine npm-Version min-release-age nicht unterstützt, aktualisiere npm oder erzwinge die Verzögerung an anderer Stelle, zum Beispiel über eine private Registry, einen Dependency Proxy oder eine Regel in deinem Update-Bot. Ein Lockfile schützt wiederholbare Installationen. Es erzeugt kein Wartefenster für neue Auflösungen.

Die gängigen Alternativen haben ähnliche Kontrollen:

# pnpm-workspace.yaml
minimumReleaseAge: 10080
minimumReleaseAgeStrict: true
pnpm install --frozen-lockfile

pnpm nutzt Minuten, also sind 10080 sieben Tage.

Für Yarn 4:

# .yarnrc.yml
defaultSemverRangePrefix: ""
npmMinimalAgeGate: "7d"
yarn install --immutable

Für Bun:

# bunfig.toml
[install]
minimumReleaseAge = 604800
frozenLockfile = true

Bun nutzt Sekunden, also sind 604800 sieben Tage.

Die Namen unterscheiden sich, aber die Regel bleibt gleich: exakte direkte Abhängigkeiten, eingecheckte Lockfiles, eingefrorene Installationen in CI und ein Wartefenster, bevor frische Paketversionen in dein Projekt kommen.

Die einfache Regel

Du kannst Angriffe auf die Software-Lieferkette nicht unmöglich machen.

Aber du kannst den einfachen Weg versperren.

Nutze uv für Python. Nutze npm ci und projektweite npm-Einstellungen für JavaScript, oder die entsprechenden Kontrollen in pnpm, Yarn oder Bun. Pinne direkte Abhängigkeiten exakt. Füge ein Wartefenster für neue Versionen hinzu. Nutze eingefrorene Installationen in CI. Sag deinen KI-Agenten, dass neue Pakete eine Freigabe brauchen.

Das schützt dich nicht vor jedem Angriff.

Es schützt dich vor vielen leichtsinnigen.

Und das zählt, weil die meisten Sicherheitsvorfälle nicht mit einem genialen Angreifer und einem Filmplot anfangen.

Sie fangen mit einem normalen Entwicklerablauf an, der einem Paket zu viel vertraut hat.

Wenn dein Team KI-Agenten in einer Python-Codebasis nutzt und sinnvolle technische Standards setzen will, bevor die nächste Abhängigkeit landet, ist genau das ein Thema für eine Fractional-CTO-Begleitung.