License scanning
License scanning answers the what am I allowed to ship question — complementing the SBOM’s what’s in this image and the dependency scan’s what’s vulnerable. fremforge runs Trivy licence detection on every PR, classifies each resolved dependency’s licence into an SPDX-policy severity bucket, and surfaces the result next to the PR and in a dedicated org dashboard. It is on by default for every org — no enable step.
How it works
The consolidated pr-security.yaml workflow (the same one that runs SAST, dependency scanning, and image scanning) runs Trivy’s licence scan over your resolved dependencies on each PR. Unlike SAST or dependency scanning — which post a problems-only list — license scanning posts a full licence inventory: one finding per (package × licence) across every resolved dependency. The workflow then POSTs the inventory to fremforge:
POST https://frem.sh/_app/api/v1/license-scan/findingsThe runner PAT needs the findings:write scope (minted per-repo by the runner controller at workflow-install time). On a successful ingest, fremforge:
- Stores the findings. One row per finding in
license_scan_findings. Each scan supersedes the prior scan for the same(tenant, repo, pr_number)wholesale — a PR that drops a dependency stops surfacing its stale rows. Push scans (pr_numbernull) live in their own bucket so they don’t cross-replace PR scans. - Posts a PR comment summarising the findings (severity, licence, package, manifest), via the Forgejo issues API.
- Sets the
fremforge/license-scancommit status on the PR head SHA —successwhen advisory-only or all-allowlisted,failurewhen the policy gate is hit.
The Forgejo-side comment and status are best-effort: a Forgejo outage logs and downgrades to advisory but never fails the ingest. The stored findings are the source of truth; the PR surface is a convenience layer.
Severity buckets
License scanning uses a 7-bucket severity vocabulary (not the 4-tier CRITICAL/HIGH/… used by CVE scanning), matching Trivy’s SPDX-policy classification — ordered from least to most encumbering:
| Bucket | Meaning | Example licences |
|---|---|---|
unknown | Licence could not be determined (still surfaced for review) | NOASSERTION |
unencumbered | Public-domain-equivalent | CC0-1.0, Unlicense |
permissive | Use freely, minimal obligations | MIT, BSD-3-Clause, Apache-2.0 |
notice | Permissive but with attribution / notice obligations | Apache-2.0 (notice tier) |
reciprocal | Weak copyleft — file-level share-alike | MPL-2.0, EPL-2.0 |
restricted | Strong copyleft | GPL-2.0, LGPL-3.0 |
forbidden | Strictest copyleft / network-copyleft | AGPL-3.0, SSPL-1.0 |
Policy controls
Configure at Org admin → Code security → Licenses:
- Enabled — when off, ingest returns
202 {accepted: 0, status: 'disabled'}(the workflow still runs, the findings are just dropped). Default-on. - Block severity — the threshold at or above which a finding blocks merge. Default
forbidden. Any active (non-dismissed) finding at or above this bucket sets thefremforge/license-scancommit status tofailure; combine with branch protection requiring thefremforge/license-scancheck to actually block merge. - Allowlist — a list of SPDX licence IDs that are auto-dismissed at ingest. An allowlisted licence still gets a finding row (audit trail), but with
dismissed_atpre-set and actortenant_allowlist. The allowlist is re-derived fresh on every scan, so removing a licence from the allowlist correctly re-surfaces it on the next scan.
Findings at forbidden (→ critical) or restricted (→ high) that are newly introduced and not dismissed by a scan also fire the real-time security-alert hook; lower buckets are advisory only.
Dismissing findings
An org admin can dismiss individual findings (with a required reason, written to the audit log) from the Licenses tab. Manual dismissals are carried forward across the supersede-on-ingest re-scan, keyed on (licence ID, package, manifest path) — re-scanning a PR doesn’t resurrect a finding an admin already accepted. (Allowlist auto-dismissals are not carried forward; they’re re-derived from settings each scan, as above.)
Where findings surface
Org admin → Code security → Licenses lists active findings and recent dismissals. Each finding shows the severity bucket, the SPDX licence ID, the ecosystem:package, the licence category (declared vs concluded), and the manifest path. The PR comment links back to this dashboard.
Retention
Findings follow the per-tenant audit-retention policy, consistent with the other code-security scanners. Closed-PR findings are swept by the daily /jobs/license-scan-closed-pr-cleanup job, since a closed PR never re-scans.
See also
- OSV recommendations — dependency CVE scanning (the vulnerability complement to this licence view).
- SBOMs — the package inventory the licences attach to.
- Image scanning — the sibling scanner that shares the
pr-security.yamlworkflow andfindings:writeingest pattern. - Security features at a glance — the full control matrix.