Licentiescan
De licentiescan beantwoordt de vraag wat mag ik uitleveren, als aanvulling op de SBOM, die zegt wat er in dit image zit, en op de afhankelijkheidsscan, die zegt wat er kwetsbaar is. fremforge voert bij elke PR licentiedetectie met Trivy uit, deelt de licentie van elke opgeloste afhankelijkheid in een ernstcategorie volgens het SPDX-beleid in, en toont het resultaat naast de PR en in een eigen dashboard voor de organisatie. Hij staat standaard aan voor elke organisatie; je hoeft niets in te schakelen.
Hoe het werkt
De centrale scanner van fremforge (dezelfde die SAST, de afhankelijkheidsscan en de imagescan uitvoert) leest bij elke PR de licentie van elke afhankelijkheid die je lockfiles vastleggen, en voert de licentiebestanddetectie van Trivy uit op de eigen LICENSE- en COPYING-bestanden van je repository. Hij draait op de eigen runners van fremforge; er wordt niets in je repository geïnstalleerd. Anders dan SAST of de afhankelijkheidsscan, die alleen een lijst met problemen plaatsen, plaatst de licentiescan een volledige inventaris van licenties: één bevinding per (package × licence) over elke opgeloste afhankelijkheid. De workflow stuurt de inventaris daarna met een POST naar fremforge:
POST https://frem.sh/_app/api/v1/license-scan/findingsDe PAT van de runner heeft de scope findings:write nodig (per repo aangemaakt door de runnercontroller bij de installatie van de workflow). Bij een geslaagde inname doet fremforge het volgende:
- De bevindingen opslaan. Eén rij per bevinding in
license_scan_findings. Elke scan vervangt de vorige scan voor dezelfde(tenant, repo, pr_number)in zijn geheel: een PR die een afhankelijkheid laat vallen, toont de verouderde rijen niet meer. Pushscans (pr_numbernull) staan in een eigen categorie, zodat ze PR-scans niet vervangen. - Een PR-opmerking plaatsen die de bevindingen samenvat (ernst, licentie, pakket, manifest), via de issues-API van Forgejo.
- De commitstatus
fremforge/license-scaninstellen op de head-SHA van de PR:successals alles adviserend is of op de lijst met toegestane licenties staat,failureals de beleidscontrole wordt geraakt.
De opmerking en de status aan de kant van Forgejo zijn best effort: bij een storing van Forgejo wordt dat gelogd en vallen ze terug op adviserend, maar de inname mislukt nooit. De opgeslagen bevindingen zijn de bron van waarheid; wat bij de PR staat, is een gemak.
Waar de licenties van afhankelijkheden vandaan komen
Er wordt niets geïnstalleerd: licenties worden gelezen uit de metadata van de registry voor de exacte versie die elke lockfile vastzet. Voor npm, PyPI, Go en Maven wordt die metadata gelezen via de dependency proxy van je organisatie, over de interne route van de scanservice, of via de gedeelde scancache van fremforge als je organisatie geen proxy heeft; alleen als geen van beide antwoord geeft, wordt ze uit de publieke registry gelezen via de egress-proxy van fremforge. Licenties van NuGet, crates.io, RubyGems en Packagist worden via de egress-proxy uit de publieke registry gelezen. De vroegere pakketmirrors van fremforge in het cluster zijn uit gebruik genomen.
| Ecosysteem | Bron | Wat er wordt gelezen |
|---|---|---|
| npm (lockfiles van npm / pnpm / yarn / bun) | registry.npmjs.org, via de dependency proxy | het veld license van het pakket |
Go (go.mod) | proxy.golang.org, via de dependency proxy; privémodules (frem.sh, je opgegeven GOPRIVATE) worden nooit opgevraagd | het bestand LICENSE / COPYING in de root van de module, ingedeeld met dezelfde licentiebestanddetectie als je eigen bestanden (Go heeft geen licentiemetadata) |
Maven (pom.xml, lockfiles van Gradle) | Maven Central, via de dependency proxy | de <licenses> van de POM, met het volgen van verplaatsingen en bovenliggende POM’s |
NuGet (packages.lock.json) | nuget.org, via de egress-proxy | de licentie-expressie van het pakket, of een herkenbare licentie-URL |
PyPI (requirements.txt, Poetry, uv) | pypi.org, via de dependency proxy | de JSON-metadata van de release (license_expression, een korte license, licentieclassifiers) |
| crates.io, RubyGems, Packagist | de publieke registry, via de egress-proxy | het licentieveld per versie van de registry |
Licentienamen in vrije tekst (GPL v3, GNU AFFERO GPL 3.0, Apache License, Version 2.0, …) worden omgezet naar SPDX-id’s. Alles wat niet met zekerheid kan worden gelezen, wordt gemeld als unknown en ter beoordeling getoond, en nooit als permissief geraden.
Als een bron niet bereikbaar is
Als een mirror of registry niet antwoordt (netwerkfout, time-out, 5xx), worden de betrokken pakketten niet als schoon gelezen en mogen ze ook niet blokkeren: de status fremforge/license-scan houdt wat de rest van de scan opleverde en voegt lookup-unavailable for N package(s), re-scan queued toe. fremforge voert de scan om de paar minuten automatisch opnieuw uit tot de bron antwoordt, en dan verdwijnt de melding.
Ernstcategorieën
De licentiescan gebruikt een vocabulaire van 7 categorieën (niet de 4 niveaus CRITICAL/HIGH/… van de CVE-scan), in lijn met de SPDX-beleidsindeling van Trivy, gerangschikt van minst naar meest belastend:
| Categorie | Betekenis | Voorbeeldlicenties |
|---|---|---|
unknown | De licentie kon niet worden vastgesteld (wordt nog wel ter beoordeling getoond) | NOASSERTION |
unencumbered | Gelijkwaardig aan het publieke domein | CC0-1.0, Unlicense |
permissive | Vrij te gebruiken, minimale verplichtingen | — |
notice | Permissief, met verplichtingen voor naamsvermelding of kennisgeving | MIT, BSD-3-Clause, Apache-2.0, ISC |
reciprocal | Zwakke copyleft: wijzigingen aan de bibliotheek zelf worden gedeeld | LGPL-2.1, LGPL-3.0, MPL-2.0, EPL-2.0, CDDL-1.0, GPL-2.0 WITH Classpath-exception-2.0 |
restricted | Sterke copyleft | GPL-2.0, GPL-3.0, CC-BY-SA-4.0 |
forbidden | Netwerk-copyleft / source-available | AGPL-3.0, SSPL-1.0, BUSL-1.1, CC-BY-NC-4.0 |
LGPL is in fremforge reciprocal (de eigen tabel van Trivy zet het in restricted): een programma mag een LGPL-bibliotheek linken zonder zelf LGPL te worden, dus het is zwakke copyleft en blokkeert niet bij de standaarddrempel.
Beleidsinstellingen
Stel het in op de pagina Licentiebeleid, via de API (PUT /orgs/<org>/code-security/licenses/settings, alleen pull requests) of als code met de Terraform- en OpenTofu-module:
- Ingeschakeld: staat dit uit, dan geeft de inname
202 {accepted: 0, status: 'disabled'}terug (de workflow draait nog, de bevindingen worden alleen weggegooid). Staat standaard aan. - Blokkeerernst: de drempel op of boven welke een bevinding het mergen blokkeert. Standaard
restricted(sinds 2026-09-29; het wasforbidden, waardoor GPL-afhankelijkheden als adviserend doorgingen): sterke copyleft en hoger blokkeert, zwakke copyleft blijft adviserend. Een licentie die op geen van de lijsten hieronder staat, wordt aan deze drempel getoetst. Combineer dit met branchbeveiliging die de checkfremforge/license-scanvereist om het mergen echt te blokkeren. - Licentiebeleid: expliciete lijsten en twee regels, beschreven in de volgende sectie.
Bevindingen van forbidden (→ critical) of restricted (→ high), of geweigerd door de lijst met geweigerde licenties, die door een scan nieuw zijn geïntroduceerd en niet zijn afgewezen, activeren ook de realtime hook voor beveiligingsmeldingen; lagere categorieën zijn alleen adviserend.
Licentiebeleid
Elke bevinding wordt per SPDX-operand beslist, met dezelfde lezing als het pakketbeleid: in MIT OR GPL-3.0-only mag de gebruiker kiezen, dus beslist de minst beperkende operand; in Apache-2.0 AND GPL-2.0-only gelden beide, dus beslist de meest beperkende operand. Geneste haakjes, uitzonderingen met WITH (GPL-2.0-only WITH Classpath-exception-2.0) en id’s als LicenseRef-… worden gelezen zoals SPDX ze definieert.
| Instelling | API-veld | Standaard | Effect |
|---|---|---|---|
| Lijst met toegestane licenties | allowlisted_license_ids | leeg | Een operand op deze lijst is toegestaan: de bevinding wordt bij de inname afgewezen (actor tenant_allowlist), ongeacht de categorie. |
| Lijst met geweigerde licenties | denied_license_ids | leeg | Een operand op deze lijst blokkeert, ongeacht de categorie. Weigeren wint van toestaan voor dezelfde id. |
| Lijst met licenties die waarschuwen | warned_license_ids | leeg | Een operand op deze lijst is adviserend: gemeld, nooit blokkerend, ongeacht de categorie. |
| Onbekende licenties | unknown_action | threshold | Een licentie zonder SPDX-id, NOASSERTION, NONE, UNKNOWN, een string die geen SPDX-expressie is, of een id die fremforge niet kan indelen (zoals een niet-vermelde LicenseRef-…): threshold beoordeelt hem als de categorie unknown onder de blokkeerernst; allow wijst hem af, warn meldt hem, deny blokkeert hem. |
| Meerdere licenties | multiple_mode | all | Een component die meerdere licenties opgeeft (aparte bevindingen, geen enkele expressie): all beoordeelt elke licentie afzonderlijk, dus elke licentie kan blokkeren; any laat de minst beperkende voor alle licenties beslissen. |
Items op de lijsten zijn SPDX-id’s (MIT, LicenseRef-acme) of een id met zijn uitzondering (GPL-2.0-only WITH Classpath-exception-2.0), zonder onderscheid tussen hoofd- en kleine letters; GPL-2.0+ komt overeen met GPL-2.0-or-later. Een item voor de basislicentie dekt die met elke uitzondering; een item voor het paar dekt alleen het paar. Maximaal 100 items per lijst. Een PUT vervangt de instellingen: een weggelaten veld krijgt zijn standaardwaarde.
De standaardwaarden veranderen niets. Met lege lijsten voor weigeren en waarschuwen, unknown_action: threshold en multiple_mode: all wordt elke bevinding precies zo beslist als de blokkeerernst en de lijst met toegestane licenties haar beslisten voordat dit beleid bestond. Het enige verschil is de lijst met toegestane licenties zelf, die nu op operanden matcht: MIT OR GPL-3.0-only slaagt bij een lijst met toegestane licenties die MIT bevat.
Waarom een bevinding blokkeert
Bij een pull request blokkeren alleen de bevindingen die de pull request introduceert: een pakket en licentie die de branch al heeft, blokkeren niet.
Elke bevinding draagt de beslissing als policy in de findings-API (GET /orgs/<org>/code-security/licenses/findings), en het dashboard toont haar in de kolom Besluit van het beleid:
{
"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 is allow, warn of block; rule is allow_list, deny_list, warn_list, threshold, below_threshold, unknown, multiple_any of exception. De PR-opmerking vermeldt elke blokkerende bevinding met haar reden, en de status fremforge/license-scan noemt de regels (2 finding(s) blocked by the licence policy (1 denied, 1 at or above restricted)).
Om een beleid uit te proberen voordat een PR dat doet, beslist POST /orgs/<org>/code-security/licenses/evaluate over een lijst licenties zonder iets weg te schrijven:
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/evaluateOverschrijvingen per repository
Een repository kan elk van de vijf beleidsvelden overschrijven: PUT /orgs/<org>/repos/<repo>/code-security/licenses/settings met de velden die je wilt instellen. Een ingesteld veld vervangt de waarde van de organisatie voor die repository (een lijst vervangt de lijst van de organisatie; een lege array is ook een waarde); een veld dat ontbreekt of null is, neemt de waarde van de organisatie over. GET op hetzelfde pad toont de overschrijving en het effectieve beleid, DELETE verwijdert haar. In de console staan overschrijvingen onder Overschrijvingen per repository op Beveiliging › Beleid › Licentiebeleid.
Licentie-uitzonderingen voor één component
Om één licentie voor één component te accepteren, bijvoorbeeld een GPL-tool die je juridische team heeft goedgekeurd, vraag je een uitzondering aan (POST /orgs/<org>/exceptions met kind: package, of via de console) met scope component, regel licence, de purl van de component zonder versie (pkg:npm/left-pad, pkg:pypi/requests, pkg:maven/org.example:lib) en de licentie-id als selector (laat de selector leeg voor elke licentie van die component). Het is dezelfde uitzondering als bij pakketbeleid: een onderbouwing, een verplichte vervaldatum van hooguit een jaar, en goedkeuring door een eigenaar of aangewezen goedkeurder die niet de aanvrager is. Zodra hij is goedgekeurd (POST /orgs/<org>/exceptions/<id>/approve, door iemand anders dan de aanvrager), wijst de volgende scan van elke repository de overeenkomende bevindingen af (actor licence-exception:<id>, dismissal_approved_by de goedkeurder, reden met de onderbouwing), tot de uitzondering verloopt (de bevinding gaat die dag weer open) of wordt ingetrokken (de volgende scan opent haar weer).
Bevindingen afwijzen
Een beheerder van de organisatie kan afzonderlijke bevindingen afwijzen (met een verplichte reden, die in het auditlog wordt geschreven) vanaf het tabblad Licenties. Handmatige afwijzingen worden overgenomen bij de nieuwe scan die bij de inname de vorige vervangt, op basis van (licence ID, package, manifest path): een PR opnieuw scannen haalt geen bevinding terug die een beheerder al heeft geaccepteerd. Een afwijzing kan een optionele vervaldatum hebben, waarna de bevinding vanzelf weer opengaat; zie wanneer een bevinding sluit. Afwijzingen door het licentiebeleid (lijst met toegestane licenties, allow voor onbekend, multiple_mode: any, een uitzondering) worden niet overgenomen; ze worden bij elke scan opnieuw uit de instellingen afgeleid, dus een wijziging van het beleid werkt vanaf de volgende scan.
Waar bevindingen verschijnen
Beveiliging › Bevindingen › Licenties toont actieve bevindingen en recente afwijzingen. Elke bevinding toont de ernstcategorie, de SPDX-licentie-id, het ecosystem:package, het pad van het manifest en de beslissing van het beleid met de reden. De PR-opmerking linkt terug naar dit dashboard.
Bewaartermijn
Bevindingen volgen het bewaarbeleid voor audits per tenant, net als de andere scanners voor codebeveiliging. Bevindingen van gesloten PR’s worden opgeruimd door de dagelijkse job /jobs/license-scan-closed-pr-cleanup, omdat een gesloten PR nooit opnieuw wordt gescand.
Zie ook
- OSV-aanbevelingen: het scannen van afhankelijkheden op CVE’s (de aanvulling over kwetsbaarheden bij deze weergave van licenties).
- SBOM’s: de pakketinventaris waaraan de licenties hangen.
- Imagescan: de verwante scanner die dezelfde centrale scanrun en hetzelfde innamepatroon met
findings:writedeelt. - Beveiligingsfuncties in één oogopslag: de volledige matrix van maatregelen.