Zum Hauptinhalt springen

Lizenz-Scanning

Lizenz-Scanning beantwortet die Frage Was darf ich ausliefern? und ergänzt damit die Frage der SBOM, Was steckt in diesem Image?, und die des Abhängigkeits-Scans, Was ist verwundbar?. fremforge führt bei jedem PR die Lizenzerkennung von Trivy aus, ordnet die Lizenz jeder aufgelösten Abhängigkeit einer Schweregradstufe der SPDX-Richtlinie zu und zeigt das Ergebnis neben dem PR und in einem eigenen Dashboard der Organisation an. Es ist für jede Organisation standardmäßig eingeschaltet, ohne Aktivierungsschritt.

So funktioniert es

Der zentrale Scanner von fremforge (derselbe, der SAST, Abhängigkeits-Scanning und Image-Scanning ausführt) liest bei jedem PR die Lizenz jeder Abhängigkeit, die deine Lockfiles auflösen, und wendet die Lizenzdatei-Erkennung von Trivy auf die eigenen LICENSE- und COPYING-Dateien deines Repositorys an. Er läuft auf den eigenen Runnern von fremforge; in deinem Repository wird nichts installiert. Anders als SAST oder Abhängigkeits-Scanning, die eine Liste nur mit Problemen senden, sendet das Lizenz-Scanning ein vollständiges Lizenz-Inventar: einen Befund pro (Paket × Lizenz) über alle aufgelösten Abhängigkeiten. Der Workflow sendet das Inventar dann per POST an fremforge:

POST https://frem.sh/_app/api/v1/license-scan/findings

Der Runner-PAT braucht den Scope findings:write (pro Repository vom Runner-Controller bei der Installation des Workflows ausgestellt). Bei einem erfolgreichen Ingest:

  1. Speichert fremforge die Befunde. Eine Zeile pro Befund in license_scan_findings. Jeder Scan ersetzt den vorherigen Scan für dasselbe (tenant, repo, pr_number) vollständig; ein PR, der eine Abhängigkeit entfernt, zeigt deren veraltete Zeilen nicht mehr. Push-Scans (pr_number null) liegen in einem eigenen Bereich, damit sie PR-Scans nicht gegenseitig ersetzen.
  2. Schreibt fremforge einen PR-Kommentar, der die Befunde zusammenfasst (Schweregrad, Lizenz, Paket, Manifest), über die Issues-API von Forgejo.
  3. Setzt fremforge den Commit-Status fremforge/license-scan auf den Head-SHA des PR: success, wenn nur Hinweise vorliegen oder alles auf der Erlaubt-Liste steht, failure, wenn das Richtlinien-Gate greift.

Kommentar und Status auf der Forgejo-Seite erfolgen nach bestem Bemühen: Ein Ausfall von Forgejo wird protokolliert und auf einen Hinweis herabgestuft, lässt den Ingest aber nie fehlschlagen. Die gespeicherten Befunde sind die maßgebliche Quelle; die Anzeige am PR ist eine Komfortschicht.

Woher die Lizenzen der Abhängigkeiten kommen

Es wird nichts installiert: Die Lizenzen werden aus den Registry-Metadaten für genau die Version gelesen, die jede Lockfile pinnt. Für npm, PyPI, Go und Maven werden diese Metadaten über den Dependency-Proxy deiner Organisation gelesen, auf der internen Route des Scan-Dienstes, oder über den gemeinsamen Scan-Cache von fremforge, wenn deine Organisation keinen Proxy hat; nur wenn keiner von beiden antworten kann, werden sie über den Egress-Proxy von fremforge aus der öffentlichen Registry gelesen. Lizenzen aus NuGet, crates.io, RubyGems und Packagist werden über den Egress-Proxy aus der öffentlichen Registry gelesen. Die früheren clusterinternen Paketspiegel von fremforge sind stillgelegt.

ÖkosystemQuelleWas gelesen wird
npm (Lockfiles von npm / pnpm / yarn / bun)registry.npmjs.org, über den Dependency-Proxydas Feld license des Pakets
Go (go.mod)proxy.golang.org, über den Dependency-Proxy; private Module (frem.sh, dein angegebenes GOPRIVATE) werden nie angefragtdie Datei LICENSE / COPYING im Wurzelverzeichnis des Moduls, klassifiziert mit derselben Lizenzdatei-Erkennung wie deine eigenen Dateien (Go hat keine Lizenzmetadaten)
Maven (pom.xml, Gradle-Lockfiles)Maven Central, über den Dependency-Proxy<licenses> im POM, unter Berücksichtigung von Relocations und Parent-POMs
NuGet (packages.lock.json)nuget.org, über den Egress-Proxyder Lizenzausdruck des Pakets oder eine erkennbare Lizenz-URL
PyPI (requirements.txt, Poetry, uv)pypi.org, über den Dependency-Proxydie JSON-Metadaten des Releases (license_expression, ein kurzes license, Lizenz-Classifier)
crates.io, RubyGems, Packagistdie öffentliche Registry, über den Egress-Proxydas Lizenzfeld der Registry pro Version

