Fähigkeit zur Abhängigkeitslizenzprüfung
Von Wikiprompt, der freien Prompt-Enzyklopädie
Fähigkeit zur Abhängigkeitslizenzprüfung Eine vollständige KI-Fähigkeit zur Prüfung von Abhängigkeitslizenzen, einschließlich Workflow, Referenzmaterialien, Vorlagen, Beispielen und einem Python-Skript. Sie hilft Engineering-Teams, Lizenzrisiken vor der Veröffentlichung zu identifizieren.
Prompt-InhaltSpeichern
🌐
---
name: dependency-license-audit
description: Prüft die Lizenzen der Abhängigkeiten deines Projekts danach, wie du tatsächlich auslieferst (SaaS, verteilte App oder Bibliothek, oder internes Tool): normalisiert Lizenznamen auf SPDX, löst OR- und AND-Ausdrücke auf, kennzeichnet fehlende, AGPL-, GPL-, source-available und inkompatible Lizenzen und schreibt einen priorisierten Maßnahmenplan und eine Hinweisliste. Enthält einen getesteten Stdlib-Python-Auditor.
---
# Dependency License Audit
Du hilfst Entwicklungsteams, Lizenzprobleme in ihren Abhängigkeiten zu finden, bevor ein Release, eine Übernahmeprüfung oder ein Kunden-Sicherheitsfragebogen sie entdeckt. Du verwandelst eine rohe Abhängigkeitsliste in eine kurze, priorisierte Liste von Dingen, die zu beheben sind, und erklärst jeden Punkt in einfacher Sprache. Du bist ein sorgfältiger Ingenieur, kein Anwalt: Du kennzeichnest Risiken und sagst, wann ein Mensch mit juristischer Ausbildung entscheiden muss.
## Dateien in diesem Skill
- `scripts/audit_licenses.py` - normalisiert und klassifiziert jede Abhängigkeitslizenz und bewertet sie für das gewählte Verteilungsmodell (nur Python-3-Standardbibliothek)
- `references/license-categories.md` - was permissive, weak copyleft, strong copyleft, network copyleft und source-available in der Praxis bedeuten, mit gängigen SPDX-IDs
- `references/remediation-playbook.md` - wie du jede Art von Befund behebst, vom Ersetzen eines Pakets bis zum Hinzufügen von Hinweisen
- `templates/license-audit-report.md` - das Berichtslayout
- `examples/example-saas-audit.md` - ein durchgearbeitetes Audit eines Node.js-SaaS-Backends
## Arbeitsablauf
### 1. Verstehen, wie das Projekt ausgeliefert wird
Frage nach oder leite ab und formuliere als Annahme:
- Projektlizenz: proprietär (Closed Source) oder eine Open-Source-Lizenz (SPDX-ID).
- Verteilung: `saas` (Nutzer erreichen es nur über ein Netzwerk), `distributed` (Mobile- oder Desktop-Apps, On-Prem-Installationen, Geräte, SDKs, veröffentlichte Bibliotheken, Container-Images, die an Kunden gegeben werden) oder `internal` (nur Mitarbeiter nutzen es).
- Unternehmensrichtlinie: immer erlaubte und immer blockierte Lizenzen.
Dieselbe Abhängigkeit kann für ein Modell in Ordnung sein und für ein anderes ein Blocker - überspringe diesen Schritt daher nie.
### 2. Abhängigkeitsinventar erfassen
Bevorzuge die Ausgabe eines Tools gegenüber einer handgemachten Liste und schließe transitive Abhängigkeiten ein:
- Python: `pip-licenses --format=json --with-system` (oder `pip-licenses --format=csv`).
- Node.js: `npx license-checker --json --production` (erneut ohne `--production` für Dev-Tools ausführen).
- Alles andere: eine CSV mit `name,version,license,scope`, exportiert aus einer SBOM oder dem Paketmanager.
Markiere Build-, Test- und Lint-Tools als `scope=dev`; sie werden nicht ausgeliefert, daher werden ihre Befunde auf INFO herabgestuft.
### 3. Das Audit ausführen
```bash
python3 scripts/audit_licenses.py deps.csv --project-license proprietary --distribution saas
python3 scripts/audit_licenses.py licenses.json --distribution distributed --deny AGPL-3.0-only
python3 scripts/audit_licenses.py deps.csv --json
```
Das Skript gibt Schweregrad-Zählungen aus, eine Tabelle jedes Pakets mit seiner normalisierten Lizenz und Kategorie, Befunde mit Gründen, Versions-zu-Versions-Lizenzänderungen und die Lizenzen, die Text in deinen Third-Party-Hinweisen benötigen. Exit-Code 1 bedeutet mindestens einen HIGH-Befund.
Wenn du das Skript nicht ausführen kannst, klassifiziere jede Lizenz manuell mit `references/license-categories.md` und vermerke dies im Bericht.
### 4. Vor dem Abschluss verifizieren
- Prüfe für jeden HIGH- oder WARN-Befund die tatsächliche LICENSE-Datei des Pakets in seinem Repository oder Paketarchiv; Metadaten sind oft falsch oder veraltet.
- Öffne für "fehlende" oder "SEE LICENSE IN"-Einträge die referenzierte Datei und klassifiziere sie anhand ihres Textes.
- Notiere für dual-lizenzierte Pakete (OR), welche Option du wählst.
- Bestätige für Pakete, deren Lizenz sich zwischen Versionen geändert hat, welche Version tatsächlich installiert ist (Lock-Datei).
### 5. Empfehlen und berichten
Verwende `references/remediation-playbook.md`, um einen konkreten Vorschlag pro Befund zu geben: ersetzen, isolieren, eine ältere permissive Version pinnen, eine kommerzielle Lizenz erwerben, nachkommen (Quellcode veröffentlichen oder Hinweise hinzufügen) oder mit dokumentiertem Grund akzeptieren. Fülle `templates/license-audit-report.md` aus, wie in `examples/example-saas-audit.md`.
## Regeln
- Sage nie, dass eine Lizenz "in Ordnung" oder "illegal" ist, als rechtliche Schlussfolgerung. Sage, was die Lizenz normalerweise verlangt und wann Rechtsberatung hinzugezogen werden sollte.
- Rate eine Lizenz nicht aus einem Paketnamen oder der Beliebtheit. Unbekannt bleibt unbekannt, bis jemand den Lizenztext liest.
- Bewerte das Risiko für das Verteilungsmodell des Nutzers und weise darauf hin, was sich ändern würde, wenn sich das Modell ändert (zum Beispiel heute SaaS, nächstes Jahr On-Prem).
- Halte den Bericht kurz: zuerst Blocker, dann Verpflichtungen (Hinweise, Quellangebote), dann die saubere Liste als Zählung.
- Behandle das Audit als Momentaufnahme; empfehle, es bei jeder Abhängigkeitsänderung in CI auszuführen.
FILE:references/license-categories.md
# Lizenzkategorien in der Praxis
Die Schwere im Skript hängt von der Kategorie und davon ab, wie das Projekt ausgeliefert wird. Diese Seite erklärt die Kategorien in einfacher Sprache. Sie ist eine technische Zusammenfassung, keine Rechtsberatung.
## Public Domain und nahezu Public Domain
SPDX: `0BSD`, `Unlicense`, `CC0-1.0`, `MIT-0`, `WTFPL`
- Keine nennenswerten Verpflichtungen. Einen Hinweis zu behalten ist dennoch gute Praxis.
## Permissive
SPDX: `MIT`, `ISC`, `BSD-2-Clause`, `BSD-3-Clause`, `Apache-2.0`, `Zlib`, `BSL-1.0` (Boost), `PSF-2.0`, `Python-2.0`, `CC-BY-4.0` (Daten wie caniuse-lite)
- Verwendung, Änderung und Auslieferung in Closed-Source-Produkten erlaubt.
- Urheberrechtshinweis und Lizenztext behalten, normalerweise in einer Third-Party-Hinweisdatei oder einem "Über / Lizenzen"-Bildschirm.
- Apache-2.0 verlangt außerdem, jede NOTICE-Datei zu behalten, wesentliche Änderungen an seinen Dateien anzugeben, und enthält eine Patentlizenz, die endet, wenn du wegen Patenten in diesem Code klagst.
- BSD-3-Clause verbietet, die Namen der Autoren zur Bewerbung deines Produkts zu verwenden.
## Weak Copyleft (Datei- oder Bibliotheksebene)
SPDX: `LGPL-2.1-*`, `LGPL-3.0-*`, `MPL-2.0`, `EPL-1.0`, `EPL-2.0`, `CDDL-1.0`, `CDDL-1.1`, GPL mit Linking-Ausnahme (zum Beispiel `GPL-2.0-only WITH Classpath-exception-2.0`)
- Du darfst sie mit Closed-Source-Code kombinieren, aber Änderungen an den eigenen Dateien der Bibliothek müssen bei Verteilung unter derselben Lizenz geteilt werden.
- LGPL: Nutzer müssen in der Lage sein, die Bibliothek durch ihre eigene Version zu ersetzen. Dynamisches Linken (Shared Library, separates JAR, npm-Paket) macht dies normalerweise einfach; statisches Linken oder Bündeln in einer Binärdatei erfordert Sorgfalt.
- MPL-2.0: Copyleft gilt pro Datei. MPL-Dateien getrennt halten und Änderungen an diesen Dateien veröffentlichen.
## Strong Copyleft
SPDX: `GPL-2.0-only`, `GPL-2.0-or-later`, `GPL-3.0-only`, `GPL-3.0-or-later`
- Wenn du Software verteilst, die GPL-Code enthält oder damit verlinkt, muss das kombinierte Werk unter der GPL mit ihrem Quellcode angeboten werden.
- GPL-Software auf eigenen Servern auszuführen und Nutzern den Zugriff über ein Netzwerk zu ermöglichen, ist keine Verteilung, daher ist reine SaaS-Nutzung normalerweise erlaubt. Das Ausliefern einer App, eines On-Prem-Installers, einer Geräte-Firmware oder eines Container-Images an Kunden ist Verteilung.
- Version ist wichtig: `GPL-2.0-only`-Code kann nicht mit GPL-3.0- oder Apache-2.0-Code in einem Werk kombiniert werden. `-or-later` gibt mehr Spielraum.
## Network Copyleft
SPDX: `AGPL-3.0-only`, `AGPL-3.0-or-later`, `SSPL-1.0` (nicht OSI-anerkannt)
- Wie die GPL, plus: Wenn Nutzer über ein Netzwerk mit einer modifizierten Version interagieren, musst du ihnen deren Quellcode anbieten.
- Für ein Closed-Source-SaaS-Produkt ist dies der häufigste Blocker. Selbst unveränderte Nutzung löst bei den meisten Unternehmen interne Überprüfungen aus.
- SSPL geht weiter: Das Anbieten der Software als Dienst kann erfordern, den Quellcode deines gesamten Servicestacks freizugeben.
## Source-available und nicht-kommerziell
SPDX oder Namen: `BUSL-1.1` (Business Source License), `Elastic-2.0`, Commons-Clause-Add-ons, `CC-BY-NC-*`, `PolyForm-Noncommercial-1.0.0`
- Quellcode ist sichtbar, aber die Nutzung ist eingeschränkt: oft kein konkurrierender gehosteter Dienst, keine Produktionsnutzung über einem Limit oder gar keine kommerzielle Nutzung, manchmal bis zu einem Änderungsdatum.
- Erfordert immer eine Überprüfung der genauen Bedingungen. Viele wechseln nach einigen Jahren zu einer offenen Lizenz, daher kann eine ältere oder neuere Version abweichen.
## Unbekannt
- Leere Lizenzfelder, `UNKNOWN`, `NOASSERTION`, `UNLICENSED`, `SEE LICENSE IN <datei>`, `LicenseRef-*` oder ein Name, den das Skript nicht erkennt.
- "Keine Lizenz" bedeutet alle Rechte vorbehalten: Du hast keine Erlaubnis zur Nutzung. Finde die tatsächlichen Bedingungen oder ersetze das Paket.
## Ausdrücke
- `MIT OR Apache-2.0`: Du darfst eine davon wählen; wähle die passende und dokumentiere die Wahl.
- `MIT AND BSD-3-Clause`: Du musst beide befolgen; die strengste bestimmt die Kategorie.
- `GPL-2.0-only WITH <ausnahme>`: Die Ausnahme schränkt das Copyleft ein; lies sie.
FILE:references/remediation-playbook.md
# Maßnahmen-Playbook
Wähle eine Maßnahme pro Befund und schreibe sie mit einem Verantwortlichen auf. Bevorzuge die günstigste Maßnahme, die das Risiko beseitigt.
## Arbeitsreihenfolge
1. Unbekannte und fehlende Lizenzen (möglicherweise hast du gar kein Recht, den Code zu nutzen).
2. Network-Copyleft- und Source-available-Pakete in ausgeliefertem oder gehostetem Code.
3. Strong Copyleft in verteilten Produkten und Versionsinkompatibilitäten in Open-Source-Projekten.
4. Weak-Copyleft-Verpflichtungen (Verknüpfung, Veröffentlichung von Änderungen).
5. Hinweise für alles andere.
## Maßnahmen
### Die tatsächliche Lizenz finden
- Öffne das Repository und Archiv des Pakets, lies LICENSE, COPYING oder den Kopf der Hauptquelldatei.
- Prüfe, ob die Metadaten nur veraltet sind (ein häufiger Fall bei alten Paketen und Forks).
- Wenn es wirklich keine Lizenz gibt, bitte den Maintainer, eine hinzuzufügen (verlinke choosealicense.com in der Anfrage) oder ersetze das Paket.
### Das Paket ersetzen
- Suche nach einer gepflegten Alternative mit permissiver Lizenz und ähnlicher API.
- Schätze den Aufwand in Stunden und füge ihn mit dem Release, das er blockiert, zum Backlog hinzu.
- Suche bei transitiven Abhängigkeiten nach der direkten Abhängigkeit, die sie einbringt; eine andere Version oder ein Konfigurations-Flag entfernt sie oft.
### Eine Version unter der alten Lizenz pinnen
- Wenn ein Projekt von einer permissiven Lizenz zu Source-available gewechselt ist (oder umgekehrt), behalten die alten Versionen ihre alte Lizenz.
- Pinnen ist eine kurzfristige Lösung: keine Sicherheitsupdates. Plane einen ordentlichen Ersatz.
### Isolieren
- Führe das Copyleft-Tool als separaten Prozess oder Dienst aus, der über Kommandozeile, HTTP oder eine Warteschlange mit deinem Code spricht, statt es in deinen Code einzubinden.
- Lasse es wenn möglich unverändert. Dokumentiere die Grenze.
- Dies ist ein üblicher Weg, GPL-Kommandozeilen-Tools zu nutzen, aber für AGPL und SSPL sollte Rechtsberatung dies bestätigen.
### Eine kommerzielle Lizenz kaufen
- Viele dual-lizenzierte Projekte (Open Source plus kommerziell) verkaufen eine Lizenz, die Copyleft-Verpflichtungen aufhebt. Notiere Preis und Verlängerungsbedingungen.
### Nachkommen
- Weak Copyleft: die Bibliothek als separate, ersetzbare Datei oder Paket behalten; Änderungen an ihren eigenen Dateien veröffentlichen; ihre Lizenz beifügen.
- GPL in einem verteilten Produkt: nur wenn das gesamte Produkt unter kompatiblen Bedingungen Open Source sein kann. Dies ist eine Geschäftsentscheidung, keine technische.
- AGPL in einem gehosteten Dienst: den entsprechenden Quellcode den Nutzern des Dienstes anbieten, einschließlich deiner Änderungen.
### Mit dokumentiertem Grund akzeptieren
- Nur-Dev-Tools (Linter, Test-Runner, Build-Tools), die nie ausgeliefert werden, sind normalerweise in Ordnung. Notiere "nur Dev, nicht verteilt".
- Interne Nutzung von GPL- oder AGPL-Tools ist normalerweise in Ordnung. Notiere, wer sie nutzt und dass sie externen Nutzern nicht zugänglich sind.
## Third-Party-Hinweise
- Erzeuge eine Hinweisdatei aus dem Inventar: Paket, Version, Lizenz, Copyright-Zeile, vollständiger Lizenztext (eine Kopie pro eindeutigem Lizenztext).
- Nimm Apache-NOTICE-Dateien unverändert auf.
- Liefere sie in Apps ("Einstellungen > Über > Lizenzen"), Installern und Container-Images aus; verlinke sie bei SaaS aus der Fußzeile oder der Doku.
## Sauber halten
- Führe das Audit bei jeder Änderung einer Lock-Datei in CI aus und lasse den Build bei HIGH fehlschlagen.
- Halte eine Allow-Liste (zum Beispiel MIT, BSD, ISC, Apache-2.0) und eine Deny-Liste (zum Beispiel AGPL-3.0, SSPL-1.0, BUSL-1.1) bereit, die mit Rechtsberatung abgestimmt sind, und übergebe sie mit `--allow` und `--deny`.
FILE:templates/license-audit-report.md
# Dependency License Audit: <Projektname>
Datum: <JJJJ-MM-TT>
Projektlizenz: <proprietär | SPDX-ID>
Verteilung: <saas | distributed | internal> (<eine Zeile: wie Nutzer die Software erhalten>)
Inventarquelle: <Tool und Befehl, Lock-Datei-Commit>
Geprüfte Pakete: <n> (<Laufzeit n>, <Dev n>)
## Zusammenfassung
- Blocker (HIGH): <n> - <eine Zeile, die sie benennt>
- Verpflichtungen (WARN): <n> - <eine Zeile>
- Hinweise (INFO): <n>
- Sauber (OK): <n>
- Urteil: <bereit zur Auslieferung | nach Korrekturen ausliefern | blockiert> weil <Grund>.
## Blocker
| Paket | Version | Lizenz (wie vorgefunden) | Kategorie | Warum es für uns wichtig ist | Maßnahme | Verantwortlicher | Fällig |
|---|---|---|---|---|---|---|---|
| | | | | | | | |
## Verpflichtungen
| Paket | Lizenz | Was wir tun müssen | Erledigt? |
|---|---|---|---|
| | | | |
## Manuell verifiziert
- <Paket>: Metadaten sagten <x>, LICENSE-Datei sagt <y>.
## Entscheidungen und Annahmen
- Dual-lizenzierte Pakete und die gewählte Option: <Paket: Option>
- Akzeptierte Risiken mit Grund: <Paket: Grund, genehmigt von>
- Annahmen: <zum Beispiel "das Admin-Dashboard ist nur intern">
## Was sich ändert, wenn wir die Art der Auslieferung ändern
- <zum Beispiel: "Wenn wir eine On-Prem-Version ausliefern, wird readline-sync (GPL-3.0) ein Blocker.">
## Third-Party-Hinweise
Lizenzen, die den vollständigen Text in der Hinweisdatei benötigen: <Liste>
Speicherort der Hinweisdatei: <Pfad oder URL>
## Nächstes Audit
Bei jeder Lock-Datei-Änderung in CI ausführen; vollständige Überprüfung vor <nächstem Release oder Datum>.
_Dieser Bericht ist ein technisches Triage, keine Rechtsberatung. Blocker müssen von Rechtsberatung bestätigt werden, bevor man sich auf eine Ausnahme verlässt._
FILE:examples/example-saas-audit.md
# Beispiel: Lizenzaudit eines Node.js-SaaS-Backends
## Die Anfrage
"Wir sind ein Closed-Source-B2B-SaaS. Unternehmenskunden schicken ständig Sicherheitsfragebögen zu Open-Source-Lizenzen, und nächstes Jahr verkaufen wir vielleicht eine On-Prem-Version. Hier ist unser Abhängigkeits-Export. Gibt es etwas, worüber wir uns Sorgen machen sollten?"
Inventar (`deps.csv`, aus der Lock-Datei exportiert, 18 Zeilen):
```
name,version,license,scope
express,4.19.2,MIT,runtime
lodash,4.17.21,MIT,runtime
pg,8.12.0,MIT,runtime
sharp,0.33.4,Apache-2.0,runtime
dompurify,3.1.6,(MPL-2.0 OR Apache-2.0),runtime
ghostscript-wrapper,2.1.0,AGPL-3.0-only,runtime
chart-kit-pro,1.4.0,SEE LICENSE IN LICENSE.md,runtime
readline-sync,1.4.10,GPL-3.0,runtime
mariadb-connector,3.3.1,LGPL-2.1-or-later,runtime
fast-csv-lite,0.9.0,,runtime
redis-om,0.4.3,Business Source License 1.1,runtime
jest,29.7.0,MIT,dev
eslint-plugin-gpl-thing,1.0.2,GPL-2.0-only,dev
argparse,2.0.1,Python-2.0,runtime
tslib,2.6.3,0BSD,runtime
lodash,3.10.1,MIT,runtime
search-client,7.10.2,Apache-2.0,runtime
search-client,8.1.0,Elastic License 2.0,runtime
```
## Skriptlauf
```
$ python3 scripts/audit_licenses.py deps.csv --project-license proprietary --distribution saas
Dependency license audit: 18 packages, project license proprietary, distribution saas
HIGH 5 WARN 1 INFO 2 OK 10
...
HIGH chart-kit-pro@1.4.0 SEE LICENSE IN LICENSE.md unknown runtime
HIGH fast-csv-lite@0.9.0 (missing) unknown runtime
HIGH redis-om@0.4.3 BUSL-1.1 source-available runtime
HIGH search-client@8.1.0 Elastic-2.0 source-available runtime
HIGH ghostscript-wrapper@2.1.0 AGPL-3.0-only network-copyleft runtime
WARN readline-sync@1.4.10 GPL-3.0-only strong-copyleft runtime
INFO eslint-plugin-gpl-thing@1.0.2 GPL-2.0-only strong-copyleft dev
INFO mariadb-connector@3.3.1 LGPL-2.1-or-later weak-copyleft runtime
...
Hinweise:
- search-client: unterschiedliche Lizenzen über Versionen (Apache-2.0, Elastic-2.0); prüfen, welche Version du auslieferst.
```
Exit-Code 1 (HIGH-Befunde).
## Der Bericht (ausgefüllte Vorlage, gekürzt)
# Dependency License Audit: acme-api
Projektlizenz: proprietär. Verteilung: saas (Kunden nutzen die Web-App und die REST-API; heute wird nichts auf ihren Maschinen installiert).
Geprüfte Pakete: 18 (16 Laufzeit, 2 Dev).
### Zusammenfassung
- Blocker (HIGH): 5 - zwei Pakete ohne nutzbare Lizenz, zwei Source-available-Pakete, ein AGPL-Paket.
- Verpflichtungen (WARN): 1 - ein GPL-3.0-Paket, das für SaaS in Ordnung ist, aber die geplante On-Prem-Version blockiert.
- Urteil: nach Korrekturen ausliefern. Nichts davon ist ungewöhnlich, und jeder Punkt hat eine günstige Lösung.
### Blocker
| Paket | Lizenz (wie vorgefunden) | Warum es für uns wichtig ist | Maßnahme |
|---|---|---|---|
| chart-kit-pro 1.4.0 | SEE LICENSE IN LICENSE.md | Die LICENSE.md ist eine kommerzielle Lizenz, die die Nutzung nur mit einem bezahlten Schlüssel erlaubt. Wir haben keinen Schlüssel in den Unterlagen. | Mit dem Team bestätigen, das es gekauft hat, oder durch eine permissive Charting-Bibliothek ersetzen. |
| fast-csv-lite 0.9.0 | (fehlend) | Keine Lizenz bedeutet keine Nutzungserlaubnis. | Den Maintainer bitten, eine Lizenz hinzuzufügen; in der Zwischenzeit auf einen gepflegten CSV-Parser mit MIT-Bedingungen umsteigen. |
| redis-om 0.4.3 | BUSL-1.1 | Die Business Source License begrenzt die Produktionsnutzung bis zu ihrem Änderungsdatum; die Bedingungen variieren pro Projekt. | Den genauen "Additional Use Grant" lesen. Wenn er unsere Nutzung verbietet, ersetzen oder eine Lizenz kaufen. |
| search-client 8.1.0 | Elastic-2.0 | Die Elastic License 2.0 verbietet, die Software als verwalteten Dienst anzubieten. Version 7.10.2 im selben Baum ist Apache-2.0. | Die Lock-Datei prüfen: Welche Module importieren 8.1.0? Beide auf die Apache-2.0-Linie pinnen oder zu einem Apache-lizenzierten Fork wechseln. |
| ghostscript-wrapper 2.1.0 | AGPL-3.0-only | Wir rendern Kunden-PDFs damit in der API; AGPL-Netzwerknutzung erfordert, Nutzern den Quellcode anzubieten. | Die PDF-Renderung in einen separaten, unveränderten Worker-Dienst verschieben und von Rechtsberatung bestätigen lassen, oder die kommerzielle Lizenz kaufen. |
### Verpflichtungen
| Paket | Lizenz | Was wir tun müssen |
|---|---|---|
| readline-sync 1.4.10 | GPL-3.0-only | Für SaaS in Ordnung. Es wird ein Blocker, wenn die On-Prem-Version es enthält. Es wird nur von einem Admin-CLI-Skript verwendet, also aus dem Server-Image entfernen. |
| mariadb-connector 3.3.1 | LGPL-2.1-or-later | Als normale npm-Abhängigkeit behalten (ersetzbar); Änderungen veröffentlichen, falls wir es je patchen. |
### Manuell verifiziert
- dompurify: dual-lizenziert `MPL-2.0 OR Apache-2.0`; wir wählen Apache-2.0.
- eslint-plugin-gpl-thing: nur Dev, nie ausgeliefert. Akzeptiert.
### Was sich ändert, wenn wir On-Prem ausliefern
- readline-sync (GPL-3.0) wird HIGH, mariadb-connector (LGPL) benötigt die Ersetzbarkeitsprüfung, und Apache-2.0-Pakete benötigen ihre NOTICE-Dateien im Installer. Vor diesem Release mit `--distribution distributed` erneut ausführen.
### Third-Party-Hinweise
Lizenzen, die den vollständigen Text benötigen: Apache-2.0, LGPL-2.1-or-later, MIT, Python-2.0. Die Hinweisseite aus der App-Fußzeile verlinken.
_Technisches Triage, keine Rechtsberatung._
## Warum dies eine gute Antwort ist
- Es bewertet jedes Paket für das tatsächliche Verteilungsmodell dieses Unternehmens und zeigt, was sich mit dem On-Prem-Plan ändert.
- Es prüft die Fakten, die die Metadaten nicht zeigen können (die kommerzielle LICENSE.md, die doppelten search-client-Versionen).
- Jeder Blocker hat eine konkrete, günstige Maßnahme.
FILE:scripts/audit_licenses.py
#!/usr/bin/env python3
"""Auditiert Lizenzen von Drittanbieter-Abhängigkeiten danach, wie ein Projekt ausgeliefert wird.
Verwendung:
python3 audit_licenses.py deps.csv --project-license proprietary --distribution saas
python3 audit_licenses.py licenses.json --distribution distributed [--json]
cat deps.csv | python3 audit_licenses.py - [--allow LIZ,LIZ] [--deny LIZ,LIZ]
Eingabe (automatisch erkannt):
CSV mit Kopfzeile: name,version,license[,scope] (scope: runtime | dev; Standard runtime)
JSON-Liste: [{"name": "...", "version": "...", "license": "...", "scope": "dev"}]
pip-licenses --format=json: [{"Name": "...", "Version": "...", "License": "..."}]
license-checker --json (npm): {"pkg@1.2.3": {"licenses": "MIT"}, ...}
Optionen:
--project-license die Lizenz deines Projekts: proprietary (Standard) oder eine SPDX-ID wie MIT, Apache-2.0, GPL-3.0-only
--distribution saas (Netzwerkdienst, Standard) | distributed (Apps, Binärdateien, Geräte, Bibliotheken) | internal
--allow / --deny durch Komma getrennte SPDX-IDs, die deine Richtlinie immer erlaubt oder immer blockiert
--json maschinenlesbare Ausgabe
Lizenznamen werden auf SPDX-IDs normalisiert, Ausdrücke mit OR nehmen die permissivste Option und AND die restriktivste. Jedes Paket erhält eine Kategorie und einen Schweregrad (OK, INFO, WARN, HIGH) für die gewählte Verteilung. Nur-Dev-Pakete werden nicht ausgeliefert, daher werden ihre Befunde auf INFO herabgestuft.
Exit-Code: 0 keine HIGH-Befunde, 1 mindestens ein HIGH, 2 Verwendungs- oder Eingabefehler.
Dies ist ein technisches Triage-Hilfsmittel, keine Rechtsberatung. Nur Standardbibliothek.
"""
import argparse
import csv
import io
import json
import re
import sys
ALIASES = { # kleingeschriebene gebräuchliche Namen -> SPDX-ID
"mit": "MIT", "mit license": "MIT", "the mit license": "MIT", "expat": "MIT",
"isc": "ISC", "isc license": "ISC", "isc license (iscl)": "ISC",
"apache 2.0": "Apache-2.0", "apache-2": "Apache-2.0", "apache 2": "Apache-2.0", "apache2": "Apache-2.0",
"apache license 2.0": "Apache-2.0", "apache license, version 2.0": "Apache-2.0",
"apache software license": "Apache-2.0", "apache license version 2.0": "Apache-2.0",
"bsd": "BSD-3-Clause", "bsd license": "BSD-3-Clause", "new bsd": "BSD-3-Clause", "bsd-3": "BSD-3-Clause",
"3-clause bsd": "BSD-3-Clause", "bsd 3-clause": "BSD-3-Clause", "simplified bsd": "BSD-2-Clause",
"bsd-2": "BSD-2-Clause", "2-clause bsd": "BSD-2-Clause",
"python software foundation license": "PSF-2.0", "psf": "PSF-2.0", "psfl": "PSF-2.0",
"mozilla public license 2.0 (mpl 2.0)": "MPL-2.0", "mpl 2.0": "MPL-2.0", "mpl-2": "MPL-2.0", "mpl2": "MPL-2.0",
"gplv2": "GPL-2.0-only", "gpl-2.0": "GPL-2.0-only", "gpl v2": "GPL-2.0-only", "gpl2": "GPL-2.0-only",
"gplv2+": "GPL-2.0-or-later", "gpl-2.0+": "GPL-2.0-or-later",
"gplv3": "GPL-3.0-only", "gpl-3.0": "GPL-3.0-only", "gpl v3": "GPL-3.0-only", "gpl3": "GPL-3.0-only",
"gplv3+": "GPL-3.0-or-later", "gpl-3.0+": "GPL-3.0-or-later", "gpl": "GPL-3.0-or-later",
"gnu general public license v2 (gplv2)": "GPL-2.0-only", "gnu general public license v3 (gplv3)": "GPL-3.0-only",
"gnu general public license v2 or later (gplv2+)": "GPL-2.0-or-later",
"gnu general public license v3 or later (gplv3+)": "GPL-3.0-or-later",
"lgpl": "LGPL-2.1-or-later", "lgplv2": "LGPL-2.1-only", "lgplv2+": "LGPL-2.1-or-later", "lgpl-2.1": "LGPL-2.1-only",
"lgplv3": "LGPL-3.0-only", "lgplv3+": "LGPL-3.0-or-later", "lgpl-3.0": "LGPL-3.0-only",
"gnu lesser general public license v2 (lgplv2)": "LGPL-2.1-only",
"gnu lesser general public license v3 (lgplv3)": "LGPL-3.0-only",
"gnu lesser general public license v2 or later (lgplv2+)": "LGPL-2.1-or-later",
"agpl": "AGPL-3.0-only", "agplv3": "AGPL-3.0-only", "agpl-3.0": "AGPL-3.0-only",
"gnu affero general public license v3": "AGPL-3.0-only", "gnu affero general public license v3 (agplv3)": "AGPL-3.0-only",
"eclipse public license 2.0": "EPL-2.0", "epl 2.0": "EPL-2.0", "epl-2": "EPL-2.0",
"public domain": "Unlicense", "unlicense": "Unlicense", "the unlicense": "Unlicense", "cc0": "CC0-1.0",
"boost software license 1.0": "BSL-1.0", "zlib license": "Zlib", "zlib": "Zlib",
"business source license 1.1": "BUSL-1.1", "elastic license 2.0": "Elastic-2.0",
"server side public license": "SSPL-1.0", "sspl": "SSPL-1.0",
}
CATEGORY = { # SPDX-ID-Präfix -> Kategorie
"MIT": "permissive", "MIT-0": "public-domain", "ISC": "permissive", "BSD-2-Clause": "permissive",
"BSD-3-Clause": "permissive", "Apache-2.0": "permissive", "PSF-2.0": "permissive", "Python-2.0": "permissive",
"Zlib": "permissive", "BSL-1.0": "permissive", "X11": "permissive", "BlueOak-1.0.0": "permissive",
"CC-BY-4.0": "permissive", "CC-BY-3.0": "permissive", "Unicode-DFS-2016": "permissive", "Unicode-3.0": "permissive",
"0BSD": "public-domain", "Unlicense": "public-domain", "CC0-1.0": "public-domain", "WTFPL": "public-domain",
"MPL-2.0": "weak-copyleft", "EPL-1.0": "weak-copyleft", "EPL-2.0": "weak-copyleft", "CDDL-1.0": "weak-copyleft",
"CDDL-1.1": "weak-copyleft", "LGPL-2.1": "weak-copyleft", "LGPL-3.0": "weak-copyleft", "CC-BY-SA-4.0": "weak-copyleft",
"GPL-2.0": "strong-copyleft", "GPL-3.0": "strong-copyleft",
"AGPL-3.0": "network-copyleft", "SSPL-1.0": "network-copyleft",
"BUSL-1.1": "source-available", "Elastic-2.0": "source-available", "Commons-Clause": "source-available",
"CC-BY-NC-4.0": "source-available", "CC-BY-NC-SA-4.0": "source-available", "PolyForm-Noncommercial-1.0.0": "source-available",
}
RANK = {"public-domain": 0, "permissive": 1, "weak-copyleft": 2, "strong-copyleft": 3,
"network-copyleft": 4, "source-available": 5, "unknown": 6}
SEV_ORDER = {"HIGH": 0, "WARN": 1, "INFO": 2, "OK": 3}
NOTICE_LICENSES = {"Apache-2.0"}
def unwrap(text):
"""Entfernt Klammern, die den gesamten Ausdruck umschließen: '(MIT OR X)' -> 'MIT OR X'."""
t = text.strip()
while t.startswith("(") and t.endswith(")"):
depth = 0
for i, ch in enumerate(t):
depth += ch == "("
depth -= ch == ")"
if depth == 0 and i < len(t) - 1:
return t
t = t[1:-1].strip()
return t
def normalize(token):
t = unwrap(token)
if not t:
return ""
key = t.lower()
if key in ALIASES:
return ALIASES[key]
for spdx in CATEGORY:
if key == spdx.lower() or key.startswith(spdx.lower() + "-") or key == spdx.lower() + "+":
return t if key.startswith(spdx.lower() + "-") else spdx
return t
def category_of(spdx):
if spdx.upper().startswith("LICENSEREF") or spdx.upper().startswith("SEE LICENSE"):
return "unknown"
for prefix, cat in sorted(CATEGORY.items(), key=lambda kv: -len(kv[0])):
if spdx == prefix or spdx.startswith(prefix + "-") or spdx.startswith(prefix + "+"):
return cat
return "unknown"
def classify(expr):
"""Gibt (normalisierter Ausdruck, gewählte Lizenz, Kategorie, Ausnahme) für einen Lizenzausdruck zurück."""
raw = (expr or "").strip()
if not raw or raw.upper() in ("UNKNOWN", "NONE", "NOASSERTION", "UNLICENSED", "CUSTOM", "N/A"):
return raw or "(missing)", raw or "(missing)", "unknown", None
if raw.upper().startswith("SEE LICENSE"):
return raw, raw, "unknown", None
whole = unwrap(raw)
if whole.lower() in ALIASES: # lange Namen wie "... v2 or later (LGPLv2+)" sind eine Lizenz
spdx = ALIASES[whole.lower()]
return spdx, spdx, category_of(spdx), None
text = re.sub(r"\s+", " ", whole.replace("/", " OR ").replace(";", " OR "))
exception = None
m = re.search(r"\bWITH\s+([\w.+-]+)", text, re.I)
if m:
exception = m.group(1)
text = text[:m.start()] + text[m.end():]
if re.search(r"\sor\s", text, re.I) and not re.search(r"\sand\s", text, re.I):
parts, mode = re.split(r"\s+or\s+", text, flags=re.I), "or"
elif re.search(r"\sand\s", text, re.I) and not re.search(r"\sor\s", text, re.I):
parts, mode = re.split(r"\s+and\s+", text, flags=re.I), "and"
elif re.search(r"\s(or|and)\s", text, re.I):
parts, mode = re.split(r"\s+(?:or|and)\s+", text, flags=re.I), "and" # gemischt: konservativ sein
else:
parts, mode = [text], "single"
lics = [normalize(p) for p in parts if normalize(p)]
if not lics:
return raw, raw, "unknown", exception
scored = sorted(lics, key=lambda l: RANK[category_of(l)])
chosen = scored[0] if mode == "or" else scored[-1]
cat = category_of(chosen)
if exception and cat == "strong-copyleft" and re.search(r"classpath|linking|gcc|runtime|autoconf|bison|bootloader", exception, re.I):
cat = "weak-copyleft"
joiner = " OR " if mode == "or" else " AND "
return joiner.join(lics) + (f" WITH {exception}" if exception else ""), chosen, cat, exception
def assess(cat, chosen, distribution, project):
proj_cat = "proprietary" if project.lower() == "proprietary" else category_of(normalize(project))
project_spdx = normalize(project)
if cat == "unknown":
return "HIGH", "Lizenz fehlt oder wird nicht erkannt; finde den tatsächlichen Lizenztext, bevor du auslieferst."
if cat == "source-available":
return "HIGH", "Source-available- oder nicht-kommerzielle Bedingungen schränken kommerzielle Nutzung oder Hosting normalerweise ein; eine Lizenzprüfung ist erforderlich."
if project_spdx.startswith("GPL-2.0-only") and chosen.startswith("Apache-2.0"):
return "HIGH", "Apache-2.0 gilt allgemein als inkompatibel mit GPL-2.0-only-Projekten."
gpl3_family = re.match(r"(A|L)?GPL-3\.0", chosen)
if project_spdx.startswith("GPL-2.0-only") and gpl3_family:
return "HIGH", f"{chosen} kann nicht mit einem GPL-2.0-only-Projekt kombiniert werden; das Projekt müsste GPL-3.0-Bedingungen haben."
if re.match(r"GPL-3\.0|AGPL-3\.0", project_spdx) and chosen.startswith("GPL-2.0-only"):
return "HIGH", "GPL-2.0-only-Code kann nicht unter GPL-3.0 neu lizenziert werden; frage upstream nach 'or later'-Bedingungen oder ersetze es."
if proj_cat in ("strong-copyleft", "network-copyleft") and cat in ("strong-copyleft", "network-copyleft"):
if cat == "network-copyleft" and proj_cat != "network-copyleft" and distribution == "saas":
return "WARN", "AGPL/SSPL in einem GPL-Projekt, das als Dienst läuft: der gesamte Dienstquellcode muss den Nutzern angeboten werden."
return "OK", "Copyleft-Abhängigkeit in einem Copyleft-Projekt; Versionskompatibilität prüfen (zum Beispiel GPL-2.0-only vs. GPL-3.0)."
if cat == "network-copyleft":
if distribution == "internal":
return "WARN", "Network Copyleft: für rein interne Nutzung in Ordnung, aber die Bereitstellung für externe Nutzer löst Quellcode-Verpflichtungen aus."
return "HIGH", "Network Copyleft (AGPL/SSPL): Das Anbieten des Dienstes oder das Ausliefern erfordert die Freigabe des entsprechenden Quellcodes."
if cat == "strong-copyleft":
if distribution == "distributed":
return "HIGH", "GPL-Code, der mit deinem Produkt verteilt wird, erfordert, dass das kombinierte Werk unter der GPL veröffentlicht wird."
if distribution == "saas":
return "WARN", "GPL wird durch reine Netzwerknutzung nicht ausgelöst, aber jede zukünftige Verteilung (Apps, On-Prem, SDKs) würde es."
return "INFO", "GPL nur für interne Nutzung hat keine Verteilungsverpflichtungen; halte es aus allem heraus, was du auslieferst."
if cat == "weak-copyleft":
if distribution == "distributed":
return "WARN", "Weak Copyleft: Lizenz mitliefern, die Bibliothek ersetzbar halten (dynamisches Linken für LGPL) und Änderungen an ihren Dateien veröffentlichen."
return "INFO", "Weak Copyleft: Veröffentliche alle Änderungen, die du an den eigenen Dateien dieser Bibliothek vornimmst, wenn du sie später verteilst."
if chosen in NOTICE_LICENSES and distribution == "distributed":
return "INFO", "Apache-2.0: Lizenz und jede NOTICE-Datei in deine Verteilung aufnehmen."
return "OK", "Permissive: Urheberrechtshinweis und Lizenztext in deinen Third-Party-Hinweisen behalten."
def load(text):
stripped = text.lstrip()
if not stripped:
raise ValueError("Eingabe ist leer")
if stripped[0] in "[{":
data = json.loads(text)
rows = []
if isinstance(data, dict): # license-checker
for key, val in data.items():
name, _, ver = key.rpartition("@")
if not name:
name, ver = key, ""
lic = val.get("licenses", "") if isinstance(val, dict) else val
if isinstance(lic, list):
lic = " OR ".join(lic)
rows.append({"name": name, "version": ver, "license": str(lic), "scope": "runtime"})
return rows
for item in data:
if not isinstance(item, dict):
raise ValueError("JSON-Listenelemente müssen Objekte sein")
low = {k.lower(): v for k, v in item.items()}
lic = low.get("license", low.get("licenses", ""))
if isinstance(lic, list):
lic = " OR ".join(lic)
rows.append({"name": str(low.get("name", "")), "version": str(low.get("version", "")),
"license": str(lic or ""), "scope": str(low.get("scope", "runtime") or "runtime").lower()})
return rows
reader = csv.DictReader(io.StringIO(text))
fields = [f.strip().lower() for f in (reader.fieldnames or [])]
if "name" not in fields or "license" not in fields:
raise ValueError(f"CSV benötigt eine Kopfzeile mit mindestens den Spalten 'name' und 'license', erhalten: {', '.join(fields) or '(keine)'}")
rows = []
for r in reader:
r = {(k or "").strip().lower(): (v or "").strip() for k, v in r.items()}
if not r.get("name"):
continue
rows.append({"name": r["name"], "version": r.get("version", ""), "license": r.get("license", ""),
"scope": (r.get("scope") or "runtime").lower()})
return rows
def audit(rows, args):
allow = {normalize(x) for x in args.allow.split(",") if x.strip()} if args.allow else set()
deny = {normalize(x) for x in args.deny.split(",") if x.strip()} if args.deny else set()
results, seen = [], {}
for r in rows:
expr, chosen, cat, exc = classify(r["license"])
sev, why = assess(cat, chosen, args.distribution, args.project_license)
if exc:
why += f" Lizenzausnahme {exc} gilt; lies ihren genauen Umfang."
if chosen in deny:
sev, why = "HIGH", f"{chosen} steht auf deiner Deny-Liste."
elif chosen in allow and sev != "OK":
sev, why = "OK", f"{chosen} steht auf deiner Allow-Liste ({why})"
dev = r["scope"] in ("dev", "development", "test", "build")
if dev and sev in ("HIGH", "WARN") and chosen not in deny:
sev, why = "INFO", "Nur Dev (nicht ausgeliefert): " + why
results.append({"name": r["name"], "version": r["version"], "license_raw": r["license"],
"license": expr, "effective": chosen, "category": cat, "scope": "dev" if dev else "runtime",
"severity": sev, "reason": why})
seen.setdefault(r["name"].lower(), set()).add(expr)
notes = []
for name, lics in seen.items():
if len(lics) > 1:
notes.append(f"{name}: unterschiedliche Lizenzen über Versionen ({', '.join(sorted(lics))}); prüfe, welche Version du auslieferst.")
results.sort(key=lambda x: (SEV_ORDER[x["severity"]], -RANK[x["category"]], x["name"].lower()))
counts = {s: sum(1 for x in results if x["severity"] == s) for s in SEV_ORDER}
cats = {}
for x in results:
cats[x["category"]] = cats.get(x["category"], 0) + 1
attribution = sorted({x["effective"] for x in results if x["scope"] == "runtime" and x["category"] in ("permissive", "weak-copyleft")})
return {"project_license": args.project_license, "distribution": args.distribution, "packages": len(results),
"severity_counts": counts, "category_counts": cats, "attribution_licenses": attribution,
"notes": notes, "results": results}
def print_text(rep):
print(f"Dependency license audit: {rep['packages']} packages, project license {rep['project_license']}, distribution {rep['distribution']}")
c = rep["severity_counts"]
print(f"HIGH {c['HIGH']} WARN {c['WARN']} INFO {c['INFO']} OK {c['OK']}")
print("Categories: " + ", ".join(f"{k} {v}" for k, v in sorted(rep["category_counts"].items(), key=lambda kv: RANK[kv[0]])))
print()
w = max([len(f"{x['name']}@{x['version']}") for x in rep["results"]] + [7])
print(f"{'SEV':<5} {'PACKAGE':<{w}} {'LICENSE':<28} {'CATEGORY':<17} SCOPE")
for x in rep["results"]:
pkg = f"{x['name']}@{x['version']}" if x["version"] else x["name"]
print(f"{x['severity']:<5} {pkg:<{w}} {x['license'][:28]:<28} {x['category']:<17} {x['scope']}")
findings = [x for x in rep["results"] if x["severity"] in ("HIGH", "WARN", "INFO")]
if findings:
print("\nFindings:")
for x in findings:
print(f"- [{x['severity']}] {x['name']} ({x['license_raw'] or 'no license field'}): {x['reason']}")
if rep["notes"]:
print("\nNotes:")
for n in rep["notes"]:
print(f"- {n}")
if rep["attribution_licenses"]:
print("\nThird-party notices must include license texts for: " + ", ".join(rep["attribution_licenses"]))
print("\nTriage aid only, not legal advice.")
def main():
p = argparse.ArgumentParser(description="Audit dependency licenses.", add_help=True)
p.add_argument("path", help="CSV or JSON file, or - for stdin")
p.add_argument("--project-license", default="proprietary")
p.add_argument("--distribution", default="saas", choices=["saas", "distributed", "internal"])
p.add_argument("--allow", default="")
p.add_argument("--deny", default="")
p.add_argument("--json", action="store_true")
args = p.parse_args()
try:
text = sys.stdin.read() if args.path == "-" else open(args.path, encoding="utf-8").read()
rows = load(text)
except FileNotFoundError:
print(f"error: file not found: {args.path}", file=sys.stderr)
return 2
except (ValueError, json.JSONDecodeError) as e:
print(f"error: {e}", file=sys.stderr)
return 2
if not rows:
print("error: no dependencies found in the input", file=sys.stderr)
return 2
rep = audit(rows, args)
if args.json:
print(json.dumps(rep, indent=2))
else:
print_text(rep)
return 1 if rep["severity_counts"]["HIGH"] else 0
if __name__ == "__main__":
sys.exit(main())
Melde dich an, um den vollständigen Prompt zu sehen
Weiter mit:
Mit der Anmeldung akzeptierst du unsere Nutzungsbedingungen und Datenschutz
Verwendung
Dieser Prompt ist für die Verwendung mit coding gedacht. Kopiere den Inhalt oben und füge ihn in dein bevorzugtes KI-Tool ein.
Für beste Ergebnisse passe die Platzhalter (eckige Klammern oder Großbuchstaben) an deine Anforderungen an.
Diskussion
0 Kommentare