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
fremforge’s central scanner (the same one that runs SAST, dependency scanning, and image scanning) runs Trivy’s licence scan over your resolved dependencies on each PR. It runs on fremforge’s own runners — nothing is installed in your repository. 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 same central scan run and
findings:writeingest pattern. - Security features at a glance — the full control matrix.