Lizenznamen in Freitext (GPL v3, GNU AFFERO GPL 3.0, Apache License, Version 2.0, …) werden auf SPDX-Kennungen abgebildet. Alles, was sich nicht sicher lesen lässt, wird als unknown gemeldet und zur Prüfung angezeigt, nie als permissiv geraten.

Wenn eine Quelle ausfällt

Antwortet ein Spiegel oder eine Registry nicht (Netzwerkfehler, Timeout, 5xx), werden die betroffenen Pakete weder als sauber gewertet noch dürfen sie blockieren: Der Status fremforge/license-scan behält, was der Rest des Scans ergeben hat, und ergänzt lookup-unavailable for N package(s), re-scan queued. fremforge startet den Scan alle paar Minuten automatisch neu, bis die Quelle antwortet, und der Hinweis verschwindet.

Schweregradstufen

Lizenz-Scanning verwendet ein Vokabular aus 7 Stufen (nicht die 4 Stufen CRITICAL/HIGH/… des CVE-Scannings), passend zur Klassifizierung der SPDX-Richtlinie in Trivy, geordnet von der geringsten zur stärksten Belastung:

StufeBedeutungBeispiellizenzen
unknownLizenz konnte nicht ermittelt werden (wird trotzdem zur Prüfung angezeigt)NOASSERTION
unencumberedGleichwertig mit Public DomainCC0-1.0, Unlicense
permissiveFrei nutzbar, minimale Pflichten—
noticePermissiv mit Pflichten zur Namensnennung / zu HinweisenMIT, BSD-3-Clause, Apache-2.0, ISC
reciprocalSchwaches Copyleft: Änderungen an der Bibliothek selbst werden geteiltLGPL-2.1, LGPL-3.0, MPL-2.0, EPL-2.0, CDDL-1.0, GPL-2.0 WITH Classpath-exception-2.0
restrictedStarkes CopyleftGPL-2.0, GPL-3.0, CC-BY-SA-4.0
forbiddenNetzwerk-Copyleft / Source-availableAGPL-3.0, SSPL-1.0, BUSL-1.1, CC-BY-NC-4.0

LGPL ist in fremforge reciprocal (die Tabelle von Trivy selbst ordnet sie restricted zu): Ein Programm darf eine LGPL-Bibliothek linken, ohne selbst LGPL zu werden. Sie ist also schwaches Copyleft und blockiert bei der Standardschwelle nicht.

Richtlinieneinstellungen

Eine Lizenzrichtlinie seit dem 2026-10-15. Die Listen, die Regeln für unbekannte und mehrfache Lizenzen und der Blockier-Schweregrad werden unter Sicherheit › Richtlinien › Lizenzrichtlinie bearbeitet, zusammen mit den Lizenzregeln für Paket-Downloads. Siehe Lizenzrichtlinie. Sicherheit › Befunde › Lizenzen behält den Ein/Aus-Schalter und die Überschreibungen pro Repository. Die API-Felder unten funktionieren weiterhin und ändern nur Pull Requests.

Konfiguriere es auf der Seite Lizenzrichtlinie, über die API (PUT /orgs/<org>/code-security/licenses/settings, nur Pull Requests) oder als Code mit dem Terraform- und OpenTofu-Modul:

  • Aktiviert: Ist es aus, antwortet der Ingest mit 202 {accepted: 0, status: 'disabled'} (der Workflow läuft weiter, die Befunde werden nur verworfen). Standardmäßig an.
  • Blockier-Schweregrad: die Schwelle, ab der ein Befund den Merge blockiert. Standard ist restricted (seit dem 2026-09-29; vorher forbidden, wodurch GPL-Abhängigkeiten nur als Hinweis durchgingen): Starkes Copyleft und darüber blockiert, schwaches Copyleft bleibt ein Hinweis. Eine Lizenz, die auf keiner der unten genannten Listen steht, wird nach dieser Schwelle beurteilt. Kombiniere es mit einem Branch-Schutz, der den Check fremforge/license-scan verlangt, damit der Merge tatsächlich blockiert wird.
  • Lizenzrichtlinie: explizite Listen und zwei Regeln, beschrieben im nächsten Abschnitt.

Befunde der Stufe forbidden (→ kritisch) oder restricted (→ hoch) oder solche, die von der Verboten-Liste abgelehnt werden, lösen, wenn ein Scan sie neu einführt und sie nicht verworfen sind, außerdem den Echtzeit-Hook für Sicherheitsalarme aus; niedrigere Stufen sind nur Hinweise.

