Skip to main content
License scanning

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/findings

The runner PAT needs the findings:write scope (minted per-repo by the runner controller at workflow-install time). On a successful ingest, fremforge:

  1. 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_number null) live in their own bucket so they don’t cross-replace PR scans.
  2. Posts a PR comment summarising the findings (severity, licence, package, manifest), via the Forgejo issues API.
  3. Sets the fremforge/license-scan commit status on the PR head SHA — success when advisory-only or all-allowlisted, failure when 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:

BucketMeaningExample licences
unknownLicence could not be determined (still surfaced for review)NOASSERTION
unencumberedPublic-domain-equivalentCC0-1.0, Unlicense
permissiveUse freely, minimal obligationsMIT, BSD-3-Clause, Apache-2.0
noticePermissive but with attribution / notice obligationsApache-2.0 (notice tier)
reciprocalWeak copyleft — file-level share-alikeMPL-2.0, EPL-2.0
restrictedStrong copyleftGPL-2.0, LGPL-3.0
forbiddenStrictest copyleft / network-copyleftAGPL-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 the fremforge/license-scan commit status to failure; combine with branch protection requiring the fremforge/license-scan check 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_at pre-set and actor tenant_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