Lizenzrichtlinie

Jeder Befund wird pro SPDX-Operand entschieden, mit derselben Lesart wie in der Paketrichtlinie: Bei MIT OR GPL-3.0-only darf der Nutzer wählen, also entscheidet der am wenigsten einschränkende Operand; bei Apache-2.0 AND GPL-2.0-only gelten beide, also entscheidet der am stärksten einschränkende Operand. Verschachtelte Klammern, WITH-Ausnahmen (GPL-2.0-only WITH Classpath-exception-2.0) und LicenseRef-…-Kennungen werden so gelesen, wie SPDX sie definiert.

EinstellungAPI-FeldStandardWirkung
Erlaubt-Listeallowlisted_license_idsleerEin Operand auf dieser Liste ist erlaubt: Der Befund wird beim Ingest verworfen (Akteur tenant_allowlist), unabhängig von seiner Stufe.
Verboten-Listedenied_license_idsleerEin Operand auf dieser Liste blockiert, unabhängig von seiner Stufe. Bei derselben Kennung gewinnt Verboten über Erlaubt.
Warnlistewarned_license_idsleerEin Operand auf dieser Liste ist ein Hinweis: gemeldet, nie blockierend, unabhängig von seiner Stufe.
Unbekannte Lizenzenunknown_actionthresholdEine Lizenz ohne SPDX-Kennung, NOASSERTION, NONE, UNKNOWN, eine Zeichenkette, die kein SPDX-Ausdruck ist, oder eine Kennung, die fremforge nicht klassifizieren kann (etwa ein nicht gelistetes LicenseRef-…): threshold beurteilt sie als Stufe unknown nach dem Blockier-Schweregrad; allow verwirft sie, warn meldet sie, deny blockiert sie.
Mehrere Lizenzenmultiple_modeallEine Komponente, die mehrere Lizenzen angibt (getrennte Befunde, kein einzelner Ausdruck): all beurteilt jede Lizenz für sich, sodass jede einzelne blockieren kann; bei any entscheidet die am wenigsten einschränkende für alle.

Listeneinträge sind SPDX-Kennungen (MIT, LicenseRef-acme) oder eine Kennung mit ihrer Ausnahme (GPL-2.0-only WITH Classpath-exception-2.0), abgeglichen ohne Berücksichtigung der Groß- und Kleinschreibung; GPL-2.0+ trifft GPL-2.0-or-later. Ein Eintrag für die Basislizenz deckt sie mit jeder Ausnahme ab; ein Eintrag für das Paar deckt nur das Paar ab. Bis zu 100 Einträge pro Liste. Ein PUT ersetzt die Einstellungen: Ein weggelassenes Feld erhält seinen Standard.

Die Standards ändern nichts. Mit leerer Verboten- und Warnliste, unknown_action: threshold und multiple_mode: all wird jeder Befund genau so entschieden, wie der Blockier-Schweregrad und die Erlaubt-Liste ihn entschieden haben, bevor es diese Richtlinie gab. Der einzige Unterschied ist die Erlaubt-Liste selbst, die jetzt Operanden abgleicht: MIT OR GPL-3.0-only besteht eine Erlaubt-Liste, die MIT enthält.

Warum ein Befund blockiert

An einem Pull Request blockieren nur Befunde, die der Pull Request einführt: Ein Paket mit einer Lizenz, das der Branch bereits hat, blockiert nicht.

Jeder Befund trägt die Entscheidung als policy in der Findings-API (GET /orgs/<org>/code-security/licenses/findings), und das Dashboard zeigt sie in der Spalte Entscheidung der Richtlinie:

{
  "license_id": "MIT OR GPL-3.0-only",
  "policy": {
    "action": "allow",
    "rule": "allow_list",
    "decided_by": ["MIT"],
    "operands": [
      { "operand": "MIT", "action": "allow", "rule": "allow_list", "matched": "MIT" },
      { "operand": "GPL-3.0-only", "action": "block", "rule": "deny_list", "matched": "GPL-3.0-only" }
    ],
    "why": "allowed: MIT is on the allow list (OR takes the least restrictive operand)"
  }
}

action ist allow, warn oder block; rule ist allow_list, deny_list, warn_list, threshold, below_threshold, unknown, multiple_any oder exception. Der PR-Kommentar listet jeden blockierenden Befund mit seinem Grund auf, und der Status fremforge/license-scan nennt die Regeln (2 finding(s) blocked by the licence policy (1 denied, 1 at or above restricted)).

Um eine Richtlinie auszuprobieren, bevor ein PR es tut, entscheidet POST /orgs/<org>/code-security/licenses/evaluate über eine Liste von Lizenzen, ohne etwas zu schreiben:

curl -sS -X POST -H "Authorization: Bearer $FREMFORGE_TOKEN" -H 'Content-Type: application/json' \
  -d '{"licenses":[{"license_id":"MIT OR GPL-3.0-only"},{"license_id":"Apache-2.0 AND GPL-2.0-only"}]}' \
  https://frem.sh/_app/api/v1/orgs/<org>/code-security/licenses/evaluate

Überschreibungen pro Repository

Ein Repository kann jedes der fünf Richtlinienfelder überschreiben: PUT /orgs/<org>/repos/<repo>/code-security/licenses/settings mit den zu setzenden Feldern. Ein gesetztes Feld ersetzt den Wert der Organisation für dieses Repository (eine Liste ersetzt die Liste der Organisation; ein leeres Array ist ein Wert); ein fehlendes Feld oder null erbt ihn. GET auf demselben Pfad zeigt die Überschreibung und die wirksame Richtlinie, DELETE entfernt sie. In der Konsole stehen die Überschreibungen unter Überschreibungen pro Repository auf Sicherheit › Richtlinien › Lizenzrichtlinie.

Lizenzausnahmen für eine Komponente

Um eine Lizenz für eine Komponente zu akzeptieren, etwa ein GPL-Tool, das deine Rechtsabteilung freigegeben hat, beantragst du eine Ausnahme (POST /orgs/<org>/exceptions mit kind: package oder in der Konsole) mit dem Geltungsbereich component, der Regel licence, der purl der Komponente ohne Version (pkg:npm/left-pad, pkg:pypi/requests, pkg:maven/org.example:lib) und der Lizenzkennung als Selektor (lass den Selektor leer für jede Lizenz dieser Komponente). Es ist dieselbe Ausnahme wie bei den Paketrichtlinien: eine Begründung, ein verpflichtendes Ablaufdatum von höchstens einem Jahr und die Freigabe durch einen Owner oder benannten Freigebenden, der nicht der Antragsteller ist. Nach der Freigabe (POST /orgs/<org>/exceptions/<id>/approve, durch jemand anderen als den Antragsteller) verwirft der nächste Scan jedes Repositorys die passenden Befunde (Akteur licence-exception:<id>, dismissal_approved_by die freigebende Person, Grund mit der Begründung), bis die Ausnahme abläuft (der Befund wird an diesem Tag wieder geöffnet) oder widerrufen wird (der nächste Scan öffnet ihn wieder).

Befunde verwerfen

Ein Admin der Organisation kann einzelne Befunde im Tab Lizenzen verwerfen (mit einem verpflichtenden Grund, der ins Audit-Log geschrieben wird). Manuelle Verwerfungen werden über den ersetzenden Neu-Scan beim Ingest hinweg übernommen, mit dem Schlüssel (Lizenzkennung, Paket, Manifestpfad); ein erneuter Scan eines PR lässt einen Befund, den ein Admin bereits akzeptiert hat, nicht wieder aufleben. Eine Verwerfung kann ein optionales Ablaufdatum tragen, nach dem der Befund von selbst wieder geöffnet wird; siehe wann ein Befund geschlossen wird. Verwerfungen durch die Lizenzrichtlinie (Erlaubt-Liste, unbekannt allow, multiple_mode: any, eine Ausnahme) werden nicht übernommen; sie werden bei jedem Scan neu aus den Einstellungen abgeleitet, sodass eine Änderung der Richtlinie beim nächsten Scan wirkt.

Wo Befunde erscheinen

Sicherheit › Befunde › Lizenzen listet aktive Befunde und kürzliche Verwerfungen auf. Jeder Befund zeigt die Schweregradstufe, die SPDX-Lizenzkennung, ecosystem:package, den Manifestpfad und die Entscheidung der Richtlinie mit ihrem Grund. Der PR-Kommentar verlinkt zurück auf dieses Dashboard.

Aufbewahrung

Befunde folgen der Audit-Aufbewahrungsrichtlinie des Mandanten, wie bei den anderen Code-Security-Scannern. Befunde geschlossener PRs werden vom täglichen Job /jobs/license-scan-closed-pr-cleanup entfernt, da ein geschlossener PR nie erneut gescannt wird.

Siehe auch

  • OSV-Empfehlungen: CVE-Scanning der Abhängigkeiten (die Ergänzung zu Schwachstellen für diese Sicht auf Lizenzen).
  • SBOMs: das Paketinventar, an dem die Lizenzen hängen.
  • Image-Scanning: der verwandte Scanner, der denselben zentralen Scan-Lauf und dasselbe Ingest-Muster mit findings:write nutzt.
  • Sicherheitsfunktionen auf einen Blick: die vollständige Kontrollmatrix.